Reference
A Fisk AI agent is described by a single YAML configuration file. It has the path to the application, selects which of
its commands become tools, and sets the model, the prompt, and how the harness behaves. The run, mcp, and serve
commands all read the same file; each uses the parts it needs and ignores the rest.
The --config flag selects the file, defaulting to agent.yaml in the working directory:
Each section below is a slice of that file: read it top to bottom and you have seen every setting Fisk AI understands. Fields that are required are called out as such; everything else has a working default and can be left out.
Note
Most agents need only a handful of these settings. The Agents guide walks through building one from scratch; this page is the exhaustive list to reach for when you want to know exactly what a field does.
A minimal file
The smallest useful agent names the application, sets a model, and gives a prompt:
Everything after this point expands on those blocks and adds the optional ones.
A knowledge-only agent
application_path is optional. Leave it out to run an agent with no wrapped application, on the built-in tools alone.
This is useful for a knowledge agent that answers from an indexed corpus over knowledge:
With no application_path, the identity defaults to fisk; set an explicit identity to keep the
knowledge/<identity> and memory/<identity> stores separate when you run more than one such agent in a directory.
Identity and application
identity is load-bearing beyond a label: it is the NATS subject key when the agent serves or is discovered over
agent-to-agent, and the default memory directory is memory/<identity>. Keep it to the safe
character set so those uses stay valid.
application_path is optional for run and mcp modes and required only when expose.agent.a2a.serve_tools is set,
because no built-in is offered over a2a today and such an endpoint would have nothing to serve. When set, the target must
be built with a current Fisk (v0.9.0 or newer) that supports --fisk-introspect
and precomputed per-command schemas. When it is left out, Fisk AI skips introspection entirely and the agent runs on
its built-in tools and whatever remote_tools and mcp_clients import; see
a knowledge-only agent below.
Tool selection
include and exclude choose which of the application’s commands become tools. Each takes a list of regular
expressions matched against the tool name, and a list of fisk tags:
A tool’s name is its command path joined with underscores, so a nested command like stream info becomes
stream_info. Grouping commands and hidden commands are skipped and never become tools. include and exclude can be
used together: for example include ^stream_ but exclude ^stream_rm$. Commands tagged ai:deny are dropped before
any of this runs and can never be added back.
Run fisk info to preview the resulting tool set before a run.
Model and budget
The llm block selects the model and limits what the loop may do. llm.model is the only required field in it:
Larger models reason better on complex, long-horizon tasks; smaller models like Haiku are faster and cheaper for narrow ones. When the agent exposes ten or more tools it relies on the model’s server-side tool search, which recent models support and older ones do not; see Models.
Harness
The harness block governs how the agent harness behaves during a run, as distinct from the model (llm) or the tool
selection. Everything in it is optional and the whole block can be omitted to leave every setting at its default. These
settings apply to the agent loop only; mcp mode and the a2a endpoint ignore them.
human_in_the_loop lets the model decide when to ask; the ai:confirm tag and confirm_tags gate a command the model
wanted to run anyway. The two are compared in detail under Command tags and in the
Agents guide.
Point two agents at the same memory directory and they share a memory; leave the default and each keeps its own.
Treat what a memory contains as data the model saved, not as trusted instructions.
Command tags
Fisk commands can carry tags, set in their fisk definition or, for App Builder applications, in YAML. Any tag can be
matched by include/exclude. The ai: prefix is reserved for the tags Fisk AI interprets; a tag under that prefix
that is not one of the tags below does nothing and is reported as a warning at startup, by fisk info, by the MCP
server and by the a2a endpoint.
Control tags
These change what Fisk AI does with a command.
| Tag | Meaning |
|---|---|
ai:deny | Never expose the command; dropped before include/exclude and can never be added back. The reliable off switch. |
ai:no_defer | Always send the command directly instead of deferring it behind the tool-search tool. |
ai:confirm | Require the operator to approve the command at the terminal before it runs; always active, no config flag. |
ai:confirm denies by default: no interactive terminal, or a prompt that cannot be shown, declines rather than runs. An
interrupt or an end-of-input at the prompt ends the run instead of declining, since the operator did not answer; on a
the conversation survives and fisk run --resume puts the question again. An “allow for the conversation”
answer is remembered by command regardless of its arguments: the conversation records it and honors it on every
resume. /clear and a --force resume across a changed
configuration drop it, and a resume with no terminal attached declines a gated command rather than honoring it.
harness.confirm_tags extends the same gate to any other tag your application already uses. Over MCP these gates are
requested through elicitation instead of a local operator prompt; over agent-to-agent, confirmation-gated commands are
not served at all. The full behavior is documented under Command tags in the Agents guide.
Behavior tags
These describe what the command does. They enforce nothing: they are advice, carried to the model, to MCP clients as
tool annotations, and to peer agents. Use
ai:deny and ai:confirm for control.
| Tag | Meaning |
|---|---|
ai:read_only | The command does not modify anything. |
ai:destructive | The command may destroy or overwrite existing state. |
ai:additive | The command changes state but only adds to it. |
ai:idempotent | Running the command again with the same arguments has no further effect. |
Most commands need one tag: ai:read_only for a read, ai:destructive for a delete. Leave a command untagged and
clients fall back to the MCP defaults, which assume the worst and treat it as destructive.
Each tag sets only what it names. ai:read_only does not imply ai:idempotent, and MCP clients ignore the destructive
and idempotent hints for a read-only tool. A command tagged both ai:read_only and ai:destructive is used as
destructive and the contradiction is reported as a warning.
Because these are ordinary tags, harness.confirm_tags: [ai:destructive] gates every destructive command behind
approval, and include: {tags: [ai:read_only]} serves a read-only tool set. Both select on what the command author
remembered to tag, so they are a convenience rather than a boundary; ai:deny and name-based include/exclude remain
the reliable controls.
All of a command’s tags, reserved and free-form alike, are appended to the tool description Fisk AI sends the model as a
trailing Tags: ... line, so a prompt can reference them. Adding or changing a tag changes that description, which
changes the tool-set fingerprint a conversation is keyed on: one stopped before the change refuses to continue after
it.
Serving over MCP
To serve the same tools over the Model Context Protocol instead of running the agent
loop, add an expose.agent.mcp block. It is opt-in: without this block, fisk mcp refuses to start. MCP mode uses
only the fields that describe the application and the tool set; system_prompt, llm.model, and the harness settings
are ignored.
The served tools are the agent’s top-level include/exclude selection, narrowed further by expose.agent.tools when
set. identity, if set, becomes the MCP server name. Elicitation is a request the client fulfills, not an enforcement
boundary; for a command that must never be reachable over MCP, use ai:deny rather than confirmation. The
MCP server guide covers this mode end to end.
Agent-to-agent
Fisk AI agents can also serve tools to, and import tools from, one another over NATS with no LLM on the serving side.
Both sides use a named NATS context, given as nats_context. Serving is an
endpoint of fisk serve; the Serving tools guide covers it end to end.
Note
A2A capabilities are under development, this is included here for completeness but subject to radical change
Imported tools keep their own name where it is unambiguous, and take the <alias>_<name> form only when the bare name
would collide. A run is strict: an unreachable or unimportable remote agent fails the run. fisk info is lenient
and reports each remote host’s reachability instead.
The timeouts around a2a each cover one thing. expose.agent.a2a.tool_timeout limits a call this agent answers for a
peer. expose.agent.a2a.request_timeout sets how long it waits for a peer to say anything before treating it as gone.
harness.tool_timeout limits any tool call the loop makes, remote ones included, so it is how long a remote call may
take in total. llm.budget.call_timeout limits a model call and reaches nothing on the network.
MCP clients
mcp_clients imports the tools of third-party MCP servers into an agent run, alongside the wrapped application’s
commands, the built-ins and any remote tools. Each entry names one server and selects a transport by which of command
and url it sets: command starts the server as a child process and speaks stdio to it, url reaches an
already-running server over streamable HTTP. Setting both is an error, and so is setting neither. Stdio and streamable
HTTP are the only transports, and an endpoint that speaks the older HTTP+SSE transport is not supported.
Two entries sharing a name is an error when the file is parsed, and so is two entries whose effective alias is the
same, since that alias prefixes every tool they both expose. Every imported tool is named <alias>_<tool>, where the
alias defaults to the server name, and a collision against a local, remote or another server’s tool fails the run. A
call to an imported tool is limited by harness.tool_timeout, like every other tool. The
MCP client guide covers this end to end.
Queued jobs
expose.agent.jobs opts the agent in to taking whole units of work off a Choria asyncjobs work queue. Its presence is
the switch for fisk serve: without the block, the command refuses to start. Every field under it defaults, so an empty
block is a working worker.
A job runs the whole agent loop, so it uses the agent’s own include and exclude rather than expose.agent.tools,
which selects only what is served over MCP and a2a. It needs identity, system_prompt and llm.model like any other
run, where a worker serving only tools needs none of them. The Queued jobs guide covers
submitting work and reading answers.
Telemetry
telemetry exports OpenTelemetry traces and metrics over OTLP/HTTP. It applies to fisk run, to the runs fisk serve
hosts, and to knowledge searches served by fisk mcp. The a2a endpoint exports nothing. Nothing is exported unless
enabled is true.
| Setting | Description |
|---|---|
enabled | Turns export on. Default false. |
endpoint | OTLP/HTTP base URL; /v1/traces and /v1/metrics are appended. Unset, the standard OTEL_EXPORTER_OTLP_* variables apply, defaulting to http://localhost:4318. |
service_name | Service name reported to the backend. Unset, it falls back to OTEL_SERVICE_NAME, then identity, then fisk-ai. |
sample_ratio | Head sampling ratio from 0.0 to 1.0. Default 1.0. An explicit 0 samples nothing. |
no_metrics | Exports traces only. Metrics are on with telemetry. |
capture | Exports the conversation itself, not only structure and timing. Off by default; see below. |
Content capture
| Setting | Description |
|---|---|
capture.enabled | Exports the system prompt, the conversation, model replies, tool arguments and tool results. Default false. |
capture.messages | delta (default) exports what each model call added; full exports the whole conversation on every call. |
capture.max_bytes | Cap per content attribute, measured on the encoded JSON. Default 8192, from 256 to 65536. |
Everything the model saw and everything the tools returned reaches the collector, including the verbatim output of
commands the model ran, and an export cannot be recalled. There is no command-line flag; only --no-telemetry, which
suppresses the whole export.
What harness.pii removes is gone before this sees it, since the scan runs as the text enters the conversation. That is
the only redaction on this path: everything harness.pii does not scan or does not detect is exported verbatim.
Plain http:// to a non-loopback host is rejected at startup while capture is on. The settings under capture are
ignored and unvalidated while capture.enabled is false. See the telemetry guide
for the attributes, sizing and collector limits.
Transport credentials are never written in the file. OTEL_EXPORTER_OTLP_HEADERS and the other standard OTEL_*
variables configure the connection, so the same configuration sends to a collector, Grafana Tempo, Honeycomb, or any
OTLP/HTTP endpoint without a change here. This build speaks OTLP/HTTP only: port 4318, not 4317.
Whether a run exports is decided in this order:
| Condition | Result |
|---|---|
--no-telemetry, NO_TELEMETRY, or OTEL_SDK_DISABLED=true | Off |
telemetry.enabled: true | On |
| Otherwise | Off |
Setting OTEL_EXPORTER_OTLP_* does not enable export on its own; a host-wide collector endpoint does not turn every
agent on the machine into an exporter. A run that finds those variables set while telemetry is off prints a note saying
so.
An invalid configuration fails at startup rather than exporting nowhere: an endpoint that is not an http or https
URL, an endpoint on port 4317, OTEL_EXPORTER_OTLP_PROTOCOL=grpc, a sample_ratio outside 0.0 to 1.0, or plain
http to a non-loopback host while an OTEL_EXPORTER_OTLP_*_HEADERS variable is set, which would send the credential in
the clear.
fisk info shows the resolved settings and where each came from. After a run, an export that did not reach the
collector is reported; --verbose also reports a successful one.
A run exports one trace covering the whole run, with spans for setup, each turn, each model call, each tool call, each knowledge search, each request to the embeddings server and each tool served by a remote agent, plus the GenAI metric instruments. A model call carries one event per HTTP attempt, so a retried call reports what it spent waiting. Indexing is not instrumented. The Telemetry guide has a local collector to try it against, the span tree to expect, and the attribute reference.
Models
Well-known Anthropic model identifiers are available as constants in the config package; any value the Anthropic API
accepts may be used in llm.model, local LLMs will have their own convention. fisk does not restrict what you enter here.
| Constant | Identifier | Notes |
|---|---|---|
ModelClaudeFable5 | claude-fable-5 | Most capable overall, for demanding reasoning and long-horizon agentic work; highest cost. |
ModelClaudeOpus48 | claude-opus-4-8 | Most capable Opus tier; slowest and most expensive Opus. |
ModelClaudeOpus47 | claude-opus-4-7 | Prior Opus release. |
ModelClaudeOpus46 | claude-opus-4-6 | Earlier Opus release. |
ModelClaudeOpus45 | claude-opus-4-5-20251101 | Earlier Opus release. |
ModelClaudeSonnet5 | claude-sonnet-5 | Balanced capability, speed, and cost; good default. |
ModelClaudeSonnet46 | claude-sonnet-4-6 | Prior Sonnet release. |
ModelClaudeSonnet45 | claude-sonnet-4-5-20250929 | Earlier Sonnet release. |
ModelClaudeHaiku45 | claude-haiku-4-5-20251001 | Fastest and cheapest; best for simpler tasks. |
Every model in the table supports the server-side tool-search tool that deferred tool discovery relies on. Anthropic’s
tool search is generally available on Claude Opus 4.5, Sonnet 4.5, Haiku 4.5, and later; Claude Opus 4.1 and earlier, and
local models, do not support it. If you point llm.model at an older identifier or a local model while exposing ten or
more tools, the model is left holding only the tool-search tool with no way to reach the deferred commands and the run
stalls. With such a model, keep the exposed set below ten tools (around 15 for local runners) so every tool is sent
directly.
Command-line flags and environment
Some behavior is set per run on the command line rather than in the file. The flags override the file where they
overlap, except for the hard off switches (harness.no_tui), which the command line cannot re-enable.
| Flag | Environment variable | Description |
|---|---|---|
--config | Path to the configuration file. Default agent.yaml. | |
--api-key | ANTHROPIC_API_KEY | Anthropic API key. Required. |
--base-url | ANTHROPIC_BASE_URL | Anthropic API base URL to use, for example a local Anthropic-compatible runner. Either http or https, naming a host, with no embedded userinfo credentials. |
--http-debug | HTTP_DEBUG | Dump Anthropic API request and response bodies to http-debug.log. The file holds the full conversation and is created mode 0600. |
--no-color | NO_COLOR | Disable markdown rendering of the final answer, emitting raw text. |
--no-tui | NO_TUI | Disable the full-screen terminal UI and answer one prompt with line-by-line output. The full-screen view holds a conversation of many turns; this answers one. |
--verbose | VERBOSE | Show more verbose output. |
--thinking | THINKING | Show the model’s reasoning, which is hidden by default. On fisk session show --transcript it includes reasoning in the transcript. The thinking=N token counter is reported either way. |
--trace | Write a JSON-lines trace of every LLM request and response to a file. | |
--resume | Continue a stored conversation, by the session id fisk session ls shows or by a conversation token. | |
--force | Continue a stored conversation whose configuration has changed since it started. Standing approvals are dropped. | |
--state-dir | Override where the sessions of the agent this process hosts are stored, default $XDG_STATE_HOME/fisk-ai/runs. Refused with --nats-context, where the agent is elsewhere and keeps its own. | |
--nats-context | On fisk run, talk to an agent on this NATS context instead of running one in this process. The configuration’s identity names the agent and must be set. | |
--a2a-debug | Dump every a2a message between this terminal and the agent to a2a-debug.log. The file holds the conversation token, your prompts and all tool output; it is created mode 0600. | |
--no-telemetry | NO_TELEMETRY | Suppress OpenTelemetry export, whatever telemetry.enabled says. On fisk run it covers the run, on fisk serve the whole worker. The credential scrub still applies. |
--workers | On fisk serve, how many jobs to run at once, overriding expose.agent.jobs.workers. | |
--work-dir | On fisk serve, the directory command tools run in. Must be an absolute path that exists. Defaults to the worker’s own working directory. |
The MCP server port also reads FISK_AI_MCP_PORT, which --port overrides and which in turn overrides
expose.agent.mcp.port. Sessions, chat, and their durability semantics are covered under Session snapshots.
--workers overriding the file is the opposite of how harness.tool_timeout works, where a configured value beats the
built-in default. The worker count is a property of the process; the tool timeout is a property of the agent.
Safety
The configuration is the boundary on what the model can reach: application_path fixes the one binary it can drive
(and with no application_path set the agent can drive no external binary at all), include/exclude and ai:deny
fix which of its commands become tools, and nothing outside that set is callable.
Commands run as an argument vector rather than through a shell, each argument is checked against the command’s schema, the
ANTHROPIC_API_KEY is stripped from their environment, output is capped at 64 KiB, and LLMFORMAT=1 is set. The
Agents and MCP guides describe the full threat model for each mode.
The OpenTelemetry export credentials are stripped from tool environments too: OTEL_EXPORTER_OTLP_HEADERS and its
per-signal forms, and the mTLS variables OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_CERTIFICATE and
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE with their per-signal forms. This happens whether or not telemetry is enabled,
so --no-telemetry does not re-expose a collector token. The mTLS variables name a file path rather than holding a
secret, so removing them from the environment hides the location of the key, not the key: a tool running as the same
user can still read that file if it knows where to look.
Spans carry structure and timing, tool names and argument key names, and no prompts, tool arguments or results.
Setting telemetry.capture.enabled reverses that for every one of them, and for the error.type reduction as well,
since a tool’s error text is part of its result.
Personal data
harness.pii scans the prompt and each tool result as it enters the conversation, before the model, the session store
or a telemetry collector sees it, and either replaces what it finds or refuses the text. It redacts unless configured
otherwise. fisk info reports the mode in effect, and a run says so the first time it acts.
It looks for BANK_ACCOUNT, CLOUD_RESOURCES, CREDIT_CARD, DATE_OF_BIRTH, DRIVERS_LICENSE, EMAIL,
MEDICAL_ID, OTP, PASSPORT, PHONE, PHYSICAL_ADDRESS, SECRETS and SSN. mode is its only key:
nothing narrows or widens that set. A value of another kind reaches the model as written, including a national
identity number that is not a US SSN, an account identifier internal to your own systems, and a person’s name in
prose.
Detection is pattern matching and is best-effort in both directions: it misses real values (a valid US social security number went undetected in testing) and it flags text that is no such thing. It lowers what leaks; it does not gate it, and no decision to send data somewhere should rest on it.
Credentials are scanned alongside personal data: API keys with a recognizable prefix (sk-…, sk-or-v1-…, sk-ant-…,
xoxb-…, ghp_…), bearer tokens, and NATS credentials and nkey seeds. These are matched by their own shape, so a key
pasted into a prompt or printed by a tool is found wherever it appears, not only where it is assigned to a name. A
credential with no distinctive shape is not found: an opaque value is indistinguishable from a git SHA or a base64 blob,
and a rule wide enough to catch it redacts those too.
Four limits are worth knowing before relying on it:
- It does not see the system prompt, the memory index that prompt carries, the model’s own replies, the arguments the model writes for a tool call, a result a caller supplies for a deferred call, or the history a resume restores. A session journaled before the feature was turned on keeps what it recorded.
- Redaction is one-way. A placeholder the model reads out of a file goes back into that file through the next tool call, since there is no restore path. Asking an agent to edit a file whose contents are redacted will corrupt it.
- The scan is the last point the text passes, not the first. A prompt sent to an agent over a2a is already on the broker, in the task record and in any wire log before the agent that answers scans it.
- What the operator sees is what the model was given. A redacted tool result reads as redacted in your own terminal, for data on your own machine, because the same trace reaches a caller who is not at it.