Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration Reference

Authoritative reference for every YAML configuration key recognized by the Nexus engine and its plugins. Tables are derived from each plugin’s Init() (and any parser helpers it calls) — not from prose docs. If you change a config key in source, update this page in the same commit.

Maintenance rule. Any addition, removal, rename, default change, or type change to a configuration key — at the engine level or in any plugin — must be reflected in this file. Per-plugin pages may add narrative, but this page is the single source of truth.

Conventions

  • Type column uses YAML-native names (string, int, bool, float, duration, list, map). duration is parsed by Go’s time.ParseDuration (e.g. 30s, 1m, 5m).
  • Default column shows the value used when the key is absent. *(none)* means no value is set; *(required)* means the plugin will fail to start without it; *(env)* means the value is read from an environment variable.
  • Path expansion. Every filesystem path supplied via configuration — engine sessions.root, plugin path, dir, file, cache_dir, scan_paths, system_prompt_file, schema_file, patterns_file, word_files, base_dir, working_dir, path_dirs, ingest watch[].path, etc. — is funneled through engine.ExpandPath. Bare ~ resolves to the user’s home directory; ~/foo resolves to <home>/foo. Relative paths are resolved against the engine’s working directory and not modified.

Validation

Configuration is validated strictly at boot, after YAML loading and before any plugin’s Init() runs. The engine compiles the top-level schema plus every active plugin’s schema (when the plugin advertises one) and validates each config block against it. Validation errors abort boot — they are aggregated across every plugin so a single boot attempt surfaces every issue at once, sorted, with the offending key path on each line:

config validation failed:
  - plugins.nexus.gate.token_budget.warning_threshold: unknown key "warning_threshold"
  - plugins.nexus.tool.web.timeoutt: unknown key "timeoutt" (did you mean "timeout"?)
2 errors; aborting boot

Unknown keys fail boot. Plugin schemas declare additionalProperties: false at every object level, so a typo in a key name is a hard error rather than a silently-ignored value. The validator emits a “did you mean” suggestion when a close match exists in the schema’s declared keys (Levenshtein distance ≤ 3 and strictly less than half the candidate’s length). This guarantees that the only keys you can write in YAML are the ones the engine actually reads — silent typos are not possible for plugins that ship a schema.

Deprecated keys log a warning but do not fail boot. Schemas mark these with deprecated: true. Move off the key when convenient; deprecation warnings are the engine’s signal that a future minor release may delete the shim.

Plugins without a schema (legacy or third-party) are skipped with a debug log. They are not blocked from booting. The engine’s own top-level keys (core, engine, capabilities, plugins.active, journal) are always validated against the engine schema.

Schema authoring

Plugins expose their schema by implementing the optional engine.ConfigSchemaProvider interface (defined in pkg/engine/config_validation.go). Conventions:

  • The schema lives at plugins/<id>/schema.json and is //go:embed-ed by a small schema.go in the same package; the plugin’s ConfigSchema() method returns the embedded bytes.
  • Schemas declare "$schema": "https://json-schema.org/draft/2020-12/schema" and must set "additionalProperties": false on every object level so unknown keys are caught.
  • Mark deprecated keys with "deprecated": true (a sibling of type, description, etc.). The validator walks the schema once and warns when a deprecated key is present in the user’s config.
  • The schema is canonical for the plugin’s config map — what Init() actually reads. Schema drift from real consumption is a bug; the smoke test in pkg/engine/configs_smoke_test.go validates every shipped YAML against every active plugin’s schema and is the canary for that drift.

Top-level structure

core:           # engine-level settings
engine:         # engine resilience knobs (shutdown drain, ...)
capabilities:   # capability → plugin-ID pinning (optional)
journal:        # durable per-session event log (always on; tunables only)
plugins:
  active: []    # plugin IDs (with optional /instance suffix)
  <plugin.id>:  # per-plugin config map
    key: value
KeyTypeDefaultDescription
coremap(see core section)Engine-level settings (logging, sessions, models).
enginemap(see engine section)Engine resilience knobs (shutdown drain budget).
capabilitiesmap(empty)Pin capability names to specific provider plugin IDs (e.g. search.provider: nexus.search.brave). Overrides default resolution (first active provider).
journalmap(see journal section)Tuning knobs for the always-on event journal. The journal cannot be disabled.
plugins.activelist[]Plugin IDs to activate. Order doesn’t matter — Requires() and Dependencies() are resolved automatically. Multi-instance plugins use a slash suffix: nexus.agent.subagent/researcher.
plugins.<id>map(none)Per-plugin configuration. Keys other than active are treated as plugin IDs.

Core engine

core

KeyTypeDefaultDescription
log_levelstringinfoGlobal log level: debug, info, warn, error.
tick_intervalduration1sInterval for the internal core.tick heartbeat.
max_concurrent_eventsint100Maximum concurrent event handlers across the bus.
logging.bootstrap_stderrboolfalseRegister a stderr sink at engine construction so pre-sink slog records appear on the terminal. Rejected at validation time when any of nexus.io.tui, nexus.io.browser, nexus.io.wails is active.
logging.buffer_sizeintDefaultLogRingSizeCapacity of the log/event ring buffers. Values <= 0 use the default.
sessions.rootstring~/.nexus/sessionsBase directory for session workspaces.
sessions.retentionstring30dRetention policy for old sessions.
sessions.id_formatstringtimestampSession ID format: timestamp, datetime_short.
agent_idstring(empty)Partitions per-agent storage and other per-agent state. Set by multi-agent embedders (the desktop shell). Empty in CLI / single-agent embedders, which collapses agent-scope storage to app-scope.
storage.rootstring~/.nexusData root for app- and agent-scope per-plugin storage. App-scope .db files land at <root>/plugins/<pluginID>/store.db; agent-scope at <root>/agents/<agent_id>/plugins/<pluginID>/store.db.
storage.busy_timeout_msint5000SQLite busy_timeout PRAGMA per handle (milliseconds).
storage.cache_size_kbint2048SQLite cache_size PRAGMA per handle (negative-form, in KiB).
storage.pool_max_idleint2*sql.DB.SetMaxIdleConns per handle.
storage.pool_max_openint4*sql.DB.SetMaxOpenConns per handle.
modelsmap(empty)Model role registry — see core.models below.

core.models

Maps role names → model configurations. Roles can be:

  • single model — map with provider, model, max_tokens,
  • fallback chain — list of single-model maps (tried in order on non-retryable error or exhausted retries; coordinated by nexus.provider.fallback),
  • fanout role — map with fanout: true and a providers: list (dispatched in parallel by nexus.provider.fanout).
Key (per role)TypeDefaultDescription
defaultstringbalancedName of the role used when a request specifies no role.
<role>.providerstring(required)Plugin ID of the LLM provider (e.g. nexus.llm.anthropic).
<role>.modelstring(required)Model identifier as understood by the provider.
<role>.max_tokensint(provider default)Maximum response tokens.
<role>.fanoutboolfalseIf true, treat as fanout role; providers: list is dispatched in parallel.
<role>.providerslist(required if fanout: true)List of model configs for fanout dispatch.

Example:

core:
  models:
    default: balanced
    reasoning:
      provider: nexus.llm.anthropic
      model: claude-opus-4-7
      max_tokens: 16384
    balanced:
      - provider: nexus.llm.anthropic
        model: claude-sonnet-4-6
        max_tokens: 8192
      - provider: nexus.llm.openai           # fallback
        model: gpt-4o
        max_tokens: 8192
    panel:
      fanout: true
      providers:
        - provider: nexus.llm.anthropic
          model: claude-sonnet-4-6
        - provider: nexus.llm.gemini
          model: gemini-2.5-pro

A role missing from core.models whose name contains a hyphen is treated as a raw model ID with no provider (backward-compat). Otherwise resolution fails.

Engine

Engine-level resilience knobs that don’t belong under core (which is for runtime settings like log level and tick interval) and aren’t journal-specific.

engine:
  shutdown:
    drain_timeout: 30s
  config_watch:
    enabled: false      # opt in to fsnotify hot-reload
    debounce: 1s
KeyTypeDefaultDescription
shutdown.drain_timeoutduration30sMaximum time the engine waits for in-flight bus dispatches to complete on Shutdown before the plugin teardown phase begins. Acts as a floor: a plugin implementing engine.DrainOverride can extend (but not shorten) the effective window so a single batch poller or MCP server can flush without operators bumping the global setting. Sub-second values are accepted but rarely useful.
config_watch.enabledboolfalseWhen true, the CLI starts an fsnotify watcher on the -config path and calls Engine.ReloadConfig on every debounced edit. Default off because production deploys often touch the config file mid-rollout and operators rarely want auto-reload during such windows. SIGHUP and the browser admin endpoint remain available regardless.
config_watch.debounceduration1sWindow across which fsnotify write/create events on the same file are coalesced into a single reload. Editors commonly fire two or three Write events per save; 1s is well above that storm but short enough that the operator perceives the reload as instant.

Hot reload

The engine supports applying a new config to a running process without restarting any unaffected plugins. The flow is two-phase:

  1. Validate phase (atomic). The new config is run through the same schema validation as boot; capability provider identity is pinned (a plugin advertising memory.history cannot be replaced by another provider mid-flight); the diff between current and new active sets is computed. Any failure here returns an error and leaves the engine untouched.
  2. Apply phase (best-effort). The diff is walked: removed plugins shut down, in-place reloaders accept their new config, restart-only plugins are torn down and re-initialized, and added plugins run the full lifecycle. Engine-level fields (drain_timeout, config_watch) are swapped atomically before per-plugin work.

A reload that fails midway through the apply phase logs the error and surfaces it to the caller; the engine is left in a best-effort consistent state. True rollback is not attempted because “undoing” a Shutdown is not generally possible.

Triggers:

  • SIGHUP to the CLI process re-reads the original -config path and applies the result. (SIGINT and SIGTERM continue to terminate the engine.)
  • POST /admin/reload-config on the browser plugin’s HTTP server. Body is empty (re-read original path) or {"path": "/abs/path/to/new.yaml"} for ad-hoc paths. No auth layer yet — alpha-only; front with a reverse proxy if exposed.
  • fsnotify watcher on the original path. Off by default; opt in via engine.config_watch.enabled: true. Debounced by engine.config_watch.debounce to absorb editor save bursts.

Plugin opt-in: ConfigReloader. A plugin that implements

type ConfigReloader interface {
    ReloadConfig(old, new map[string]any) error
}

receives the in-place hook on a config-only change instead of going through ShutdownInitReady. Implementations must be transactional from the bus’s perspective: returning an error must leave the plugin in its prior state. Plugins that don’t implement the interface go through the full restart path; both work, the in-place hook is just an optimization for plugins where a restart would drop in-progress work (active streams, bound listeners) the operator would notice.

Capability provider identity is pinned. Hot-reload rejects any config change that would resolve a currently-bound capability (e.g. memory.history) to a different concrete provider. The session has in-flight state bound to the existing provider; a silent swap would strip the operator’s history. Restart the engine to change capability providers.

Journal

The journal is the engine’s always-on durable event log. Every dispatched bus event lands as a JSONL envelope at <sessions.root>/<session_id>/journal/events.jsonl with a monotonic per- session sequence number, the parent dispatch’s seq (best-effort), and the veto outcome for before:* events. The journal cannot be disabled — it is core infrastructure underpinning crash recovery, deterministic replay, and observability projections.

journal:
  fsync: turn-boundary       # turn-boundary | every-event | none
  retain_days: 30
  rotate_size_mb: 4
  exclude_events:            # event types the journal must not record
    - core.tick
KeyTypeDefaultDescription
fsyncstringturn-boundaryDisk-flush policy. turn-boundary fsyncs once per agent.turn.end (good throughput, recovers to last completed turn). every-event fsyncs after every envelope (strongest crash guarantee). none skips explicit fsync (test-only).
retain_daysint30Age in days past which a session’s journal directory is removed on engine boot. 0 disables sweeping. In-flight sessions are never touched.
rotate_size_mbint4Active segment size threshold (MiB). When agent.turn.end lands and the active segment exceeds this, it is compressed into events-NNN.jsonl.zst and the active segment is truncated.
exclude_events[]string["core.tick"]Event types the journal must not record. Excluded events still dispatch to bus subscribers (otel, eval, custom plugins); only the durable log skips them, and their seq is not consumed so on-disk envelopes stay gap-free. Default suppresses the engine heartbeat. Set to [] to record everything.

Disk layout

~/.nexus/sessions/<id>/journal/
  header.json                 # schema_version, created_at, fsync_mode, session_id
  events.jsonl                # active segment (append-only)
  events-001.jsonl.zst        # rotated, zstd-compressed
  events-002.jsonl.zst
  cache/                      # args-keyed tool result cache
    <tool_id>/
      <sha256>.json           # one file per (tool, canonical_args) pair

Tool result cache

Every tool.invoke / tool.result pair is recorded under journal/cache/ keyed by sha256(tool_id || canonical_args). During replay, the short-circuit helper consults the cache first — same args produce the same result regardless of dispatch order, so replay survives memory-state divergence between the original and replay runs. On cache miss, the helper falls back to the FIFO stash seeded by the coordinator from the journal’s tool.result events.

The canonical args hash sorts keys recursively, so two semantically equivalent argument maps with different key iteration order map to the same cache file.

Journal projections

Plugins that need to derive files from event streams register a projection via Journal.SubscribeProjection(types, handler). The handler fires on the writer’s drain goroutine after the envelope lands on disk, so derived files always lag the durable record by zero envelopes. Projections also drive post-mortem regeneration: journal.ProjectFile(dir, types, handler) walks an existing journal and feeds the same handler — a derived file deleted between runs will rebuild from the journal at the next boot.

The shipped nexus.observe.thinking plugin no longer uses this hook itself: its thinking.step and plan.progress events are already in the journal alongside every other event, so the plugin acts purely as a UI feature flag for shells that want to surface thinking. Custom plugins that need their own derived view should adopt the projection pattern.

Deterministic replay

bin/nexus -config <path> -replay <session-id> re-runs a journaled session without external calls. The Anthropic / OpenAI / Gemini providers and the side-effecting tools (shell, file, code_exec, web, pdf, ask_user), along with the nexus.control.hitl plugin’s hitl.responded events, detect replay mode and emit the next journaled llm.response / tool.result from a FIFO stash seeded from the source journal in seq order. The replay coordinator drives io.input events; the live agent loop reacts as if the inputs were fresh.

Replay produces functional equivalence (same final assistant outputs, same memory state) rather than byte-identical event re-emission. Side- effecting plugins expose a LiveCalls() counter that stays at zero during replay — tests assert this to catch regressions.

Crash recovery

bin/nexus -config <path> -recall <session-id> resumes a session whose journal ended mid-turn. The engine detects the partial turn via coord.IsPartialTurn(), restores conversational memory from context/conversation.jsonl, and re-emits the io.input that started the unfinished turn so the live ReAct loop restarts it.

Phase 3 minimum: the partial turn restarts from scratch rather than mid-step resume. Mid-step resume (replay-stash-short-circuit the completed prefix, then live-fire the unanswered tool.invoke) is a future PR. Re-firing the input after a crash mints fresh seqs that append to the same journal alongside the orphaned partial-turn events; a subsequent --replay of a crash-resumed session sees both the orphaned and the re-fired io.input.

Plugin activation

plugins:
  active:
    - nexus.io.tui
    - nexus.llm.anthropic
    - nexus.agent.react
    - nexus.agent.subagent/researcher   # multi-instance suffix
    - nexus.agent.subagent/writer

Each entry in active may be followed by /<instance> to register a second copy of a multi-instance plugin (e.g. subagents). The base plugin ID + instance suffix forms the full ID used for per-plugin config:

plugins:
  nexus.agent.subagent/researcher:
    model_role: reasoning
    tool_name: spawn_researcher

A plugin with no configuration still parses cleanly without an explicit entry, but you may declare an empty map for clarity:

plugins:
  nexus.tool.file: {}
  nexus.observe.thinking: {}

Agents

nexus.agent.react

Source: plugins/agents/react/plugin.go.

KeyTypeDefaultDescription
planningboolfalseEmit plan.request before iterating; defers to a planner plugin.
model_rolestring(default)Role name from core.models.
system_promptstring(none)Inline system prompt (overrides system_prompt_file).
system_prompt_filestring(none)Path to file containing the system prompt.
parallel_toolsboolfalseRun multiple tool calls from a single LLM response in parallel.
max_concurrentint4Concurrency ceiling when parallel_tools: true.
tool_choicestring | map(none)Constrain tool selection. See “Tool choice” below.

Iteration limits are not an agent setting — enforce them with nexus.gate.endless_loop. ReAct’s required capabilities (memory.history, control.cancel, tool.catalog) are auto-activated by Requires() when no provider for those capabilities is already in plugins.active.

Tool choice

tool_choice accepts:

  • a string shorthand — tool_choice: required (or auto, any, none),
  • a map — tool_choice: { mode: tool, name: read_file },
  • a sequence — tool_choice: { sequence: [{ mode: required }, { mode: auto }] } applied per iteration; the last entry sticks.

Dynamic overrides arrive via agent.tool_choice events with duration: once (consumed after one iteration) or sticky (until cleared).

nexus.agent.planexec

Source: plugins/agents/planexec/plugin.go.

KeyTypeDefaultDescription
execution_model_rolestringbalancedRole used to execute each step.
replan_on_failurebooltrueRe-plan remaining work when a step fails (max 2 replans).
approvalstringneverPlan approval mode: always (block until user approves) or never.
system_promptstring(none)Inline system prompt.
system_prompt_filestring(none)Path to file containing the system prompt.

Step iteration and step counts are managed internally by the planner plugin that emits plan.result; they are not configured here.

nexus.agent.subagent

Source: plugins/agents/subagent/plugin.go. Multi-instance: register multiple copies via nexus.agent.subagent/<suffix>.

KeyTypeDefaultDescription
model_rolestring(default)Role used for the subagent’s LLM calls.
system_promptstring(none)Inline system prompt.
system_prompt_filestring(none)Path to file containing the system prompt.
tool_namestringspawn_<suffix> or spawn_subagentName of the spawn tool registered with the catalog.
tool_descriptionstring(auto)Description shown to the parent agent.

Depends on nexus.agent.react.

nexus.agent.orchestrator

Source: plugins/agents/orchestrator/plugin.go.

KeyTypeDefaultDescription
max_workersint5Concurrency cap for worker subagents.
max_subtasksint8Hard cap on subtasks (excess truncated).
worker_max_iterationsint10Iteration limit per worker (enforced via gate.endless_loop).
orchestrator_model_rolestringreasoningRole used for decomposition.
worker_model_rolestringbalancedRole used by workers.
synthesis_model_rolestringbalancedRole used for the final synthesis.
fail_fastboolfalseCancel remaining workers on the first failure.
system_promptstring(none)Inline system prompt.
system_prompt_filestring(none)Path to file containing the system prompt.

Depends on nexus.agent.subagent.

nexus.agent.postures

Source: plugins/agents/postures/plugin.go. Loads AgentPosture YAML files from the configured directories and advertises the posture.registry capability consumed by nexus.agent.delegate. fsnotify watches each directory for live edits; active sub-sessions keep their old posture, new invocations resolve the new one. See Postures for the AgentPosture schema.

KeyTypeDefaultDescription
scan_dirs[]string[]Directories scanned for *.yaml / *.yml posture files. Each entry runs through engine.ExpandPath so ~ expands.
debounce_msint250fsnotify reload debounce in milliseconds.

nexus.agent.delegate

Source: plugins/agents/delegate/plugin.go. Exposes the delegate tool that the LLM calls to invoke a registered posture. Requires the posture.registry capability (typically provided by nexus.agent.postures). Enforces budgets and recursion depth defined on each posture; results cached by posture version + task + context hash so posture edits invalidate stale entries. See Sub-agent delegation.

KeyTypeDefaultDescription
max_depthint3Hard cap on sub-agent recursion depth across all postures. Individual postures may set a lower cap via max_recursion_depth.
cache_sizeint256Capacity of the in-process LRU result cache (entries, not bytes). Zero disables eviction; the cache grows unbounded.
cachebooltrueSet false to disable result caching entirely.

nexus.agent.agui_remote

Source: plugins/agents/aguiremote/plugin.go. Surfaces one or more remote AG-UI agents as delegate/subagent targets. Each configured agent registers an LLM-facing tool (default delegate_agui_<name>); when the parent agent calls it, the plugin builds an AG-UI RunAgentInput from the delegated task, runs the remote agent over the AG-UI wire (HTTP POST + SSE) via the reusable AG-UI client, maps the remote run’s event stream onto the Nexus bus (text deltas → io.output; tool activity + message boundaries → subagent.*), and returns the remote run’s terminal outcome as the tool result. Failures (remote RunError, timeout, transport error, auth rejection, unresolved interrupt) surface as a clean tool error. Per-call timeout enforces budget; results are cached by endpoint + task + context hash. See Sub-agent delegation.

KeyTypeDefaultDescription
agentslist(required)Non-empty list of remote AG-UI agents to expose. Each entry is a mapping (see below).
timeout_secondsint120Default per-call timeout (seconds) applied to every remote agent. Overridable per agent and per call.
cache_sizeint128Capacity of the in-process LRU result cache (entries, not bytes). Zero disables eviction; the cache grows unbounded.
cachebooltrueSet false to disable result caching entirely.

Each agents[] entry:

KeyTypeDefaultDescription
namestring(required)Human-friendly identifier; used to derive the default tool name.
endpointstring(required)Full AG-UI POST endpoint URL (e.g. https://host/agui).
tool_namestringdelegate_agui_<name>Override the LLM-facing tool name.
descriptionstring(auto)Override the tool description shown to the LLM.
bearer_tokenstring(none)Static bearer token for the Authorization header. Prefer bearer_token_env.
bearer_token_envstring(none)Name of an environment variable holding the bearer token. Read at Init; used only when bearer_token is unset.
timeout_secondsint(plugin default)Per-agent default timeout (seconds), overriding the plugin-level timeout_seconds.

nexus.scene

Source: plugins/scene/plugin.go. Owns the per-session Scene store and registers the scene_create / scene_patch / scene_get / scene_list / scene_delete tools. Every patch is journaled to <session>/plugins/nexus.scene/scenes.jsonl so the replay primitive can reconstruct historical scene state. See Scenes.

No config keys today; activate the plugin in plugins.active and the default tools register at boot.


LLM providers

All providers share the same retry block schema (see “Retry” subtable below).

nexus.llm.anthropic

Source: plugins/providers/anthropic/plugin.go + auth.go, pricing.go, cache.go, thinking.go, multimodal.go, citations.go, structured_outputs.go, files.go, retry.go.

KeyTypeDefaultDescription
debugboolfalsePersist request/response bodies to the session for debugging.
auth_modestringapi_keyOne of api_key, bedrock, vertex.
api_keystring(env)Direct API key (used when auth_mode: api_key).
api_key_envstringANTHROPIC_API_KEYEnvironment variable to read the API key from.
bedrock.regionstring(env AWS_REGION)AWS region for Bedrock.
bedrock.access_key_idstring(env AWS_ACCESS_KEY_ID)AWS access key.
bedrock.access_key_id_envstringAWS_ACCESS_KEY_IDOverride the env var name.
bedrock.secret_access_keystring(env AWS_SECRET_ACCESS_KEY)AWS secret.
bedrock.secret_access_key_envstringAWS_SECRET_ACCESS_KEYOverride the env var name.
bedrock.session_tokenstring(env AWS_SESSION_TOKEN)Optional STS session token.
bedrock.session_token_envstringAWS_SESSION_TOKENOverride the env var name.
vertex.project / project_idstring(env GOOGLE_CLOUD_PROJECT)GCP project.
vertex.region / locationstringus-east5Vertex region.
vertex.sa_key_file / service_account_jsonstring(env GOOGLE_APPLICATION_CREDENTIALS)Path to the service-account JSON.
vertex.sa_key_file_env / service_account_json_envstringGOOGLE_APPLICATION_CREDENTIALSOverride the env var name.
cache.enabledboolfalseEnable prompt caching.
cache.systembooltrueMark the system prompt for caching when enabled.
cache.toolsbooltrueMark the tools array for caching when enabled.
cache.message_prefixint0Number of leading user messages to mark for caching.
cache.ttlstring5mCache TTL: 5m (ephemeral) or 1h (extended).
thinking.enabledboolfalseEnable extended thinking (Sonnet 4+, Opus 4+).
thinking.budget_tokensint8192Thinking token budget; -1 for dynamic, 0 to disable, 1024+ fixed.
thinking.include_thoughtsbooltrueSurface thinking content via thinking.step events.
multimodal.pdf_betaboolfalseSend the pdfs-2024-09-25 beta header for legacy PDF support.
citations.enabledboolfalseEnable citations on document blocks.
structured_outputs.modestringtooltool (synthetic tool) or native (response_format).
structured_outputs.beta_headerstring(none)Optional beta header when mode: native.
files.enabledboolfalseUse the Anthropic Files API for oversize attachments.
files.upload_thresholdint40960Minimum bytes before a file is uploaded; smaller files are inlined.
files.cache_uploadsbooltrueDeduplicate identical uploads within a session.
files.delete_on_shutdownboolfalseDelete uploaded files when the engine shuts down.
retry.*(see Retry block)Backoff configuration.
pricing.<model>.*map(embedded table)Override per-model token pricing — see “Pricing override”.

Retry block (shared by all providers)

KeyTypeDefaultDescription
retry.enabledbooltrueEnable retry on 5xx / 429.
retry.max_retriesint3Maximum attempts.
retry.initial_delayduration1sFirst backoff delay.
retry.max_delayduration60sMaximum delay between retries.
retry.backoffstringexponentialconstant, linear, exponential, or jitter.
retry.multiplierfloat2.0Multiplier for linear/exponential.
retry.statusesint listAnthropic: [429, 500, 502, 503, 529]
OpenAI/Gemini: [429, 500, 502, 503]
HTTP statuses to retry.

Pricing override

KeyTypeDefaultDescription
pricing.<model>.input_per_millionfloat(embedded default)Cost per million input tokens.
pricing.<model>.output_per_millionfloat(embedded default)Cost per million output tokens.
pricing.<model>.cache_read_per_millionfloat(derived from input)Anthropic: 0.10×input; OpenAI: 0.5×input.
pricing.<model>.cache_write_5m_per_millionfloat(derived: 1.25×input)Anthropic only.
pricing.<model>.cache_write_1h_per_millionfloat(derived: 2.0×input)Anthropic only.

nexus.llm.openai

Source: plugins/providers/openai/plugin.go.

KeyTypeDefaultDescription
debugboolfalsePersist request/response bodies to the session.
auth_modestringopenaiopenai, azure_key, or azure_aad.
api_keystring(env)Direct API key (auth_mode: openai, also fallback for Azure Files API).
api_key_envstringOPENAI_API_KEYEnvironment variable for the key.
base_urlstringhttps://api.openai.com/v1Override for proxies / OpenAI-compatible endpoints.
azure.endpointstring(required for Azure)Azure OpenAI endpoint URL.
azure.api_keystring(env AZURE_OPENAI_API_KEY)Azure key (when auth_mode: azure_key).
azure.api_key_envstringAZURE_OPENAI_API_KEYOverride the env var name.
azure.api_versionstring2024-12-01-previewAzure OpenAI API version.
azure.use_msiboolfalseUse Managed Service Identity (auth_mode: azure_aad); otherwise falls back to Azure CLI auth.
files.enabledboolfalseUse the Files API.
files.purposestringassistantsFile purpose category.
files.upload_thresholdint40960Minimum bytes to upload.
files.cache_uploadsbooltrueDeduplicate within a session.
files.delete_on_shutdownboolfalseDelete on shutdown.
reasoning.enabledboolfalseEnable o-series reasoning.
reasoning.budget_tokensint10000Reasoning token budget.
force_reasoningboolfalseForce reasoning even for non-o-series models (experimental).
multimodal.visionbooltrueAllow image inputs (GPT-4V).
retry.*(shared Retry block)Backoff configuration.
pricing.<model>.*map(embedded table)Override per-model pricing.

nexus.llm.gemini

Source: plugins/providers/gemini/plugin.go.

KeyTypeDefaultDescription
debugboolfalsePersist request/response bodies.
api_keystring(env GEMINI_API_KEY or GOOGLE_API_KEY)Public Generative Language API key.
api_key_envstring(tries both env vars above)Override the env var name.
vertex.projectstring(env GOOGLE_CLOUD_PROJECT)Project ID for Vertex AI.
vertex.region / locationstringus-central1Vertex region.
vertex.sa_key_filestring(env GOOGLE_APPLICATION_CREDENTIALS)Service-account JSON path.
vertex.sa_key_file_envstringGOOGLE_APPLICATION_CREDENTIALSOverride the env var name.
thinking.enabledboolfalseEnable thinking on Gemini 2.5+.
thinking.budget_tokensint8000Thinking token budget.
thinking.include_thoughtsbooltrueSurface thinking via thinking.step.
code_executionboolfalseEnable Gemini’s built-in code-execution tool.
cache.enabledboolfalseEnable prompt caching (Gemini 2.0+).
cache.min_tokensint1000Minimum tokens required for caching.
cache.ttlstring5mCache TTL: 5m or 1h.
retry.*(shared Retry block)Backoff configuration.
pricing.<model>.*map(embedded table)Override per-model pricing.

nexus.provider.fallback

Source: plugins/providers/fallback/plugin.go. No plugin-level config — the fallback chain is defined by listing multiple providers under a single role in core.models. This plugin coordinates the re-emission to the next provider on non-retryable errors.

nexus.provider.fanout

Source: plugins/providers/fanout/plugin.go.

KeyTypeDefaultDescription
strategystringallSelection strategy: all (return first to arrive), llm_judge, heuristic, user.
deadline_msint30000Milliseconds to wait before forcing selection.
heuristic.preferstringlongestUsed when strategy: heuristic: longest, shortest, fastest, cheapest.
heuristic.require_finishboolfalseOnly consider responses with finish_reason: end_turn.
judge.rolestring(none)Model role for the judge LLM call when strategy: llm_judge.

A role becomes a fanout role when its core.models entry sets fanout: true; the fanout plugin watches before:llm.request for those roles. For strategy: user, the plugin emits provider.fanout.choose and waits for provider.fanout.chosen from the IO layer.

Search providers (search.provider capability)

Each search-provider plugin handles search.request events and writes results back into the payload. They share the same shape:

PluginSource
nexus.search.braveplugins/search/brave/plugin.go
nexus.search.anthropic_nativeplugins/search/anthropic_native/plugin.go
nexus.search.openai_nativeplugins/search/openai_native/plugin.go
nexus.search.gemini_nativeplugins/search/gemini_native/plugin.go
KeyTypeDefaultNotes
api_keystring(env, see below)Direct API key.
api_key_envstringprovider default (see below)Override the env var name.
modelstringprovider default (see below)Only on native search providers.
base_urlstringprovider default (see below)Override the upstream endpoint. Available on brave, openai_native, and gemini_native. Primarily useful for httptest-driven integration tests; OpenAI-compatible proxies can also be wired here.
timeoutduration15s (Brave), 30s (others)HTTP request timeout.

Provider defaults:

Providerapi_key_envmodelbase_url default
nexus.search.braveBRAVE_API_KEYn/ahttps://api.search.brave.com/res/v1/web/search
nexus.search.anthropic_nativeANTHROPIC_API_KEYclaude-haiku-4-5-20251001n/a (no override)
nexus.search.openai_nativeOPENAI_API_KEYgpt-4o-minihttps://api.openai.com/v1/responses
nexus.search.gemini_nativeGEMINI_API_KEY / GOOGLE_API_KEYgemini-2.5-flashhttps://generativelanguage.googleapis.com/v1beta

If multiple providers register the search.provider capability, pin one explicitly via the top-level capabilities: block.


Tools

nexus.tool.shell

Source: plugins/tools/shell/plugin.go. Routes commands through pkg/engine/sandbox so the kernel surface is a single audited boundary.

KeyTypeDefaultDescription
working_dirstring(session files dir)Working directory for executions.
timeoutduration30sPer-command timeout.
sandbox.backendstringhostSandbox tier: host (current behaviour). Future: gvisor, firecracker, landlock.
sandbox.allowed_commandslist(none — all allowed)Whitelist of base command names.
sandbox.path_dirslist(none)Directories prepended to PATH.
sandbox.env_restrictboolfalseStrip sensitive env vars (AWS, Google, Azure, Anthropic API keys) before execution.
sandbox.timeoutduration30sPer-command default; timeout above wins per-call.

nexus.tool.file

Source: plugins/tools/fileio/plugin.go. Registers read_file, write_file, check_file_size, list_files, read_image, read_document.

read_image and read_document return a MessagePart on ToolResult.OutputParts; the memory plugin copies parts onto the resulting tool-role Message.Parts so the next LLM request sees the multimodal content. Provider plugins resolve MessagePart.URI = "nexus-blob:<sha>" references from the per-session blob store at ~/.nexus/sessions/<id>/blobs/ when the payload was stored, or use the inline MessagePart.Data when it was inlined.

KeyTypeDefaultDescription
base_dirstring(session files dir)Base directory for file operations.
allow_external_writesboolfalsePermit reads/writes outside base_dir.
blob_store.byte_budgetint2147483648 (2 GiB)Soft cap on total stored blob bytes per session. 0 = unbounded. Applied via LRU sweep after each blob put.
blob_store.inline_thresholdint262144 (256 KiB)Payloads at or below this size are inlined on the MessagePart instead of being stored as a blob.
tools.<tool_name>booltrue for eachPer-tool enable/disable: read_file, write_file, check_file_size, list_files, read_image, read_document.

nexus.tool.catalog

Source: plugins/tools/catalog/plugin.go. No configuration. Provides the tool.catalog capability — a shared registry queried via tool.catalog.query. Required by nexus.agent.react.

nexus.tool.web

Source: plugins/tools/web/plugin.go. Registers web_search, web_fetch, and fetch_page_image. Requires the search.provider capability for web_search. fetch_page_image requires screenshot_provider config — without it, the tool surfaces a clear error at invoke time.

KeyTypeDefaultDescription
search.default_countint10Default result count for web_search.
search.default_safe_searchstringmoderateoff, moderate, strict.
search.default_languagestring(none)BCP-47 language tag (e.g. en, es-MX).
fetch.user_agentstringNexus/0.1 (+https://...)User-Agent header for web_fetch.
fetch.timeoutduration20sHTTP timeout.
fetch.max_sizeint5242880 (5 MB)Maximum response body size.
fetch.default_extractstringreadabilityreadability or raw.
fetch.allowed_domainslist(none — allow all)Allowlist of domains.
fetch.blocked_domainslist(none)Blocklist of domains.
fetch.follow_redirectsbooltrueFollow HTTP redirects.
fetch.max_redirectsint5Maximum redirect chain length.
screenshot_provider.urlstring(required for fetch_page_image)Endpoint of the external screenshot service (urlbox, screenshotapi.net, browserless, …).
screenshot_provider.methodstringPOSTGET or POST.
screenshot_provider.api_key_envstring(none)Env var holding the bearer token / API key. POST sends Authorization: Bearer <key>; GET appends api_key=<key> to the query.
screenshot_provider.url_param_namestringurlField name carrying the target URL.
screenshot_provider.request_templatemap(empty)Extra fields merged into the JSON body (POST) or query string (GET).
screenshot_provider.headersmap(empty)Fixed headers sent with every provider request.
blob_store.byte_budgetint2147483648 (2 GiB)Soft cap on total stored blob bytes per session for fetch_page_image. 0 = unbounded.
blob_store.inline_thresholdint262144 (256 KiB)Payloads at or below this size are inlined on the MessagePart instead of stored as a blob.

Source: plugins/tools/knowledge_search/plugin.go. Requires embeddings.provider and vector.store.

KeyTypeDefaultDescription
tool_namestringknowledge_searchName of the registered tool.
top_kint5Default chunks to return (LLM may override; capped at 50).
include_metadatabooltrueInclude vector metadata alongside chunks.
namespaceslist(required)Allowed vector store namespaces.
default_namespaceslist(required)Namespaces searched when the LLM doesn’t specify.

The active embeddings.provider plugin owns the model choice — there is no consumer-side override. Configure the model once on the provider plugin (e.g. nexus.embeddings.openai.model).

nexus.tool.pdf

Source: plugins/tools/pdf/plugin.go. Registers read_pdf. Two modes selectable per call via mode argument or default_mode config:

  • text (default) — extract text via pdftotext (poppler-utils). Requires pdftotext on PATH (or pdftotext_bin config).
  • document — return the raw PDF bytes as a file MessagePart on ToolResult.OutputParts for native multimodal providers (Anthropic, Gemini). No poppler call. first_page, last_page, and layout arguments are ignored in this mode.

If default_mode is document and pdftotext is missing, the plugin boots; text mode then surfaces an actionable error per call.

KeyTypeDefaultDescription
pdftotext_binstringpdftotextPath or name of the pdftotext binary.
pdfinfo_binstringpdfinfoPath or name of pdfinfo (optional).
timeoutduration30sPer-extraction timeout.
save_to_sessionboolfalsePersist extracted text to session files.
save_file_namestring(derived from PDF)Custom filename for the saved text.
default_modestringtexttext or document. Default read_pdf mode when the LLM doesn’t supply one.

nexus.tool.screenshot

Source: plugins/tools/screenshot/plugin.go. Registers take_screenshot. Captures the full screen as PNG and emits an image MessagePart on ToolResult.OutputParts. Capture path is platform-specific:

  • darwin: screencapture -t png -x <tmpfile>
  • linux: gnome-screenshot -f <tmpfile>, then grim <tmpfile>, then ImageMagick’s import -window root <tmpfile> as fallbacks
  • other platforms: emits ToolResult.Error: "screenshot not supported on this platform"
KeyTypeDefaultDescription
timeoutduration15sPer-capture subprocess timeout.
blob_store.byte_budgetint2147483648 (2 GiB)Soft cap on total stored blob bytes per session. 0 = unbounded.
blob_store.inline_thresholdint262144 (256 KiB)Payloads at or below this size are inlined on the MessagePart instead of stored as a blob.

nexus.tool.opener

Source: plugins/tools/opener/plugin.go. Registers open_path.

KeyTypeDefaultDescription
open_cmdstringplatform default (open macOS, xdg-open Linux, start Win)Override the platform “open” command.
timeoutduration10sPer-open timeout.

nexus.control.hitl

Source: plugins/control/hitl/plugin.go. The unified human-in-the-loop primitive. Registers the LLM-facing ask_user tool with an extended schema (prompt, mode, choices, default_choice_id, deadline_seconds) and routes hitl.requested / hitl.responded events between requesters (the tool, gates, memory plugins) and IO surfaces. Replaces the prior nexus.tool.ask. See Human-in-the-Loop plugin docs.

KeyTypeDefaultDescription
registry.enabledboolfalseMirror every hitl.requested to disk and watch for response files written by nexus hitl respond, webhook handlers, etc.
registry.dirstring~/.nexus/hitlFilesystem directory the registry uses for <id>.request.yaml / <id>.response.yaml pairs. Tilde expansion via engine.ExpandPath. Created at boot if missing.

nexus.control.hitl_synthesizer

Source: plugins/control/hitl_synthesizer/plugin.go. Optional companion to nexus.control.hitl that renders context-aware approval prompts via a small/cheap LLM. Advertises the hitl.prompt_synthesizer capability; emitters opt in by setting HITLRequest.PromptSynthesizer = "hitl.prompt_synthesizer" and leaving Prompt empty. Subscribes to before:hitl.requested (canonical vetoable entry point, pointer payload — every in-tree HITL emitter publishes here first) and to hitl.requested as a backward compat fallback for out-of-tree emitters that publish a *HITLRequest pointer directly, ahead of every IO plugin so the rendered text is in place before the operator sees the prompt. Synthesised prompts are cached on disk under <session>/plugins/nexus.control.hitl_synthesizer/cache.jsonl, keyed by (action_kind, sha256(action_ref)). See HITL Prompt Synthesizer docs.

KeyTypeDefaultDescription
model_rolestringquickModel role (resolved via core.models) used for synthesis.
max_action_ref_charsint1500ActionRef truncation budget (in JSON characters) before sending to the model.
cache_enabledbooltrueToggle the on-disk cache. Disable for debugging or strict-determinism runs.
fallback_promptstringApprove action: {{.action_kind}}Go text/template over {action_kind, action_ref, requester_plugin, request_id} used when synthesis fails.

nexus.tool.code_exec

Source: plugins/tools/codeexec/plugin.go. Registers run_code (Go). Two compilers selected via compiler:

  • yaegi-host (default) — in-process Yaegi interpreter. Full dynamic bindings (tools.*, parallel.*, skill helpers); no kernel isolation.
  • yaegi-wasm — embedded Yaegi runner inside a wazero-managed Wasm sandbox. Capability-gated I/O via nexus_sdk/{http,fs,exec,env}. v1 forfeits tools.*, parallel.*, and skill helpers — the bridge SDK does not surface them.

Multimodal helper (host compiler only): scripts may import "nexus" and call nexus.ReturnImage(data []byte, mimeType string) to attach images to the resulting tool.result (alongside main.Run’s JSON return). Multiple calls stack in script order; the routing follows the same inline / blob-store threshold pattern used by other multimodal tools.

KeyTypeDefaultDescription
compilerstringyaegi-hostyaegi-host or yaegi-wasm. The latter requires sandbox.backend: wasm.
timeout_secondsint30Script timeout in seconds.
max_output_bytesint65536Maximum captured output.
max_workersintruntime.NumCPU()Concurrency cap for parallel.* (yaegi-host only).
persist_scriptsbooltrueWrite executed scripts to session files.
reject_goroutinesbooltrueReject scripts that spawn goroutines.
allowed_packageslist(stdlib whitelist)Importable stdlib packages.
blob_store.byte_budgetint2147483648 (2 GiB)Soft cap on total stored blob bytes per session for nexus.ReturnImage payloads. 0 = unbounded.
blob_store.inline_thresholdint262144 (256 KiB)Payloads at or below this size are inlined on the MessagePart instead of being stored as a blob.
sandbox.backendstringhostRequired wasm for compiler: yaegi-wasm. Other backends (host) reject KindGoWasm requests.
sandbox.cache_dirstring(none)Persistent wazero compilation cache. Recommended for fast cold-start across processes.
sandbox.timeoutduration30sDefault per-call wasm timeout.
sandbox.net.policystringdenydeny or allow_hosts. Empty allow_hosts = deny all.
sandbox.net.allow_hostslist(empty)Exact-match hostname allowlist for nexus_sdk/http.
sandbox.fs_mountslist(empty)List of {host, guest, mode} triples. mode is ro (default) or rw. Backs nexus_sdk/fs.
sandbox.exec_allowedlist(empty)Allowlist of commands invokable from nexus_sdk/exec.Run. Empty = deny.
sandbox.envmap(empty)Sandbox-scoped env values returned by nexus_sdk/env.Get. Never the host’s real env.

The engine substitutes ${session_id} in any string under the sandbox: block at session start, so per-session host paths can be hard-coded: host: ~/.nexus/sessions/${session_id}/files.


Memory

nexus.memory.simple

Source: plugins/memory/simple/plugin.go. No configuration. Provides memory.history. Unbounded, in-memory, no persistence.

nexus.memory.capped

Source: plugins/memory/capped/plugin.go. Provides memory.history. This is the default memory.history provider auto-activated by nexus.agent.react.

KeyTypeDefaultDescription
max_messagesint100Sliding window size; older messages dropped (with tool-pair safety).
persistbooltruePersist to context/conversation.jsonl in the session workspace.

nexus.memory.summary_buffer

Source: plugins/memory/summary_buffer/plugin.go. Provides both memory.history and memory.compaction.

KeyTypeDefaultDescription
strategystringmessage_countTrigger: message_count, token_estimate, turn_count.
message_thresholdint50Used when strategy: message_count.
token_thresholdint30000Used when strategy: token_estimate.
turn_thresholdint10Used when strategy: turn_count.
chars_per_tokenfloat4.0Token estimation ratio.
max_recentint8Messages kept verbatim; older messages are summarized.
model_rolestringquickRole used for the summary call.
modelstring(none)Explicit model ID (ignored if model_role is set).
promptstring(default)Inline summary prompt. The default prompt is reasoning-preservation aware: it instructs the summariser to wrap segments in <summary topic="…" compressed-from-turns="…">…</summary> and end with a ## Preserved Kinds: trailer. Overriding loses both behaviours.
prompt_filestring(none)Path to a summary prompt file (overrides prompt).
quality_retryboolfalseWhen true, the plugin re-runs the summariser once with a stricter prompt if the trailer omits any required preserved kind. Off by default for backwards compatibility.
require_preserved_kinds[]string["decision", "rationale"]Kinds whose presence in the trailer is required when quality_retry: true. Allowed values: decision, rationale, error, next_step, technical_detail.

nexus.memory.compaction

Source: plugins/memory/compaction/plugin.go. Provides memory.compaction as an external coordinator (separate from history buffers).

KeyTypeDefaultDescription
strategystringmessage_countTrigger: message_count, token_estimate, turn_count.
message_thresholdint50Used when strategy: message_count.
token_thresholdint30000Used when strategy: token_estimate.
turn_thresholdint10Used when strategy: turn_count.
chars_per_tokenfloat4.0Token estimation ratio.
model_rolestringquickRole used for the compaction LLM call.
modelstring(none)Explicit model ID.
promptstring(default)Inline compaction prompt.
prompt_filestring(none)Path to a prompt file.
protect_recentint4Recent messages exempt from compaction.
persistbooltruePersist snapshots and archives to the session workspace.
require_approval.enabledboolfalseEmit hitl.requested before committing the summary back into history. Off = unchanged behavior.
require_approval.default_choicestring(none)Choice ID picked when the deadline expires (e.g. reject). Empty = treat timeout as cancelled.
require_approval.timeoutduration(none)Optional deadline (5m, 30s, …).
require_approval.match.size_threshold_bytesint(any)Only require approval when the summary is at least this many bytes.

nexus.memory.longterm

Source: plugins/memory/longterm/plugin.go. Provides memory.longterm. Registers LLM tools: memory_write, memory_read, memory_list, memory_delete.

KeyTypeDefaultDescription
scopestringagentagent, global, or both.
pathstring~/.nexus/memory/Base directory for memory files.
agent_idstring(auto)Agent identifier when scope includes agent.
auto_loadbooltrueLoad memory index at startup and inject into the system prompt.
auto_save_instructionsstring(none)Instructions appended to the system prompt (e.g. “save important decisions”).
require_approval.enabledboolfalseEmit hitl.requested before persisting writes. Off by default; on = every write blocks until an operator responds.
require_approval.default_choicestring(none)Choice ID picked when the deadline expires (e.g. reject). Empty = treat timeout as cancelled.
require_approval.timeoutduration(none)Optional deadline (5m, 30s, …).
require_approval.match.key_globstring(any)Only require approval when the entry key matches this glob.
require_approval.match.size_threshold_bytesint(any)Only require approval when the content is at least this many bytes.

nexus.memory.vector

Source: plugins/memory/vector/plugin.go. Provides memory.vector. Requires embeddings.provider and vector.store.

KeyTypeDefaultDescription
namespacestringmemory-{instanceID}Vector store namespace.
top_kint5Recalled matches per query.
min_similarityfloat0.0Minimum cosine similarity (0 disables filtering).
auto_store_compactionbooltrueStore summaries when memory.compacted fires.
auto_store_user_inputboolfalseStore user messages on every input (opt-in).
section_priorityint45Priority of the recalled-memory section in the system prompt.
recall_via_hybridboolfalseWhen search.hybrid is active, route recall queries through it instead of direct vector lookup. Off by default — adds the lexical leg’s latency to every user input.
store_imagesboolfalseEmbed image attachments on UserInput.Files (any MimeType starting with image/) via the multimodal embeddings.provider and store the resulting vector under image_namespace. Requires a multimodal adapter (e.g. nexus.embeddings.cohere_multimodal); text-only adapters will reject and the path no-ops. Off by default — opt-in.
image_namespacestring<namespace>-imagesVector store namespace for image embeddings. Kept separate from the text namespace so similarity queries can target one or the other.
require_approval.enabledboolfalseEmit hitl.requested before each vector.upsert. Off = unchanged behavior.
require_approval.default_choicestring(none)Choice ID picked when the deadline expires (e.g. reject). Empty = treat timeout as cancelled.
require_approval.timeoutduration(none)Optional deadline (5m, 30s, …).
require_approval.match.namespace_globstring(any)Only require approval when the configured namespace matches this glob.
require_approval.match.size_threshold_bytesint(any)Only require approval when the document content is at least this many bytes.

nexus.memory.tool_result_clear

Source: plugins/memory/tool_result_clear/plugin.go. Live curator that replaces stale tool-result bodies in outgoing LLMRequest.Messages with an inline <tool_result … cleared="true" …/> envelope. The original call/result pairing stays in history so the agent retains the fact of the call. Runs at priority 12 on before:llm.request (after nexus.discovery.progressive).

KeyTypeDefaultDescription
enabledbooltrueToggle the curator.
age_turnsint5Clear tool results older than this many turns when also exceeding the size threshold.
size_bytes_thresholdint1000Skip clearing for result bodies smaller than this many bytes.
preserve_recent_kinds[]string["error", "user_question"]Result kinds that are never cleared regardless of age.
drop_strategystringreplace_with_envelopereplace_with_envelope keeps the call/result pair with a marker body; full_drop removes the message entirely (risks tool_use/tool_result pairing breakage).

Emits memory.tool_result_cleared (per cleared call) and memory.curated (stability descriptor for the cache-aware prompt builder).

nexus.memory.tool_def_pruner

Source: plugins/memory/tool_def_pruner/plugin.go. Drops individual tool definitions from outgoing LLMRequest.Tools when they have been idle past unused_turns_threshold. Pairs with nexus.discovery.progressive — progressive scopes by class, this scopes per tool. Runs at priority 14 on before:llm.request.

KeyTypeDefaultDescription
enabledbooltrueToggle the pruner.
unused_turns_thresholdint6Drop a tool definition after this many consecutive turns without an invocation.
never_prune[]string["discover","ask_user"]Tool names exempt from pruning (e.g. discovery’s meta-tool, HITL ask-user).

Emits memory.tool_def_pruned and memory.curated. The MemoryCurated event marks cache_invalidates: true because the tool list is part of the session-cached prefix.

nexus.memory.topic_pruner

Source: plugins/memory/topic_pruner/plugin.go. Detects topic boundaries in user input and emits memory.topic_shift_detected. Two signals are combined:

  • Explicit-phrase matching (“different question”, “new topic”, “let’s move on”, …) — cheap, deterministic.
  • Embedding similarity drop against the rolling topic centroid — runs only when an embeddings.provider is active.

The plugin does not itself rewrite history; it surfaces the shift so other plugins (summary buffer, compaction) can react. Topic boundaries are journalled for replay determinism.

KeyTypeDefaultDescription
enabledbooltrueToggle the pruner.
similarity_thresholdfloat0.55Cosine similarity below which a new user input flags a topic shift. Used only when an embeddings.provider is active.
keep_last_topic_fullbooltrueReserved — informs downstream consumers whether the most recent topic should remain verbatim.
explicit_phrases[]string["different question", "different topic", "new topic", "new question", "let's move on", "moving on", "change of subject", "switching gears", "unrelated:", "separately,", "on a different note"]Lowercase substrings that signal a topic shift. Replacing the list disables the defaults.

Emits memory.topic_shift_detected and memory.curated. Same-turn duplicate signals are debounced.


Embeddings

nexus.embeddings.openai

Source: plugins/embeddings/openai/plugin.go. Provides embeddings.provider.

KeyTypeDefaultDescription
api_keystring(required, or via env)OpenAI API key.
api_key_envstringOPENAI_API_KEYOverride env var name.
base_urlstringhttps://api.openai.com/v1/embeddingsEndpoint (Azure / OpenAI-compatible proxies).
modelstringtext-embedding-3-smallDefault model.
timeoutduration30sHTTP timeout.

nexus.embeddings.mock

Source: plugins/embeddings/mock/plugin.go. Provides embeddings.provider. Deterministic hash-based vectors; opt-in via plugins.active.

KeyTypeDefaultDescription
dimensionsint128Vector dimensionality.
modelstringmock-embeddingModel ID string returned to callers.

nexus.embeddings.cohere_multimodal

Source: plugins/embeddings/cohere_multimodal/plugin.go. Provides embeddings.provider via Cohere Embed v3 (POST /v2/embed). Multimodal: accepts text and image inputs in a single batch through EmbeddingsRequest.Inputs. Opt-in: registered but not in the default plugins.active list — wire it explicitly when image embeddings are required (e.g. nexus.memory.vector with store_images: true).

For an EmbeddingsInput carrying ImageURI with the nexus-blob: scheme, the plugin resolves bytes via the per-session blob store. When the engine boots without a session (rare, mostly tests), the plugin errors clearly — callers must inline bytes via EmbeddingsInput.Image instead.

KeyTypeDefaultDescription
api_keystring(required, or via env)Cohere API key.
api_key_envstringCOHERE_API_KEYOverride env var name.
base_urlstringhttps://api.cohere.comCohere API base URL. The plugin appends /v2/embed itself.
modelstringembed-english-v3.0Cohere embedding model.
input_typestringsearch_documentCohere input_type (e.g. search_document, search_query, classification, clustering, image).
timeoutduration30sHTTP timeout.

Vector store

nexus.vectorstore.chromem

Source: plugins/vectorstore/chromem/plugin.go. Provides vector.store.

KeyTypeDefaultDescription
pathstring~/.nexus/vectorsDirectory for persistent storage (one subdir per namespace).
compressboolfalseGzip-compress JSON on disk.

Lexical store

nexus.vectorstore.sqlite_fts

Source: plugins/vectorstore/sqlite_fts/plugin.go. Provides search.lexical. BM25 ranking via SQLite FTS5 — pure Go, no CGO. Backing storage comes from the engine’s per-plugin storage capability; the scope: knob picks where the underlying store.db lands.

KeyTypeDefaultDescription
scopestringsessionStorage scope for the FTS index: session, agent, app. Knowledge-base-style corpora that survive across sessions should use agent or app.

Each namespace becomes a separate FTS5 virtual table (lex_<safe_namespace>) inside the scoped store.db. The provider auto-creates tables on first upsert; missing-namespace queries return zero results without error.


RAG

nexus.rag.hybrid

Source: plugins/rag/hybrid/plugin.go. Provides search.hybrid — a fusion orchestrator that runs vector + lexical retrieval in parallel and combines results via Reciprocal Rank Fusion or weighted score combination.

KeyTypeDefaultDescription
fusionstringrrfFusion strategy: rrf (rank-only, weight-free) or weighted (linear combination over min-max-normalized per-backend scores).
rrf_kint60RRF smoothing constant. Lower values weight top ranks more heavily.
weights.vectorfloat0.7Per-backend bias for weighted fusion.
weights.lexicalfloat0.3Per-backend bias for weighted fusion.
retrieve_kint50Per-backend candidate count gathered before fusion.
fuse_toint20Default post-fusion top-N when the caller does not specify K.
reranker.enabledboolfalseApply a post-fusion reranker pass via the search.reranker capability. Off by default — enable when a reranker provider is active and the latency budget allows.

Requires embeddings.provider, vector.store, and search.lexical. Per-query LexicalBias (range -1..1) on the hybrid.query event tilts fusion weights without rewriting config — positive favors lexical, negative favors vector.

When search.hybrid is active, nexus.tool.knowledge_search automatically routes through it instead of querying the vector store directly. nexus.memory.vector opts in via recall_via_hybrid: true (off by default because the lexical leg adds latency on every user input).


Rerankers (search.reranker capability)

Three providers ship; activate one (rarely more than one). The hybrid orchestrator’s reranker.enabled: true knob switches them on; without that, plugins can still emit reranker.rerank events directly.

nexus.rag.reranker.cohere

Source: plugins/rag/reranker/cohere/plugin.go. Cohere Rerank v2 API.

KeyTypeDefaultDescription
api_keystring(none)Cohere API key. Mutually exclusive with api_key_env.
api_key_envstringCOHERE_API_KEYEnv var to read the key from when api_key is unset.
modelstringrerank-english-v3.0Cohere reranker model identifier.
timeout_msint10000HTTP timeout in milliseconds.
api_basestring(Cohere v2 endpoint)Override for testing / private deployments.

nexus.rag.reranker.jina

Source: plugins/rag/reranker/jina/plugin.go. Jina AI Reranker API.

KeyTypeDefaultDescription
api_keystring(none)Jina API key. Mutually exclusive with api_key_env.
api_key_envstringJINA_API_KEYEnv var to read the key from when api_key is unset.
modelstringjina-reranker-v2-base-multilingualJina reranker model identifier.
timeout_msint10000HTTP timeout in milliseconds.
api_basestring(Jina v1 endpoint)Override for testing / private deployments.

nexus.rag.reranker.local

Source: plugins/rag/reranker/local/plugin.go. Pure-Go TF-IDF cosine reranker. No API calls, no model files, no extra dependencies. Quality is materially below a real cross-encoder; use it for offline / cost-sensitive deployments and as the zero-dep fallback. Future phase will add an ONNX- backed BGE Reranker behind a build tag.

KeyTypeDefaultDescription
min_token_lengthint2Drop tokens shorter than this during scoring.
disable_stopwordsboolfalseSkip the built-in English stopword filter.

nexus.rag.citations

Source: plugins/rag/citations/plugin.go. Provides rag.citations. Parses citation tags or Anthropic-native source attributions out of LLM responses and emits the structured llm.response.cited event for IO renderers to footnote.

KeyTypeDefaultDescription
modestringautoCitation source: tag (parses <cite source="..." chunk="N"/> markers), anthropic_native (reads LLMResponse.Citations[] populated by Anthropic), or auto (uses native when present, falls back to tag).
strictbooltrueWhen true, citations whose (source, chunk) does not match a chunk recorded in the current turn’s retrieval context are dropped. When false, they are kept and tagged with TrustTier="unverified".
section_priorityint60Priority of the citation-contract section in the system prompt (only used in tag/auto modes).

Subscribes to rag.retrieved (emitted by nexus.tool.knowledge_search and nexus.memory.vector) to build the per-turn validation set, then to llm.response to do the parsing. Emits llm.response.cited.


nexus.rag.ingest

Source: plugins/rag/ingest/plugin.go. Backs the nexus ingest CLI subcommand and the rag.ingest event handler.

KeyTypeDefaultDescription
chunker.sizeint1000Characters per chunk.
chunker.overlapint200Character overlap between chunks.
cache_dirstring~/.nexus/vectors/_cacheEmbedding cache directory (hash → vector). The contextual-prefix cache lands at <cache_dir>/_prefix/.
backfillbooltrueWalk watched directories at startup and ingest pre-existing files.
watchlist(empty)File watch entries; each is {path, glob, namespace}.
watch[].pathstring(required)Directory to watch.
watch[].globstring(empty — match all)Glob pattern for files to ingest.
watch[].namespacestring(required)Vector store namespace.
contextual_retrieval.enabledboolfalsePer-chunk LLM-generated situating prefix (Anthropic contextual retrieval). Adds one LLM call per uncached chunk during ingest; ~49% reported recall improvement. Stored content stays the raw chunk; only the embed/lexical text is prefixed.
contextual_retrieval.model_rolestring(role default)Model role used for prefix generation (resolved via core.models).
contextual_retrieval.max_chars_doc_windowint2000Max characters of surrounding document context handed to the LLM.
contextual_retrieval.max_chars_prefixint400Truncate generated prefix to this many characters before concatenation.
contextual_retrieval.timeout_msint30000Per-call timeout. On timeout the prefix is dropped and the raw chunk is used.

Requires embeddings.provider and vector.store. When search.lexical is also active, ingest dual-writes each chunk into the lexical store with the same (namespace, doc_id) pair the vector store uses.

To migrate an existing chromem-only corpus to dual-mode: add nexus.vectorstore.sqlite_fts to plugins.active and re-run nexus ingest --lexical=true PATH. The embedding cache short-circuits the vector pass while the lexical store is freshly populated.


I/O

nexus.io.tui

Source: plugins/io/tui/plugin.go. No configuration. Bubble Tea terminal UI.

nexus.io.browser

Source: plugins/io/browser/plugin.go.

KeyTypeDefaultDescription
hoststringlocalhostHTTP listen address.
portint8080HTTP listen port.
open_browserbooltrueAuto-open the browser tab on startup (no-op when the OS lacks an opener).

nexus.io.agui

Source: plugins/io/agui/plugin.go. AG-UI (“Agent-User Interaction”) serve transport. Clients POST a RunAgentInput to /agui and receive a text/event-stream SSE response (one stream per run), using the pkg/agui wire format rather than the browser/wails Envelope. Safe by default: binds loopback, optional bearer-token auth, and configurable CORS for browser AG-UI clients.

KeyTypeDefaultDescription
bindstring127.0.0.1:8090host:port the HTTP listener binds to. Defaults to loopback so the endpoint is not network-exposed without explicit opt-in.
bearer_tokenstring(empty)Inline bearer token. When set (and non-empty), Authorization: Bearer <token> is required on every request. Takes precedence over bearer_token_env.
bearer_token_envstring(empty)Name of an environment variable to read the bearer token from. Used only when bearer_token is empty.
cors_originslist(empty)Allowed CORS origins for browser clients. A single * echoes any request Origin; an explicit list echoes only matching origins. Empty means no CORS header (same-origin only), the safe default for a loopback listener. Also accepts a comma-separated string.
emit_stateboolfalseOpt-in AG-UI shared-state emission. When true, the transport mirrors the session’s scene store (nexus.scene) as an AG-UI shared-state document and emits a StateSnapshot at run start plus ordered StateDelta events (RFC 6902 JSON Patch) as scenes mutate. Off by default because it adds scene-event subscriptions and per-mutation diffing overhead most clients do not need. Requires the nexus.scene plugin to be active to produce any state.

Round-trip: a POST /agui maps the request messages to a Nexus io.input (the trailing user message drives the turn; earlier messages ride as PreloadMessages; threadId is recorded as the session id, runId identifies the turn). The plugin subscribes to the same bus events as the browser transport and translates them to canonical AG-UI SSE: agent.turn.startStepStarted, llm.stream.chunkTextMessage*, tool.call/tool.resultToolCall*, thinking.stepReasoning*, agent.turn.endStepFinished/RunFinished. The stream flushes incrementally and terminates at RunFinished (or RunError on failure/disconnect).

Non-canonical events: Nexus bus events with no canonical AG-UI equivalent (workflow.progress, subagent.started/iteration/complete, code.exec.stdout) consistently ride the AG-UI Custom event, with name set to the bus event type and value the JSON-encoded payload. This is a documented superset — conformance clients that only understand canonical events can ignore Custom without losing the run’s canonical lifecycle.

Scope: one in-flight run per listener (single engine/session per listener, mirroring nexus.io.browser). A second POST while a run is active receives a terminal RunStarted+RunError stream rather than interleaving.

Shared state (emit_state: true): the transport tracks the scene store’s scene.created / scene.patched / scene.deleted bus events (each carrying the scene’s full post-mutation content) into a shared-state document keyed by scene_id. A StateSnapshot of the current document is emitted immediately after RunStarted; each subsequent scene mutation during the run emits a StateDelta whose delta is an RFC 6902 JSON Patch from the prior document to the new one, so a client applying the deltas in order reconstructs the snapshot. The document is session-scoped and persists across runs on the listener (a later run’s snapshot reflects scenes created earlier). Inbound state (RunAgentInput.state, same scene-keyed shape) is applied at run start — and on a resume/continuation run — before the initial StateSnapshot, seeding the scene store via a scene_create tool.invoke per scene so the agent observes it through scene_get / scene_list. Conflict semantics are client-state-seeds-then-agent-wins: the client seed lands before the agent’s first turn, then agent-side scene_patch mutations are last-writer and flow back out as StateDelta. See Shared state.

nexus.io.realtime

Source: plugins/io/realtime/plugin.go. WebSocket bidirectional transport for low-latency clients (browser front-ends, native voice clients) that want raw stream.delta deltas, tool previews, voice audio chunks, and cancel envelopes without going through the nexus.io.browser UI hub.

KeyTypeDefaultDescription
listen_addrstring:7676TCP address the WebSocket server binds to.
pathstring/wsURL path the WebSocket handler is mounted at.
max_clientsint16Concurrent connection cap. New dials past the cap receive HTTP 503.

Outbound envelopes (server → client, JSON): stream.delta, stream.end, tool.preview, audio.chunk, cancel.complete, hitl.request.

Inbound envelopes (client → server, JSON): input, audio.chunk, cancel, approval.

No auth in v1. Origin checks and bearer-token validation are tracked follow-ups; operators running this on a public network must front it with a reverse proxy that does its own authentication.

nexus.io.broker

Source: plugins/io/broker/plugin.go. Dial-back IO transport for Nexus instances spawned by the session broker (cmd/nexus-broker). Unlike nexus.io.browser / nexus.io.realtime, which LISTEN, this plugin DIALS OUT to the broker’s instance gateway over WebSocket — the broker is the only listening socket. On Ready it dials broker_addr, sends a register frame keyed by lease_id, announces readiness, and reports the engine session id (for later -recall resume) before bridging IO frames in both directions.

Config keys fall back to environment variables the broker injects at spawn, so operators normally set neither by hand:

KeyTypeDefaultDescription
broker_addrstring$NEXUS_BROKER_ADDRWebSocket URL of the broker’s instance dial-back endpoint (e.g. ws://127.0.0.1:8080/instance). Falls back to the NEXUS_BROKER_ADDR env var. When empty the plugin stays dormant (no dial).
lease_idstring$NEXUS_BROKER_LEASE_IDLease id assigned by the broker at spawn; echoed in the register frame. Falls back to the NEXUS_BROKER_LEASE_ID env var. When empty the plugin stays dormant.

Outbound IO messages (instance → broker → client, JSON inside the frame payload): output, stream.delta, stream.end, status, approval.request, hitl.request, cancel.complete.

Inbound IO messages (client → broker → instance): input, approval.response, hitl.response, cancel.

The connection reconnects with exponential backoff until shutdown. On an inbound shutdown frame (sent by the broker for POST /release and later idle/crash teardown) the plugin emits io.session.end, which drives a clean engine Stop that flushes and persists the session before the process exits; the reconnect loop is latched off so the graceful teardown is not undone. There is no auth in the plugin itself — the broker gateway owns lease validation and any transport-level authentication.

nexus.io.voice

Source: plugins/io/voice/plugin.go. Bus-driven voice IO bridge: consumes voice.audio.input.chunk events (typically from nexus.io.realtime), runs simple energy-based VAD plus ASR via the OpenAI Whisper API, and emits io.input. Consumes llm.response, runs TTS via the OpenAI /audio/speech endpoint, and emits voice.audio.output.chunk frames back. Implements barge-in: a speech-energy input chunk arriving while a TTS turn is in flight emits cancel.request{Source: "voice"}.

Local-model providers (local_whisper, faster_whisper, distil_whisper for ASR; kokoro, local_*, *_local for TTS) are recognized by the schema but rejected at Init with a clear error pointing at issue #92, where the local-model bootstrapping work is tracked separately.

KeyTypeDefaultDescription
asr.providerstringopenai_whisperOnly openai_whisper is wired in this PR. Local-model values rejected pending #92.
asr.api_key_envstringOPENAI_API_KEYEnv var that holds the API key.
asr.api_keystring(none)Inline API key. Overrides api_key_env.
asr.modelstringwhisper-1Whisper model id.
asr.endpointstringOpenAI defaultOverride URL for the transcription endpoint (test injection).
tts.providerstringopenaiOnly openai is wired in this PR. Local-model values rejected pending #92.
tts.api_key_envstringOPENAI_API_KEYEnv var that holds the API key.
tts.api_keystring(none)Inline API key. Overrides api_key_env.
tts.modelstringtts-1TTS model id.
tts.voicestringalloyVoice preset id.
tts.streamingbooltrueAlways true in v1; reserved for future non-streaming mode.
tts.endpointstringOpenAI defaultOverride URL for the speech endpoint (test injection).
tts.chunk_bytesint8192Frame size in bytes for emitted output chunks.
vad.thresholdnumber0.02RMS energy threshold (normalized 0..1) above which the buffer is considered speech.
vad.silence_msinteger600Milliseconds of below-threshold audio that triggers an utterance flush.
barge_in.enabledbooltrueCancel an in-flight TTS turn when new speech is detected.
barge_in.thresholdnumbervad.thresholdRMS threshold above which an incoming chunk is treated as barge-in.
text_fallbackbooltrueAllow io.input from non-voice transports to flow through unchanged.

VAD energy is computed as RMS over little-endian PCM int16 samples for audio/wav / audio/pcm / audio/l16. For compressed containers (webm/opus, mpeg/mp3) the bytes are not PCM and we fall back to a byte-level energy heuristic until a proper decode is added — flagged with a TODO(#91) in plugins/io/voice/vad.go.

nexus.io.test

Source: plugins/io/test/plugin.go. Non-interactive testing transport.

KeyTypeDefaultDescription
inputslist(empty)Scripted user inputs (fed sequentially).
input_delayduration500msDelay between inputs.
approval_modestringapproveapprove, deny, per-prompt.
approval_ruleslist(empty)Per-prompt rules: each `{match: , action: <approve
hitl_responseslist(empty)Scripted answers to hitl.requested events. Bare strings are treated as free_text; {choice_id: ..., free_text: ...} maps populate the corresponding response fields.
mock_responseslist(empty)Synthetic LLM responses. Each {content, tool_calls: [{name, arguments}]}. When set, the plugin vetoes real llm.request events.
timeoutduration60sSession timeout.
read_stdinbooltrueRead stdin when no other input source is available.

nexus.io.wails

Source: plugins/io/wails/plugin.go. Wails-native transport. The runtime is installed by the embedder via Hub().SetRuntime() before engine.Boot; this plugin only configures event bridging.

KeyTypeDefaultDescription
subscribelist(empty)Event types to bridge bus → frontend. Empty triggers legacy hardcoded chat-event subscriptions for parity with nexus.io.browser.
acceptlist(empty)Event types accepted from the frontend → bus.

nexus.io.oneshot

Source: plugins/io/oneshot/plugin.go. Scripting/batch mode with JSON transcript output.

KeyTypeDefaultDescription
inputstring(none)Inline prompt (lowest precedence).
input_filestring(none)Path to a prompt file.
output_filestring(none)Path to write the JSON transcript.
prettybooltruePretty-print JSON output.
read_stdinbooltrueRead stdin when available.

Prompt resolution precedence: NEXUS_ONESHOT_PROMPT env > input > input_file

stdin.

Native Realtime API integration — deferred

OpenAI Realtime and Gemini Multimodal Live are entire new wire protocols separate from the standard chat/generate endpoints. They are not part of the multimodal-foundation PR (#93) and are tracked as a follow-up under issue #91. Until they land, voice-mode use the ASR → LLM → TTS pipeline implemented in plugins/io/voice/. See Native Realtime API integration — deferred for the full rationale and follow-up scope.


Observers

nexus.observe.thinking

Source: plugins/observe/thinking/plugin.go. No configuration. Marker plugin: presence in plugins.active lets terminal and browser shells enable thinking-related UI. The events themselves are journaled automatically and can be read live via journal.Writer.SubscribeProjection or post-mortem via journal.ProjectFile.

nexus.observe.otel

Source: plugins/observe/otel/plugin.go. OTLP exporter (one root span per session, one span per event).

KeyTypeDefaultDescription
endpointstring(none)OTLP endpoint, e.g. http://localhost:4317.
protocolstringgrpcgrpc or http/protobuf.
service_namestringnexusOpenTelemetry service name.
exclude_eventslist(empty)Event types to skip; supports prefix wildcards (llm.stream.*).

nexus.observe.sampler

Source: plugins/observe/sampler/plugin.go. Off by default. Captures a fraction of live session journals (and every failed session when failure_capture is on) into a local directory so the eval pipeline can score them later. The plugin must be both registered (it is — automatically via pkg/engine/allplugins) and listed in plugins.active and configured with enabled: true for any capture to happen. Omitting the config block, or setting enabled: false, makes the plugin a no-op: Subscriptions() returns empty, no bus traffic, no disk writes.

plugins:
  active:
    - nexus.observe.sampler

  nexus.observe.sampler:
    enabled: false
    rate: 0.0
    failure_capture: true
    out_dir: ~/.nexus/eval/samples
KeyTypeDefaultDescription
enabledboolfalseMaster switch. When false, the plugin draws no bus traffic and writes no files even if it appears in plugins.active.
ratefloat0.0Fraction of normal sessions captured at io.session.end, in [0, 1]. 0.0 disables rate sampling; 1.0 captures every session. Validated at Init; out-of-range values fail boot when enabled: true.
failure_capturebooltrueWhen true, sessions whose metadata/session.json status is anything other than active or completed are captured regardless of rate. Use false to disable failure capture entirely.
out_dirstring~/.nexus/eval/samplesDirectory where samples land. Path expansion via engine.ExpandPath. Each sample is written to <out_dir>/<session-id>/journal/ plus a <out_dir>/<session-id>/metadata.json sibling.

The plugin emits an eval.candidate event per capture (payload defined in plugins/observe/sampler/events.go) so downstream tooling — for example, nexus eval list-candidates once it lands — can enumerate fresh samples.

The pluggable Redactor interface (plugins/observe/sampler/redact.go) is the hook for future PII scrubbing. v1 ships only the IdentityRedactor (byte-pass-through). Tests inject custom redactors via the package-private Plugin.SetRedactor API; production runs leave it on the default.

Caveat: rotated journal segments. When a non-identity redactor is configured, the active events.jsonl segment is rewritten line-by-line through it. Compressed *.jsonl.zst rotated segments are byte-copied as-is in v1 — handling them transparently requires zstd round-trips that are deferred to a follow-up.


Planners

nexus.planner.dynamic

Source: plugins/planners/dynamic/plugin.go.

KeyTypeDefaultDescription
approvalstringautoalways (block until user approves), never (auto-execute), auto (LLM decides).
plan_promptstring(default)Inline planning prompt.
plan_prompt_filestring(none)Path to a planning prompt file.
model_rolestring(default)Role used for plan generation.
modelstring(none)Explicit model ID (backward-compat; prefer model_role).
max_stepsint10Hard cap; excess steps from the LLM are truncated.

nexus.planner.static

Source: plugins/planners/static/plugin.go. Approval auto-defaults to never (static plans don’t call an LLM).

KeyTypeDefaultDescription
approvalstringneveralways or never.
summarystringStatic execution planFree-form plan summary.
stepslist(required)Step list.
steps[].descriptionstring(required)Step description.
steps[].instructionsstring(none)Step-specific instructions.

Workflows

Generic workflow surface

Workflow plugins (currently nexus.workflows.icm; planned to extend to other multi-stage runners) emit a workflow-agnostic event class so IO plugins can render a dedicated progress surface (a sticky panel in the TUI right rail; a status indicator chip in the browser) without subscribing to plugin-specific event taxonomies.

Event: workflow.progress — payload events.WorkflowProgress (pkg/events/workflow.go).

FieldTypeDescription
workflow_idstringProducer plugin instance ID (nexus.workflows.icm, nexus.workflows.icm/script, …).
workflow_namestringHuman-readable workflow label (workspace name for ICM).
run_idstringIdentifier for this particular run.
stage / stage_labelstringMachine ID + display label for the current stage. Empty at run start / end.
stage_index / stage_totalint1-based position in the stage sequence.
iteration / max_iterationsintLoop iteration counters. 0 when the stage is not looping.
turn / max_turnsintInner-turn counters. 0 when not tracked.
items_done / items_totalintFan-out progress. 0 when not a fan-out stage.
current_itemstringMost recently completed item ID for fan-out.
statusstringOne of started, running, iterating, item_done, completed, failed, halted.
detailstringShort free-form one-liner suitable for display.
failureslist of stringsNames of predicates whose failure prevented this iteration / turn from converging.

ICM emits workflow.progress alongside its detailed icm.* events: the icm.* family feeds scrollback audit rows; workflow.progress feeds the dedicated status surface. Future workflow plugins can emit only the generic event and inherit the same UI treatment without per-plugin subscriptions.

The TUI (nexus.io.tui) and browser (nexus.io.browser) subscribe to workflow.progress automatically when active.

nexus.workflows.icm

Source: plugins/workflows/icm/plugin.go. File-driven multi-stage workflow runner. A workspace is a folder containing operator.md, workspace.md, and a stages/ tree of contracts; each stage runs as a sub-agent dispatched via the posture registry. Multi-instance: pin distinct workspaces per instance via the nexus.workflows.icm/<suffix> form (e.g. nexus.workflows.icm/script). See docs/src/plugins/workflows-icm.md for the full plugin guide.

Requires the posture.registry capability (provided by nexus.agent.postures). Strongly recommended companions: nexus.control.hitl (human gates + judge approvals), nexus.skills (workspace skills authoring tooling).

KeyTypeDefaultDescription
workspacestring(required)Path to the ICM workspace folder. Expanded via ~. Loaded + validated at boot; load errors fail boot.
default_judge_posturestring(empty)Registered posture name used for type: llm predicates that do not name an explicit model: posture. Required when any predicate uses type: llm.
default_workflow_posturestring(empty)Optional base posture name. Stages without an agent.posture: inherit Model / AllowedTools / Budget / MaxRecursionDepth from this posture before applying stage-level overrides.
cache_sizeint0Per-run delegate cache capacity. 0 disables caching (recommended — ICM stages typically have tool side effects + predicate retries that make cross-run caching hostile).
inline_artifact_limit_bytesint32768Maximum size for inlining an artifact body into the XML payload. Above this threshold ICM emits <artifact_ref/> and the LLM uses read_file.
loop_max_restartsint3Per-stage cap on loop.on_exhausted: human_gate restart choices. 0 = unlimited. Prevents infinite restart cycles when a workspace cannot converge.
input_filenamestringinput.txtFilename written into <runID>/00_input/ when io.input carries direct content (not a file path).
treat_input_as_path_if_existsbooltrueWhen true, io.input.Content is interpreted as a file path if os.Stat succeeds and the file is copied into 00_input/; otherwise the content is written verbatim.
workspace_inputs_dirstring(empty)Optional directory whose regular files are copied into <runID>/00_input/ at run start, before io.input content is processed. Useful for static fixtures.
auto_include_skill_reference_toolbooltrueWhen true, ICM automatically appends the read_skill_reference[_<suffix>] tool to each derived stage posture whose contract declares inputs.skills. Set false to require explicit listing in agent.tools.
predicate_command_timeout_secondsint30Default timeout for type: command predicates when neither the predicate nor the stage budget specifies one.
emit_progress_thinking_stepsbooltrueWhen true, ICM emits thinking.step events with Phase="icm.<stage_id>" so UIs that render thinking surfaces show inline stage transitions.

Events

Subscribes:

  • io.input — entry point. Each input begins a new workflow run.
  • hitl.responded — resumes a run paused at a human gate or type: human predicate.

Emits (workflow lifecycle):

  • icm.run.started / icm.run.completed / icm.run.halted — overall run boundaries.
  • icm.stage.started / icm.stage.completed / icm.stage.failed — per-stage transitions.
  • icm.stage.iteration — fires once per loop iteration with the prior iteration’s exit_failures.
  • icm.turn — fires once per inner turn with the turn’s validator failures.
  • icm.fanout.item — per-item lifecycle in a fan-out stage (active, completed, failed).
  • icm.predicate.failed — fires for every predicate evaluation whose verdict is fail.
  • plan.created / plan.progress — generic plan surface mirrored for any UI that already renders ReAct plans.
  • workflow.progress — engine-generic structured progress (see Generic workflow surface below).
  • hitl.requested — human gates and type: human predicates dispatch through HITL.

nexus.skills

Source: plugins/skills/plugin.go. Registers the activate_skill LLM tool.

KeyTypeDefaultDescription
scan_pathslist(empty)Directories scanned for SKILL.md files. No implicit defaults — discovery is gated entirely by this list.
trust_projectstringaskTrust level for project skills: ask, always, never.
max_active_skillsint10Hard cap on concurrently active skills.
catalog_in_system_promptbooltrueInject the skill catalog into the system prompt at priority 50.
disabled_skillslist(empty)Skill names to disable even if discovered.

System

nexus.system.dynvars

Source: plugins/system/dynvars/plugin.go. Registers a system-prompt section at priority 100 that lists runtime variables. Each flag defaults to false — opt-in only.

KeyTypeDefaultDescription
dateboolfalseInclude Current date: YYYY-MM-DD.
timeboolfalseInclude Current time: HH:MM:SS.
timezoneboolfalseInclude the local timezone abbreviation.
cwdboolfalseInclude the engine working directory.
session_dirboolfalseInclude the session workspace root.
osboolfalseInclude os/arch.

Control

nexus.control.cancel

Source: plugins/control/cancel/plugin.go. No configuration. Provides the control.cancel capability used by ReAct and other agents to interrupt in-flight work; also handles the /resume slash command via io.input at priority 5 (ahead of memory plugins).


Routers

Plugins that subscribe before:llm.request and rewrite request.Model based on the request’s metadata, tags, or an LLM-classifier judgment. Routers run at priority 50 (metadata) / 45 (classifier) — above gates, below the engine’s tag seeder. Both stand down when the request already carries _target_provider (a fallback retry) or _routed_by (an upstream rule already chose a model).

nexus.router.metadata

Source: plugins/router/metadata/plugin.go. Declarative rules over the request’s Metadata (_source, task_kind, iteration) and Tags (tenant, project, source_plugin, …). First matching rule wins; the terminal default_model / default_role fires when no rule matches.

KeyTypeDefaultDescription
ruleslist(empty)Ordered rule list. See below.
default_modelstring(none)Fallback model id when no rule matches.
default_rolestring(none)Fallback role when no rule matches.

Each entry under rules:

KeyTypeDefaultDescription
namestringrule#NOptional label recorded on req.Metadata["_routed_rule"].
matchmap(required)Match conditions. Keys: metadata.<key>, tags.<key>, role, model. Values: bare string (equality), or `{lt
usestring(one of)Concrete model id to assign.
rolestring(one of)Role name to assign (resolved against core.models).

nexus.router.classifier

Source: plugins/router/classifier/plugin.go. Small LLM judges the difficulty of the user’s most recent prompt and picks one of candidate_roles. The decision is cached by prompt-prefix hash (LRU). Cache hits rewrite LLMRequest.Role synchronously; misses route to fallback_role immediately and warm the cache asynchronously via a probe llm.request tagged _source: nexus.router.classifier.

KeyTypeDefaultDescription
classifier_rolestring(required)Model role (resolved via core.models) used for the classification probe.
candidate_roleslist(required)Cheapest-first list of model roles the classifier picks among.
fallback_rolestring(none)Model role used on cache miss while the cache warms.
promptstring(default)Classifier prompt template (%s for the candidate-role list and prompt).
prefix_charsint256Number of leading prompt characters folded into the cache key.
cache_classificationbooltrueWhether to cache decisions at all.
cache_max_entriesint1024LRU capacity.
latency_budget_msint800Drop the warm if the probe doesn’t return within this window.

Discovery

nexus.discovery.progressive

Source: plugins/discovery/progressive/plugin.go. Hierarchical tool discovery — the LLM sees class-level summaries and drills into specific classes via a discover meta-tool. Intercepts before:llm.request (priority 8) and tool.invoke (priority 40).

KeyTypeDefaultDescription
scopestringsessionsession, turn, or hybrid.
idle_prune_turnsint5Turns of inactivity before a class is pruned (scope: hybrid only).
classless_behaviorstringincludeinclude (always reveal classless tools) or exclude.
always_includelist(empty)Class names that are always fully revealed.
default_depthstringclassclass (summaries only) or full (all tools).

LLM batch

nexus.llm.batch

Source: plugins/llm/batch/plugin.go. Cross-provider batch coordinator (Anthropic Messages Batches, OpenAI Batch API). Subscribes llm.batch.submit; emits llm.batch.status and llm.batch.results.

KeyTypeDefaultDescription
poll_intervalduration5mHow often to poll provider batch status.
data_dirstring~/.nexus/batchesDirectory for persisted batch state (resumed across restarts).
default_max_tokensint1024Default max_tokens applied when a batched request didn’t pin one.
providers.anthropic.api_keystring(env)Anthropic API key.
providers.anthropic.api_key_envstringANTHROPIC_API_KEYEnv var to read the Anthropic key from.
providers.openai.api_keystring(env)OpenAI API key.
providers.openai.api_key_envstringOPENAI_API_KEYEnv var to read the OpenAI key from.
anthropic_api_key_envstring(none)Backward-compat: flat top-level Anthropic key env var.
openai_api_key_envstring(none)Backward-compat: flat top-level OpenAI key env var.

v1 limitations (intentional): direct-API auth only (no Bedrock/Vertex/Azure); text-only requests (no multimodal/thinking/caching/citations); single-provider per submit; no cancellation API.


MCP integration

nexus.mcp.client

Source: plugins/mcp/client/. Bridges one or more external Model Context Protocol (MCP) servers into Nexus. Tools land in the catalog under mcp__<server>__<tool>, static resources auto-register as no-arg tools, resource templates become parameterised tools, and prompts surface as slash commands. See docs/src/plugins/mcp-client.md for the user-facing guide.

Top-level keys:

KeyTypeDefaultDescription
serverslist(none)One entry per MCP server. See per-server keys below.
defaultsmap(none)Inherited by every entry in servers unless overridden inline.
aliasesmap(none)Optional alias map: short slash command → <server>.<prompt>. Aliases use the configured command_prefix chain; e.g. review: gh.review_pr makes /review rewrite to /mcp.gh.review_pr.

defaults

KeyTypeDefaultDescription
lifecyclestringengineWhen servers connect/disconnect. engine = connect on engine boot, disconnect on shutdown. session = connect on io.session.start, disconnect on io.session.end.
timeoutduration30sPer-RPC timeout used for tools/call, resources/read, prompts/get, etc.
command_prefixstringmcpFirst segment of the slash command Nexus registers per prompt. With the default a server named fake and a prompt named greet becomes /mcp.fake.greet.
resources.enabledbooltrueToggle the entire resource surface for the server.
resources.auto_register_staticbooltrueWhen true, every static resource becomes a no-arg catalog tool.
resources.auto_register_templatebooltrueWhen true, every resource template becomes a catalog tool whose inputSchema mirrors the template’s variables.
resources.auto_register_maxint50If a server returns more static resources than this, the static auto-registration is skipped and only the generic list_resources/read_resource tools are exposed.
resources.subscribe_updatesbooltrueSubscribe to resources/updated for each auto-registered static. Notifications produce mcp.resource.updated events.
prompts.enabledbooltrueToggle the prompt slash-command surface for the server.

servers[]

KeyTypeDefaultDescription
namestring(required)Lowercase alpha-numeric identifier used to namespace every catalog entry and slash command ([a-z0-9][a-z0-9_-]*).
transportstringstdiostdio (subprocess via the SDK) or http (streamable HTTP).
commandstring(required for stdio)Executable to launch. Resolved on PATH; users wanting ~ expansion can write the full path.
argslist(none)Argument list passed to command.
envmap(none)Environment variables exported to the subprocess. ${VAR} references are expanded from the host environment.
env_passthroughlist(none)Names of host environment variables forwarded verbatim (skipped silently when not set on the host).
urlstring(required for http)Base URL of the streamable HTTP MCP endpoint.
headersmap(none)HTTP headers attached to every request. ${VAR} references expand from the host environment.
lifecyclestringinherited from defaultsengine or session.
timeoutdurationinherited from defaultsOverrides defaults per server.
tools.allowlist(none) (all allowed)If set, only listed raw MCP tool names are forwarded to the catalog.
tools.denylist(none)Raw MCP tool names to drop unconditionally. Deny takes precedence over allow.
resources.*mapinherited from defaultsSame keys as defaults.resources.
prompts.enabledboolinherited from defaultsDisable per server when desired.

Events

Subscribes:

  • tool.invoke — dispatches MCP tool calls for any registered mcp__<server>__* name.
  • before:io.input — intercepts slash commands; vetoes the original input, then re-emits a fresh io.input whose PreloadMessages carry the expanded prompt.
  • io.session.start / io.session.end — drive lifecycle: session connections.
  • mcp.prompts.list — synchronous query that fills events.MCPPromptsList.Prompts so IO plugins can render /help-style listings.

Emits:

  • tool.register, tool.result, before:tool.result — the catalog projection.
  • io.input — replacement input carrying PreloadMessages after a prompt expansion.
  • io.output — system-role error messages when a slash command fails to parse or dispatch.
  • mcp.resource.updated — fired when a subscribed static resource changes.
  • mcp.tools.refreshed, mcp.prompts.refreshed — bookkeeping events emitted after each per-server reconcile.

Deferred for phase 2 (see issue #98):

  • MCP sampling (server-initiated LLM calls).
  • OAuth dynamic client registration for the HTTP transport.
  • SSE legacy transport.
  • Roots beyond the session files directory.

Apps

nexus.app.helloworld

Source: plugins/apps/helloworld/plugin.go. Built-in placeholder agent / proof-of-concept for the bus-bridge pattern.

KeyTypeDefaultDescription
greetingstringHelloGreeting prefix used when responding to hello.request events.

Gates

Gates are vetoable handlers that subscribe to before:* events and may block or transform them. See .claude/docs/gates.md for the underlying veto mechanics.

Pipeline ordering on before:* events

Handlers on a before:* event run in ascending Priority (lower runs first); dispatch breaks at the first veto. Handlers that share a priority fall back to subscription order — the first Subscribe call runs first, and the bus uses a stable sort so this tiebreak is deterministic across rebuilds and reorders of plugins.active. The engine logs one WARN at boot for every (before:*, priority) tuple shared by two or more handlers — re-space the priorities or accept the registration-order tiebreak knowingly.

The shipped gates encode an explicit policy in their priorities so safety outcomes don’t depend on activation order. The values below are the authoritative pipeline; treat them as a contract when adding a new gate.

before:io.output — mutate-then-veto pipeline:

PriorityGateRole
8nexus.gate.content_safetyRedact (mutate) first, or veto if action=block
9nexus.gate.json_schemaValidate / retry on post-redaction content; may mutate
10nexus.gate.stop_wordsFinal ban check on the content that will ship
12nexus.gate.output_lengthTruncate-retry mutation last

before:llm.request — cheap-structural → mutators → input-scanners → HITL:

PriorityGateRole
6nexus.gate.endless_loopIteration counter; structural exit
7nexus.gate.token_budgetBudget reservation; structural
8nexus.tool.discovery.progressiveMutates tool list (drill-down)
9nexus.gate.rate_limiterPause until quota available
10nexus.gate.tool_filterMutates tool list (allow/block)
11nexus.gate.prompt_injectionPattern-scan input
12nexus.gate.stop_wordsPattern-scan input
13nexus.gate.approval_policyMay trigger HITL — most expensive
15nexus.gate.context_windowCompaction trigger

nexus.gate.endless_loop

Source: plugins/gates/endless_loop/plugin.go.

KeyTypeDefaultDescription
max_iterationsint25Maximum LLM calls per turn (gate-/planner-sourced calls excluded).
warning_atint0Emit a warning when this count is reached (0 disables).

nexus.gate.stop_words

Source: plugins/gates/stop_words/plugin.go. Gates both before:llm.request (user messages) and before:io.output.

KeyTypeDefaultDescription
wordslist(empty)Inline banned words.
word_fileslist(empty)Files of newline-separated words.
case_sensitiveboolfalseCase-sensitive matching.
messagestringContent blocked: contains prohibited terms.Veto message.

nexus.gate.token_budget

Source: plugins/gates/token_budget/plugin.go. Multi-dimensional ceilings (session / tenant / source_plugin) with block, warn, or downgrade-model actions. The legacy single-ceiling shape (max_tokens) still works as a session total-token ceiling.

KeyTypeDefaultDescription
max_tokensint(unset)Backward-compat session total-token ceiling.
messagestringToken budget exhausted for this session.Default veto message for the legacy ceiling.
on_exceedstringblockDefault action when a ceiling fires (block | warn | downgrade-model). Each ceiling can override.
downgrade_candidateslist(empty)Model IDs the downgrade-model action picks the cheapest entry from (priced via pkg/engine/pricing).
pricingmap(merged provider defaults)Per-model overrides applied to the unified pricing table; same shape as the per-provider pricing block.
estimate_factorfloat1.5Multiplier on the prompt-length token estimate the gate deducts upfront at before:llm.request (reserve/commit). Tightens the TOCTOU window under concurrent fan-out by booking estimated headroom before any in-flight request returns; the response handler then subtracts the reservation and adds the actual usage so the net effect is exactly the realized spend. Increase to err on the side of overshoot-prevention; decrease to tolerate more in-flight headroom.
ceilingslist(empty)List of ceiling rules. See below.

Each entry under ceilings:

KeyTypeDefaultDescription
dimensionstringsessionOne of session, tenant, source_plugin.
matchstring(none)For tenant/source_plugin: only this bucket.
windowstringsessionsession (lifetime of the session) or day (rolling UTC midnight).
on_exceedstringtop-level defaultPer-rule override for the gate’s on_exceed.
max_input_tokensint(unset)Veto/downgrade once cumulative input tokens reach this value.
max_output_tokensint(unset)Same for completion tokens.
max_total_tokensint(unset)Same for total tokens.
max_usdfloat(unset)Same for USD spend.
max_usd_per_sessionfloat(unset)Convenience alias for max_usd with window: session.
max_usd_per_dayfloat(unset)Convenience alias for max_usd with window: day.
messagestring(reason)Override message emitted on block/warn.

Tenant ceilings persist via app-scope SQLite (~/.nexus/plugins/nexus.gate.token_budget/store.db). Other dimensions are in-memory per session.

nexus.gate.rate_limiter

Source: plugins/gates/rate_limiter/plugin.go. Vetoes before:llm.request when the per-window budget is exhausted; the agent’s gate.llm.retry subscriber re-issues the request after the limiter signals the budget has freed up. The pre-Phase-3 time.Sleep behavior was removed in alpha — there is no compat shim.

KeyTypeDefaultDescription
modestringrejectreject (veto, schedule a single one-shot retry once the window ages out) or queue (buffer up to queue.max_pending retry slots; a drainer goroutine emits gate.llm.retry at the configured rate; excess is rejected outright).
requests_per_minuteint60Requests allowed per window_seconds.
window_secondsint60Sliding window length.
pause_messagestringRate limit reached. Pausing for {seconds}s...Output template; {seconds} is interpolated.
queue.max_pendingint100Maximum buffered retry slots in mode: queue. Ignored in reject mode.

nexus.gate.tool_timeout

Source: plugins/gates/tool_timeout/plugin.go. Per-call deadline gate. On tool.invoke it starts a timer; on expiry it emits a tool.timeout observability event plus a synthetic tool.result carrying an error message that names the exact override key. A before:tool.result veto suppresses any late real result for the same call ID so the agent’s pendingToolCalls counter stays consistent. Note: Go cancellation is cooperative — the original tool goroutine may keep running until it honors its own context. The gate’s job is to unblock the agent, not preempt the tool.

Per-tool override keys may be exact tool names (web_fetch) or path.Match-style globs (mcp.*). Resolution: an exact key wins; among glob matches the longest pattern wins; otherwise default_timeout applies.

The synthetic error message format is fixed and intended to be read by operators:

tool <name> exceeded timeout <duration>; raise via gates.tool_timeout.per_tool.<name>: <duration>
KeyTypeDefaultDescription
default_timeoutduration string30sApplied when no per_tool key matches.
per_toolmap[string]duration{}Per-tool overrides keyed by exact tool name or path.Match glob.

nexus.gate.prompt_injection

Source: plugins/gates/prompt_injection/plugin.go. Regex-only — no LLM.

KeyTypeDefaultDescription
actionstringblockblock or warn.
patternslist(default set)Inline regex patterns added to defaults.
patterns_filestring(none)File of newline-separated regexes.
messagestringInput blocked: potential prompt injection detected.Block message.

nexus.gate.json_schema

Source: plugins/gates/json_schema/plugin.go. Validates before:io.output against a JSON Schema; on failure, asks the LLM to retry.

KeyTypeDefaultDescription
schemastring | object(required)JSON Schema as inline object or string.
schema_filestring(none)Path to a schema file (takes precedence over schema).
max_retriesint3Retry attempts.
retry_promptstring(default)Retry instruction; supports {schema} and {error} templates.

nexus.gate.output_length

Source: plugins/gates/output_length/plugin.go. Asks the LLM to retry with a shorter response; allows through after exhausted retries (with a warning).

KeyTypeDefaultDescription
max_charsint5000Maximum response length.
max_retriesint2Retry attempts.
retry_promptstring(default)Retry prompt; supports {length} and {limit} templates.

nexus.gate.content_safety

Source: plugins/gates/content_safety/plugin.go. Built-in checks all default to enabled.

KeyTypeDefaultDescription
actionstringblockblock or redact.
messagestringContent blocked: contains sensitive information ({checks}).Block/redact message; {checks} lists triggered checks.
scan_tool_resultsboolfalseAlso subscribe to before:tool.result and apply checks to tool output. Required to cover sub-agent / delegate output (which reaches the parent via tool.result, not io.output). Off by default because legitimate external tools (web_fetch, knowledge_search) often surface phone numbers / addresses that aren’t leaks; enable for orchestrator-style topologies.
check_pii_emailbooltrueDetect email addresses.
check_pii_phonebooltrueDetect phone numbers.
check_pii_ssnbooltrueDetect US SSNs.
check_secrets_api_keybooltrueDetect API-key-like strings.
check_secrets_private_keybooltrueDetect private-key blocks.
check_secrets_passwordbooltrueDetect password-shaped fields.
check_credit_cardbooltrueDetect credit-card numbers.
check_ip_internalbooltrueDetect RFC1918 / internal IPs.
custom_patternslist(empty)Each {name, pattern}.

nexus.gate.context_window

Source: plugins/gates/context_window/plugin.go. Triggers compaction via memory.compact.request when the estimated context approaches the limit.

KeyTypeDefaultDescription
max_context_tokensint100000Provider context window limit.
trigger_ratiofloat0.85Trigger compaction at this fraction (0.0–1.0).
chars_per_tokenfloat4.0Token estimation ratio.

nexus.gate.tool_filter

Source: plugins/gates/tool_filter/plugin.go. Modifies request.ToolFilter on before:llm.request. include takes precedence over exclude.

KeyTypeDefaultDescription
includelist(empty)Allowlist of tool names.
excludelist(empty)Blocklist of tool names.

nexus.gate.approval_policy

Source: plugins/gates/approval_policy/plugin.go. Policy-driven approvals on before:tool.invoke and before:llm.request. The gate evaluates a config-supplied list of rules, and on first match emits a hitl.requested event and blocks waiting on hitl.responded. The operator’s choice resolves to passthrough (allow), veto (reject), or passthrough-with-edits.

KeyTypeDefaultDescription
ruleslist(empty)Ordered list of approval rules. First match wins.

Each rule is a map with the following keys:

KeyTypeDefaultDescription
matchmap(empty)Field/value tests against the action payload. String values are glob (*, ?); dotted keys address nested fields (e.g. args.command).
modestringchoicesOne of free_text, choices, both.
choiceslist(see)List of {id, label, kind} (or bare-string id). When omitted in choices mode, defaults to [{id: allow, kind: allow}, {id: reject, kind: reject}].
default_choicestring(empty)Choice id auto-selected when the timeout elapses. Without a default, a timeout vetoes the action.
promptstring(auto)Go text/template string rendered against the action payload. Falls back to Approve <kind>: <target> when unset (or empty when prompt_synthesizer is set so the synthesizer can fill it in).
prompt_synthesizerstring(none)Capability ID of a registered prompt synthesizer (e.g. hitl.prompt_synthesizer). When set, the gate emits the request with HITLRequest.PromptSynthesizer populated and an empty Prompt, letting the synthesizer render an LLM-authored approval question via the canonical before:hitl.requested entry point.
timeoutstring(none)Go duration (e.g. 5m). When unset, the gate blocks indefinitely.

Match keys recognized by the runtime payload:

  • action_kindtool.invoke or llm.request.
  • tool — the tool name (only meaningful for tool.invoke).
  • args.<dotted> — any nested key inside the tool’s argument map.
  • model — the LLM model id (only meaningful for llm.request).
  • role — the LLM model role (only meaningful for llm.request).

Example:

nexus.gate.approval_policy:
  rules:
    - match: { action_kind: tool.invoke, tool: shell, args.command: "rm*" }
      mode: choices
      choices: [allow, reject]
      timeout: 5m
      default_choice: reject
    - match: { action_kind: llm.request, model: "claude-opus-*" }
      mode: choices
      prompt: "About to call expensive model {{ .model }}. Approve?"

Eval harness

The eval: block configures the offline eval harness invoked via the nexus eval subcommand. The engine itself ignores this block — only cmd/nexus/eval.go reads it. Per-flag overrides on the CLI take precedence over config values, which take precedence over built-in defaults.

eval:
  cases_dir: tests/eval/cases
  reports_dir: tests/eval/reports
  judge:
    model: claude-haiku-4-5
    temperature: 0
    n_samples: 1
    cache: true
  baseline:
    fail_on_score_drop: 0.05
    fail_on_latency_p95_drop: 0.20
KeyTypeDefaultDescription
cases_dirstringtests/eval/casesDirectory containing case bundles (<id>/case.yaml, input/, journal/, assertions.yaml). Path expansion via engine.ExpandPath.
reports_dirstringtests/eval/reportsDirectory where nexus eval run writes per-run report directories (<run-id>/report.json, <run-id>/summary.txt, <run-id>/_sessions/). Path expansion via engine.ExpandPath.
judge.modelstringclaude-haiku-4-5Model used by the LLM judge for --full semantic assertions. Declared in v1; consumed in Phase 5.
judge.temperaturefloat0Judge sampling temperature. Declared in v1; consumed in Phase 5.
judge.n_samplesint1Number of judge samples per assertion; majority-threshold kicks in at >=3. Declared in v1; consumed in Phase 5.
judge.cachebooltrueEnable provider prompt cache for judge calls. Declared in v1; consumed in Phase 5.
baseline.fail_on_score_dropfloat0Absolute pass-rate drop (0–1) that fails nexus eval baseline. 0 disables the gate. CLI flag: --fail-on-score-drop.
baseline.fail_on_latency_p95_dropfloat0Relative latency p95 increase (per case) that fails nexus eval baseline. 0 disables the gate. CLI flag: --fail-on-latency-p95-drop.

Subcommand overview

CommandDescription
nexus eval run [--case <id>] [--cases-dir <path>] [--tags <csv>] [--model <role>] [--deterministic] [--full] [--parallel <n>] [--report-dir <path>] [--config <path>]Run one or all cases under the cases dir; writes a JSON report. Exits 0 on all-pass, 1 if any case failed.
nexus eval baseline --against <path> [--report <path>] [--fail-on-score-drop <f>] [--fail-on-latency-p95-drop <f>] [--out <path>] [--config <path>]Diff a fresh report against a stored baseline; honors thresholds for CI exit codes. --against path can be a report.json file or its containing run-id directory; does not descend a parent that contains multiple runs.
nexus eval promote --session <id-or-path> --case <new-id> [--cases-dir <path>] [--owner <name>] [--tags <csv>] [--description <text>] [--no-edit] [--force] [--config <path>]Convert a real session under ~/.nexus/sessions/ into a deterministic eval case. See docs/src/eval/promotion.md.
nexus eval record --from-session <id-or-path> --case <new-id> [...]Alias of eval promote — same flag set, same behaviour.
nexus eval --inspect-mode [--timeout=DURATION]Single-shot JSON-on-stdin/stdout protocol for external harnesses (Inspect AI, Braintrust, custom CI). Reads one request from stdin, writes one response to stdout. Mutually exclusive with subcommands. Deadline via --timeout flag, NEXUS_EVAL_INSPECT_TIMEOUT env, or 60s default. Wire format documented at docs/src/eval/inspect-protocol.md.

Environment variables

VariableDefaultDescription
NEXUS_EVAL_INSPECT_TIMEOUT60sPer-request deadline for nexus eval --inspect-mode. Parsed as time.Duration (e.g. 30s, 5m). The --timeout flag overrides this; an empty value falls back to the default. Source: cmd/nexus/eval.go:514-537.
NEXUS_EVAL_INSPECT_KEEP_SESSIONS(unset)When set to any non-empty value, retains the per-call temporary sessions root (os.MkdirTemp directory) for debugging instead of deleting it on exit. Off by default — directory is removed after the response is written. Source: pkg/eval/protocol/runner.go:53-60.

Cost CLI

nexus cost report aggregates cost-attribution data from session journals (idea 09). Costs come from llm.response.cost_usd which providers emit using pkg/engine/pricing — the CLI is provider-agnostic.

CommandPurpose
nexus cost report [--session <id>] [--tenant <t>] [--group-by <dim>] [--since <duration>] [--json] [--config <path>]Aggregate llm.response records by tag dimension.

Flags:

  • --session <id> — limit to one session id. Default: every session under sessions.root.
  • --tenant <t> — only Tags["tenant"] == t.
  • --group-by <dim> — one of session_id (default), tenant, project, user, source_plugin, model, task_kind.
  • --since <duration> — only events newer than now - <duration> (e.g. 24h, 7d).
  • --json — emit JSON instead of the default table.

Tags are populated by:

  • The engine’s before:llm.request seeder (session_id, plus tenant/project/user from SessionMeta.Labels).
  • Each llm.request-emitting plugin (source_plugin, plus task_kind on req.Metadata).
  • Plugins routing decisions (_routed_by, _routed_rule, _downgraded_by, _downgraded_from on req.Metadata).

Session broker (nexus-broker)

The nexus-broker binary (cmd/nexus-broker) is a standalone service, not an engine plugin. It reads its own YAML config file (default path broker.yaml, override with -config <path>) and fronts OS-isolated Nexus instances behind an HTTP/WebSocket gateway.

# broker.yaml
listen_addr: ":8080"
nexus_binary_path: "nexus"
max_concurrent: 8
idle_timeout: 5m
queue_wait_timeout: 30s
release_grace: 10s
KeyTypeDefaultDescription
listen_addrstring:8080host:port the broker’s HTTP/WS gateway binds to. GET /healthz returns {"status":"ok"}.
nexus_binary_pathstringnexusPath to the nexus binary the broker exec()s to spawn instances. Funneled through ExpandPath (supports ~).
max_concurrentint8Maximum number of live instances (one per lease). Each POST /claim acquires a capacity slot before spawning, and the slot is freed on every teardown path (manual POST /release, idle, crash, and any failed/aborted claim), so the live count can never exceed this cap or drift. A claim that arrives at capacity does not fail outright: it parks in a FIFO wait queue bounded by queue_wait_timeout (see below). Set max_concurrent to 0 (or any non-positive value) to mean unlimited (no cap).
idle_timeoutduration5mHow long an instance may sit with no real client input before the broker releases it. “Activity” is only an inbound io frame flowing client → instance (user input); instance → client output, pings, and control frames do not reset the timer. The release reuses the POST /release teardown path (shutdown frame → release_grace → force-kill → reap), so the session is persisted and the client WS closes with the going-away status. A background sweeper polls at min(idle_timeout/4, 15s) (floored at 50ms). Set idle_timeout to 0 (or any non-positive value) to disable idle reaping entirely.
queue_wait_timeoutduration30sHow long an over-capacity POST /claim parks in the FIFO capacity wait queue before giving up. When max_concurrent is full, a claim waits in arrival order; the moment a slot frees (via POST /release, idle, or crash teardown) it is handed directly to the oldest waiter, which then spawns — no fresh claim can barge ahead of a longer-queued one, and the waiters reuse the same single slot counter (no second accounting path). A waiter that exceeds queue_wait_timeout returns HTTP 503 {"error":"capacity wait timed out"} (distinct message from the immediate {"error":"no capacity"}). If the client disconnects while queued, the waiter is dropped from the queue and holds no slot. Set queue_wait_timeout to 0 (or any non-positive value) to disable waiting: an at-capacity claim is then rejected immediately with HTTP 503 {"error":"no capacity"} (no instance spawned).
release_graceduration10sHow long a release (manual POST /release, and later idle/crash teardown) waits for an instance to shut its engine down cleanly before the broker force-kills it. The graceful path always persists the session; the kill is the orphan-prevention backstop.

The spawned-instance side is configured by the nexus.io.broker plugin (broker_addr, lease_id) — see nexus.io.broker in the I/O section. Both keys fall back to the NEXUS_BROKER_ADDR / NEXUS_BROKER_LEASE_ID environment variables the broker injects at spawn (defined as brokerframe.EnvBrokerAddr / brokerframe.EnvLeaseID).

POST /claim (HTTP API, not YAML)

POST /claim mints a lease, spawns an instance with the supplied config, waits for it to dial back and signal ready, and returns the lease coordinates. The request is a small JSON envelope; session_id is optional.

// request body
{
  "config": "engine:\n  name: example\n",  // required: full nexus config (YAML text)
  "session_id": "prior-session-id"          // optional: resume a persisted session
}

When session_id is set the broker spawns the instance with -recall <id> so the engine reloads that session and replays its history; when omitted it starts a fresh session. An unknown/invalid session_id makes the engine fail to boot, so the instance never signals ready and the claim returns 502 (“instance exited before signalling ready”) rather than silently starting a new session.

// success response (200)
{
  "lease_id": "…",                          // lease handle for this instance
  "ws_url": "ws://host:port/lease/<lease>",  // client WebSocket endpoint
  "session_id": "…"                          // engine session id: the generated id for a
                                             // new session (capture it to -recall later),
                                             // or the requested id echoed back on resume
}

POST /release/{lease_id} (HTTP API, not YAML)

POST /release/{lease_id} tears a live instance down gracefully. The broker sends a shutdown frame to the instance, whose nexus.io.broker plugin emits io.session.end so the engine performs a clean Stop — flushing and persisting the session before exit. The broker then waits up to release_grace for the process to exit and force-kills it if that window elapses (no orphan). The lease is removed and its slot freed. The session directory under ~/.nexus/sessions/<id>/ is left intact and remains resumable via -recall.

OutcomeStatusBody
Released (graceful or killed)200{"status":"released","lease_id":"…"}
Unknown / already-released lease404{"error":"unknown lease"}
Missing lease id in path400{"error":"release requires a lease id"}

Release is idempotent: releasing an already-gone lease returns 404 rather than erroring, and concurrent releases of the same lease collapse to a single teardown.

GET /leases (HTTP API, not YAML)

GET /leases is a read-only introspection surface: it reports the capacity and queue aggregates plus a snapshot of every live lease, sorted by created_at then lease_id. It performs no mutation.

// response (200)
{
  "max_concurrent": 8,     // configured cap (0 = unlimited)
  "slots_in_use": 2,       // live instances currently holding a slot
  "queue_depth": 0,        // claims parked in the FIFO capacity wait queue
  "leases": [
    {
      "lease_id": "…",
      "session_id": "…",                       // omitted until reported by the instance
      "pid": 41234,
      "state": "active",                        // "spawning" | "active" | "draining"
      "reason": "manual release",               // teardown reason once draining; omitted otherwise
      "last_activity": "2026-06-25T12:00:00Z",  // RFC3339
      "created_at": "2026-06-25T11:59:30Z"      // RFC3339
    }
  ]
}

Surface states: spawning (lease exists, instance not yet registered), active (registered, frames can flow), draining (a teardown has latched).

Full narrative, the new-vs-resume flow, a WebSocket connect sketch, and the v1 deployment caveats live in the Session Broker guide.


Cross-references

  • Plugin System — plugin lifecycle, Requires() vs Dependencies(), capability resolution.
  • Gates — vetoable event mechanics shared by every gate plugin.
  • Tool System — tool choice, parallel dispatch, structured output.
  • RAG — embeddings, vector store, ingestion.
  • I/O Transport — browser vs Wails, parity rule.
  • Desktop Shell — embedder API.