Synopsis↑
snajpagent [options] [--]
[initial-prompt ...]
snajpagent --resume [options] [session-id | -N NAME |
--last] [-- follow-up ...]
snajpagent --attach [--dotdir PATH] [session-id | -N
NAME]
snajpagent -e [options] [-- prompt ...]
snajpagent -l [N] [options]
snajpagent [options] login [provider]
[--openai-device-auth | --meta-device-auth | --with-api-key]
snajpagent [options] login status [provider]
snajpagent [options] logout [provider]
snajpagent remote [--config PATH] [--dotdir PATH]
[--] [COMMAND ARG ...]
Description↑
snajpagent is a coding agent for the terminal, with a built-in IRC server and client. The model can read and edit files and run commands. The program runs in the foreground and keeps resumable local sessions; there is no resident service.
An interactive session can independently host one single-room IRC server and maintain up to 16 outgoing IRC connections. Agents and channel operators share the room; the runtime owns joining, history, and reconnection. Startup flags and runtime slash commands control this native networking without changing session identity.
Getting started↑
Documentation and build identity
This manual describes the source revision it accompanies. The web manual tracks current source; each downloadable release includes a matching manual. Run snajpagent -V to identify the executable on disk. A running session retains the code with which it started. After an update, exit normally and use its printed resume command to launch the new code; a rebuild alone does not replace a live process.
Check the downloads page for the latest stable release; this manual matches the release it ships with. Later source commits are listed under Unreleased in CHANGELOG.md and do not replace the immutable release assets.
Choose a download
The downloads page at https://agent.snajpa.net/downloads.html has Linux, macOS, Windows, FreeBSD, OpenBSD and NetBSD panels. Choose the architecture and OS/ABI shown in the row, including its experimental status. The standalone-build reference below explains each target's requirements.
Download to a user-owned directory. Rename the selected executable to snajpagent (or snajpagent.exe on Windows). In the examples below, first compare the displayed SHA-256 with that file's download row or the release's SHA256SUMS. Run the remaining commands only after it matches and you trust the source. If a legacy system lacks a SHA-256 utility, verify on a trusted newer machine and transfer over a trusted channel; MD5, SHA-1 and plain CRC checksums are not substitutes.
For an archive, hash the archive before extracting it; its published checksum is not the checksum of the executable inside. Runnable debug archives contain an executable; separate production symbol archives only diagnose the matching production download. No compiler, Nix installation or debug symbols are needed to run an official executable.
Install on Linux
Choose x86-64, AArch64, i686, ARMv6/ARMv7 hard-float, RV64GC/LP64D, POWER8 little-endian or 32-bit big-endian PowerPC to match your system. The legacy i686 download is separate from modern i686. Application libraries and TLS roots are included in these self-contained executables.
sha256sum ./snajpagent chmod +x ./snajpagent ./snajpagent
Legacy i686 requires procfs and secure OS entropy; PTY commands also require devpts. Choose the download's documented kernel/CPU baseline, not just a matching word size. Android has experimental source support, but no production download target; these Linux downloads are not an Android installation recipe.
Install on macOS
Choose Apple Silicon, Intel, or the universal executable containing both architectures; the downloads target macOS 11 or later. These experimental executables are not Developer ID signed or notarized.
shasum -a 256 ./snajpagent chmod +x ./snajpagent ./snajpagent
A quarantined browser download can be blocked at first launch with Killed: 9. After checking the checksum and only if you trust the file, remove quarantine from that executable and launch again:
xattr -d com.apple.quarantine ./snajpagent ./snajpagent
This is a per-file security exception. Keep system-wide macOS protections enabled; execute permission alone does not clear quarantine.
Install on Windows
Choose the experimental native x64 or ARM64 .exe. Application libraries and TLS roots are included; ARM64 also needs the operating system's UCRT. The standalone-build reference records the limited Windows qualification. In PowerShell, from the directory containing the renamed file:
Get-FileHash .\snajpagent.exe -Algorithm SHA256 .\snajpagent.exe
In Command Prompt, where certutil is available:
certutil -hashfile .\snajpagent.exe SHA256 .\snajpagent.exe
Keep the .exe extension. PowerShell requires the explicit .\ prefix for an executable in the current directory. To work elsewhere, add its user-owned folder to your user PATH or invoke its full path. PowerShell uses the call operator for a quoted path, for example:
cd C:\work\myproject & "C:\Users\Alice\Tools\snajpagent.exe"
Commands in this manual otherwise use POSIX shell syntax. The Windows tool shell defaults to cmd.exe, even when the agent is launched from PowerShell; configure tools.shell explicitly to choose another shell. State defaults to HOME/.snajpagent when HOME is set, otherwise USERPROFILE/.snajpagent. Use --dotdir DIR to choose another private location.
Install on FreeBSD
Choose the amd64 download for the listed OS/ABI. The FreeBSD 5.1/5.5 legacy executable requires libc.so.5 and libc_r.so.5; the other variant targets the libc.so.7/libthr.so.3 ABI. Application libraries and TLS roots are included; the OS loader and matching base libraries must be present.
sha256 -q ./snajpagent chmod +x ./snajpagent ./snajpagent
If an older installation lacks sha256, use the trusted-machine verification procedure above. Renaming or copying a newer executable cannot change its ABI.
Install on OpenBSD
Choose the separate amd64 download for OpenBSD 7.9, 5.9 or 3.5. Their native loader/libc/thread ABIs differ; use the exact listed variant. Application libraries and TLS roots are included. Use sha256 where available; on older installations without it, verify on a trusted newer system before transferring the executable, then grant execute permission and launch.
sha256 -q ./snajpagent chmod +x ./snajpagent ./snajpagent
Install on NetBSD
Choose the amd64 download for NetBSD 10.1 (libpthread.so.1) or the legacy 2.0/5.2.3 variant (libpthread.so.0); both use libc.so.12. The OS loader and matching base libraries are required. Application libraries and TLS roots are included. On NetBSD 5.2.3 and 10.1:
cksum -a SHA256 ./snajpagent chmod +x ./snajpagent ./snajpagent
NetBSD 2.0's base cksum lacks SHA-256. Verify on a trusted newer system before transferring the file to 2.0, then grant execute permission and launch it.
Put the executable on PATH
On Linux, macOS and the BSDs, after verification, a user-local installation avoids administrator privileges. Run this from the download directory in a POSIX shell (start sh first if you normally use csh or tcsh):
mkdir -p "$HOME/.local/bin" cp ./snajpagent "$HOME/.local/bin/snajpagent" chmod 755 "$HOME/.local/bin/snajpagent" export PATH="$HOME/.local/bin:$PATH" snajpagent
Keep the PATH assignment in your shell's startup configuration to use it in future terminals. The Windows instructions above explain user PATH and full paths. New sessions use the home directory as their working directory, regardless of the shell directory used to launch the program. Use the source-build instructions below for other build environments; an unlisted OS or ABI is not a qualified binary target.
Provider setup
On a fresh interactive launch without configuration or existing environment credentials, choose ChatGPT/Codex, Meta subscription, OpenRouter, OpenAI, or a custom provider. Complete device login or enter a masked API key (blank selects an environment variable), optionally fetch that provider's catalog, and choose a model. Setup creates a minimal private configuration and separate credential store, then continues into the ordinary session. Cancellation leaves prior settings and credentials intact.
For manual configuration on POSIX systems, create a private state directory:
install -d -m 700 "$HOME/.snajpagent"
Create $HOME/.snajpagent/config.ini, choosing a model supported by your provider:
[agent]
provider = openai
model = gpt-5.5
[provider openai]
base_url = https://api.openai.com
api_key = ${OPENAI_API_KEY}
Export the named credential, change to the project directory, and run:
export OPENAI_API_KEY='your-key' cd /path/to/project snajpagent
Enter submits your text. An empty or whitespace-only prompt leaves its line visible and opens a fresh prompt, like a shell, in both rollout and chat. To ask the model to continue, type your request and press Enter. Type /help for command syntax and keys; square brackets mark optional arguments.
On Windows, first-run setup creates the directory itself. For manual setup, create the private dotdir under HOME or USERPROFILE as described above and save the same config.ini contents there as plain UTF-8 text. Keep credentials accessible only to your account. Set a credential for the current shell before launching. In PowerShell:
$env:OPENAI_API_KEY = 'your-key' & "C:\Users\Alice\Tools\snajpagent.exe"
In Command Prompt:
set "OPENAI_API_KEY=your-key" C:\Users\Alice\Tools\snajpagent.exe
These shell assignments last for that shell and its child processes. Interactive masked-key entry or device login avoids putting a secret in shell history. The provider configuration syntax is the same on all platforms; POSIX export/chmod/install commands are not Windows commands.
Start a task
After installation and provider setup, start snajpagent. New sessions begin with the home directory as their working directory. The model can call get_cwd to inspect it and cd to change it; absolute paths work anywhere the process can access. File and command tools run with your OS permissions, without per-call approval. Tool calls are hidden at the default verbosity.
Without network roles, startup opens rollout: the local view of your conversation and the model's work. Describe a task and press Enter:
Fix the empty-input bug. Keep the public API unchanged. Run the tests and summarize the change.
This prompt and the work it starts make up a turn. The model may need several responses and tool calls before it gives a final answer. The prompt marker is › while idle and » while active.
Text starting with / is handled as a command by snajpagent. Type /help and press Enter to see the controls. /verbose 1 shows compact tool activity alongside model text; /verbose 2 adds previews of tool inputs and results. These controls also work during a turn.
Correct this task or queue the next one
Typing during work opens an unsent draft; it does not interrupt the model. The rollout composer shows the steer marker once the turn starts, even before the provider has accepted a request. Enter then supplies steering. For example, "Use the existing parser; do not add a dependency" changes the current task. The model's response is interrupted and continued from its delivered text. An already-started command stays alive for the model to collect or stop.
The submitted line is shown immediately. The active prompt remains visible while a request is prepared, sent, retried or continued; a submitted steer before provider acceptance is handled at a safe request boundary. A foreground slash command temporarily hides the prompt until that command finishes, and snajpagent does not execute another app command meanwhile. Ctrl-C and exit remain priority controls.
The active prompt is visible and editable throughout the turn, even when empty or while a response is unready. Only a foreground slash command hides it until completion. Enter submits a steer for admission at a safe turn boundary.
For a separate follow-up, type "Then add regression coverage" and press Tab at the end while work is active. That message waits in the queue until the current turn ends. Queued prompts run oldest first, one turn at a time in the same session. The rollout prompt's (N) counts waiting turns, excluding current work.
Use /queue to inspect waiting prompts, /queue 2 edit to revise the second one, or /queue 2 delete to remove it. Editing keeps its place and holds queue draining while the editor is open. Enter saves the replacement; Ctrl-C abandons the edit. /queue pop removes the newest item; /queue clear removes all waiting items. Current work continues.
Queue arming survives exit and resume. Explicit cancellation, a terminal failure, a new direct prompt or an open queue editor pauses it. Idle queue additions start paused. Use /next to arm it: at idle the oldest item starts immediately; during active work it starts after the current full turn. The remaining entries drain in order. Queued prompts take precedence over automatic goal continuation. The acknowledgement queued (/next or /q c) › marks a submitted follow-up. At execution, the prompt appears again in the transcript with the normal rollout prompt, current local timestamp and effective model settings. The timestamp reflects dispatch; stored input provenance keeps the original receipt time. Retries within that turn do not repeat its prompt. An unfinished draft retains its text and original prompt timestamp. /next continues a paused queue; /q c clears waiting prompts without stopping current work. See Provider failures for the new-input/retry handoff case.
Commands while work is active
Enter slash commands in either view while a turn runs. A setting that affects later responses can still be changed now. Inspection and display commands run when accepted; deferred controls acknowledge the boundary they await and retain accepted intent for resume. A running foreground slash command owns the prompt. Bytes typed during it wait and are interpreted only after its result. In rollout, an ordinary active-turn submission is echoed before the engine handoff, so it remains visible even if that handoff blocks the next composer. The steering record becomes durable when the engine admits it; successful admission does not print the text a second time. Slash commands and queued controls keep their own acknowledgement timing. Empty-draft Ctrl-C interrupts an active operation.
Queue editing keeps command input separate from replacement text. Enter on /status inspects the session, and /exit preserves it; use // for a literal leading slash in replacement text. Delete confirmation also accepts commands, but only the displayed session prefix authorizes deletion. Ctrl-C cancels the confirmation; opening the queue editor cancels deletion and opens that editor. The external configuration editor takes exclusive terminal input until it exits.
Read history and its length
/history replays up to one recent retained turn by default. Use /history 10 to request ten, or /history 0 for the total turn count without reading the journal. Replay ends with shown turns, completions among those shown, and the total session turn count. Unfinished, failed and interrupted work is included. Automatic resume uses the same replay and defaults to one turn. The scan covers at most 4 MiB plus one atomic journal record. A partial oldest turn is explicitly marked; omitted input is not rendered as user text. When the scan ends early, its continuation sequence locates older records through the model's read_session_history tool. IRC references outside the window may remain unresolved.
Final voice transcripts and voice-initiated action results in that window are included, even when the session has no coding turns. Speaker labels distinguish ASR input from generated replies; action rows follow tool verbosity settings. Voice records do not increase the coding-turn totals. Replay displays retained text and outcomes without activating the microphone, playing speech or executing actions. /history 0 still reports only totals.
Conversation replay is separate from the session's prompt-entry history used by Up, Ctrl-P and Ctrl-R. Compaction reduces model context, not the full local conversation log available to /history.
Page a local file
/cat PATH opens a regular file with the configured pager and returns to the prompt when the pager exits. For example, /cat ~/notes.txt opens a file in the current working directory; absolute paths and ~/ paths also work. Spaces in a path are literal; you may enclose the whole path in single or double quotes. Shell expansions in PATH are not evaluated. The file is passed to the pager, without adding its contents to the session conversation or sending them to a provider. This command also works while a turn is active. It requires an interactive terminal and a configured pager; if pager = on and $PAGER is unset, set $PAGER or choose a pager command in the configuration. A missing or non-regular file reports an error.
Complete names with Tab
Command completion starts with a slash at the beginning of the draft. Type /sta and press Tab to get /status followed by a space. Press Enter to run the command. Completion applies while the cursor is in or at the end of that first command word, not in its arguments. Ordinary words do not trigger command completion.
In chat, begin a nickname word with @ and press Tab while the cursor is in or at the end of that name. If builder is the only match, @bu becomes @builder followed by a space. The nickname need not start the whole message: "please ask @bu" also completes. A unique completion adds or reuses a space and leaves the cursor after it, ready for you to finish writing.
At the end of a finished message, outside a command or nickname being completed, Tab queues a local follow-up while your model is active. In chat this still does not send to the room; Enter does that. While idle, Tab inserts spaces instead. With an empty draft, Tab cycles through rollout and each connected room's chat view, then returns to rollout.
Completion itself never submits, sends or queues. Multiple matches expand their common beginning; another consecutive Tab lists the choices. No match leaves the draft unchanged, not queued. Full cursor and command exceptions are listed under INTERACTIVE INPUT.
Edit or stop
Ctrl-J inserts a newline. Bracketed paste keeps pasted newlines in the draft instead of submitting each line. Up/Down move through wrapped or multiline draft rows, then prompt history at the start or end of the text. Ctrl-A/Ctrl-E jump to the start/end of the whole prompt. Ctrl-P/Ctrl-N go directly through history; Ctrl-R searches it. Ctrl-C clears a nonempty draft without interrupting the turn. On an empty draft it interrupts active work. Ctrl-D on an empty draft exits.
Continue toward a goal
A normal final answer ends the turn and returns control to you. A goal asks snajpagent to keep taking turns toward an explicit result:
/goal fix the parser, add tests, and validate the change
It continues until complete, paused, cancelled or blocked. Waiting operator prompts run before the next automatic goal turn. /state goal status shows its state and any blocker. /state goal pause pauses automatic continuation at a turn boundary; /state goal resume starts it again. Pausing does not kill a command already running. Ordinary errors retain the goal and recover automatically. Policy stops and provider refusals pause automatic goal continuation. Explicit turn interruption pauses automatic goal work.
Leave and resume a session
On Linux, interactive ANSI terminal sessions keep running when their terminal disconnects, including when an SSH connection drops. Open a new SSH connection and attach to the same session. Use /s d to return to the shell while requests, commands, IRC and goals continue. Reconnect with snajpagent --attach SESSION_ID; -A is the short option. Without an ID, select from the running-session list. Name a new session with snajpagent -N lead, then reconnect with snajpagent --attach -N lead. Use /session name lead inside an existing session to give it a name. Only one terminal controls a session at a time. The session keeps its unsent draft, selected view and destination; attachment displays bounded recent history before restoring the composer. Typing and terminal resizing preserve the draft and previously displayed conversation. Buffered typing is displayed as a completed edit while resize controls remain responsive. An unfinished terminal file transfer ends when its controlling terminal disconnects; queued downloads retain their saved state. Local voice, dictation and playback stop when that terminal detaches, disconnects or suspends. Reconnecting does not restart capture.
New editors and pagers use the attached terminal's TERM and GNU Screen profile. An already-running external program keeps its launch environment and receives the terminal resize notification. Owner configuration, including color policy, stays unchanged.
A session saves its name, current working directory, conversation, tool results, model selection, queue and goal on disk. Normal exit prints a command to reopen that exact session. You can also list sessions with snajpagent -l or reopen the newest session with snajpagent --resume --last. Listing shows all attached sessions, then all detached sessions, then the 10 most recent stored sessions. Use snajpagent -l 25 to include 25 stored sessions, or snajpagent -l 0 for running sessions only. Each group shows the latest saved activity first. Use snajpagent --resume -N lead to attach to a running session or resume a stored one by name.
Exit is not goal pause. No work runs after exit, but a saved active goal continues when resumed. Use /state goal pause first if you want it to remain paused. Other goal states remain unchanged. /state shows session state including the goal and its actions; /goal is an alias for /state goal. When a turn leaves a paused or blocked goal with nothing pending, the terminal prints an idle line naming the state, so paused no longer reads as stuck. A blocked goal with an armed timer says the timer is scheduled; it does not claim to be awaiting the operator. Armed waiting prompts continue after recovered active work. Paused prompts need /next and cannot be bypassed by automatic goal work. Resume restores recorded context and accounts for lost command owners; old OS processes and handles are not revived.
Resuming a stopped session retains its saved listener, clients, nicks and room. Explicit startup options replace the corresponding setting before work continues. For example, reopen a session with a hosted IRC server and no outgoing client connections:
snajpagent --listen=localhost:6667 --no-client \
--resume SESSION_ID
Listen, client, nick and room overrides apply before resumed work starts; resume options may appear before or after the session id, while follow-up text still requires --. the live network state is shown locally and supplied to the model. A fresh snapshot on interactive resume also records offline and client-only state, replacing the previous process's topology as the latest network context. A requested listener that cannot bind reports a startup error.
Direct input is recorded before preparation, credential acquisition or model requests. A preparation failure retains the pending text, accepted attachments, read-only mode and selected provider/model/effort. Resolve the reported dependency and use /retry or resume the session to continue. Ctrl-C cancels pending input. A later prompt is queued behind retained input rather than replacing it. Attachment replay uses the retained bytes and does not reread or reconvert the original files.
Same-turn recovery retains the original working-document paths. The files remain external dependencies: their contents are read when needed, and moved or missing files require inspection. New full turns discover their own entry points. Session records contain paths rather than automatic file snapshots.
Silent SSH network loss
A network outage can leave the server's SSH connection and terminal frontend alive until SSH detects the missing client. If attachment reports that a terminal is still present after an outage, wait for the SSH timeout and reconnect. Configure the SSH server's sshd_config with:
ClientAliveInterval 15 ClientAliveCountMax 3
Validate with sshd -t and reload the SSH service. New connections then release unresponsive clients after approximately 45 seconds; responsive idle clients remain connected. Existing connections keep their original liveness settings. The detached native agent owner continues running. Client-side ServerAliveInterval can detect a dead server, but server-side checks release the abandoned terminal.
Interactive --resume selects the live owner's attachment path, including by ID, -N NAME, --last or the picker. A terminal still held by another connection is reported as occupied; attachment does not displace it.
Read this reference
Use man snajpagent for the installed reference. From a checkout, man -l ./snajpagent.1 opens the same source. The hosted edition is https://agent.snajpa.net/manual.html. The website and README introduce workflows; the sections below define all options, commands, configuration, tools, files, limits, failures and security.
Native IRC↑
Host and join a room
IRC is the built-in chat protocol. Each session has an operator nickname (yours) and a model nickname. In separate terminals, start a host and client:
snajpagent -s -n builder -o alice -r work snajpagent -c -n reviewer -o bob
The first hosts room #work at localhost:6667; the second joins it on the same machine. The -n option names the model and -o names the operator. This shares conversation, not files, credentials or command processes. Use separate Git worktrees when agents should have independent checked-out files to edit.
Networked startup opens chat, the shared room conversation. Enter sends as your operator nick. Address a model, for example "@builder check the failing test", to start work or steer it at a safe boundary. Ordinary conversation is background context, not a new task. Models publish chosen replies through irc_send while their other text and tool activity remain in rollout.
Empty Tab cycles through rollout and each room without stopping work. Returning displays output that arrived while the view was hidden. To complete a nickname, type @bu and Tab, then finish your message after the added space. Enter sends it to the room; Tab at the end queues it locally while your own model is active. /chat and /rollout select either view directly. Runtime /server start and /connect change network roles without restarting or switching the current view.
Routing, identity and history
Listener mode creates one room. Its default name is the hostname and its initial topic is the server agent's absolute starting working directory. snajpagent clients join the advertised room automatically.
The local operator and ordinary IRC clients receive channel mode +o; the model nick does not. Current channel operators may change the topic; on a room this session hosts, the model link may set the topic as well. Links declare their role at registration: the model link requests the snajpagent/agent capability and sends the USER realname snajpagent agent, and the operator link sends snajpagent operator; the host derives the link role from either declaration. Agents are never opped; operator clients and the operator link are. Only direct mentions of the model nick are urgent; ordinary messages from local or channel operators remain background context. A model whose exact [model-limit] entry sets steering to all admits every room message as steering instead. Urgent input is coalesced and admitted at the earliest safe boundary; generation reaches a safe stream boundary and commands are handed back alive.
Other messages, joins, leaves, mode changes, and topic changes enter model context when convenient and do not force a reply. They wait for an active turn; background traffic never admits a second input while one is live, so another worker joining, being opped or leaving can never end or interrupt this session. First join and successful compaction inject bounded room history. The runtime reconnects outgoing links automatically. Pending room messages and notices retain their original journal event across compaction and resume, including on plain IRC servers without catch-up stream IDs. Later admission presents each saved input once. Older admissions shifted by an inserted checkpoint can recover on resume when the adjacent saved IRC event exactly matches the admission's recorded identity. The journal remains unchanged; a missing or mismatched source still fails.
Implicit outgoing nicks form zero-based sequences: agent0, agent1, and so on, with the same rule for the operator's default. When an explicit preferred nick is already present, the client handles the server's normal 433 reply by appending 1, 2, and so on until registration succeeds. The accepted nick is retained across reconnects.
The terminal starts in the timestamped chat view. Model text and tools belong to the rollout view regardless of verbosity. Chat input goes to the selected room as the local operator. Multiple destinations receive short source/prompt labels; a hosted room counts once regardless of how many clients joined it. After a submission the composer returns once that turn has begun processing rather than at once, so a ready-looking prompt never appears under a line nothing has acted on yet; keys typed in that window are buffered and appear with the composer, while an empty, slash-command or display-only submission still repaints immediately. A turn that never reports processing returns the composer after a brief bounded delay (250 ms). Explicit numbered sends and broadcasts also work from rollout, but ordinary rollout input stays local: while idle it starts a model turn, and during active work it steers that turn at the next safe boundary. It never emits an IRC message. Admitted room traffic is not echoed as submitted input either: the chat view shows the room events and the durable trail records the admission. Incoming room traffic is not automatically bridged between servers. Model response text remains local; irc_send is the only model-authored path to a room message or notice. If a local-operator mention turn reaches a terminal response without a successful irc_send message, the model receives one reminder. Notices do not satisfy that reply requirement. Other room traffic never forces a reply. Each requirement stays with the input or turn that admitted it. Resume restores those belonging to the current pending input or active turn.
The accepted per-server nick drives mentions, echo suppression, and model instructions. Live IRC nick changes retain op status and produce room notifications. Chat uses the selected destination's accepted operator nick. There is no local slash command for changing nicks; standard IRC clients can use NICK.
Nicks are at most 30 bytes and local model/operator preferences must differ under IRC case folding. Room names are at most 50 bytes excluding #. The server implements registration, join/part, messages/notices, topic, names/who, nick and mode changes, liveness, and bounded history, not the entire IRC protocol. snajpagent clients expect its advertised single-room behavior.
The built-in IRC subset has no TLS or authentication. A non-loopback listener is suitable only for a trusted network or a separately secured tunnel.
Options↑
- -s [endpoint], --listen[=endpoint]
- Host the built-in IRC server in this foreground process and select its listener. This does not detach. A missing endpoint means localhost:6667. An address already in use is a startup error, visible without verbosity flags. It does not try another address or increment the port after that collision.
- -c [endpoint], --client[=endpoint]
- Add an outgoing IRC connection. Repeat for up to 16 distinct endpoints. A missing endpoint means localhost:6667.
- --no-listen, --no-client
- Independently suppress a configured listener or clear configured outgoing connections. Each conflicts with its positive startup flag; both negatives may be used together. Nick and room preferences remain usable while offline.
- -n nick, --model-nick nick
- Set the model's preferred IRC nick. Default: agent0.
- -o nick, --operator-nick nick
- Set the local operator's IRC nick. The default is a valid USER value plus 0, then operator0, or localop0 when needed to remain distinct from the model nick.
- -r room, --room-name room
- Set the preferred room for current or future hosting. A leading # is optional. The default is derived from the hostname.
- -d directory
- Advertise an additional local working-document root. Repeat for multiple roots. Each directory must contain a regular file, or a symlink to a regular file, named AGENTS.md or AGENTS.override.md; the override takes precedence. Relative option paths resolve against the shell directory used to launch snajpagent. Canonical duplicate paths are collapsed. These roots are saved with the session; omitted roots on resume retain them, while explicit -d options replace the saved set. They remain explicit pointers even when automatic discovery is disabled. They do not grant unrelated write permission or synchronize documents between hosts.
- -m [provider/]model[/effort]
- Select the model for the next request on start or resume, including an unfinished turn. With an omitted provider, use the first provider defined in the configuration (or an explicit --provider). With two components, a configured first component means provider/model; otherwise the components mean model/effort. With an omitted effort, use the first effort listed for that model in the local cache, then its cached default, then the configured reasoning effort. --effort explicitly overrides the selector's effort. Typed model IDs need not be cached. A number, or # followed by a number, selects the entry at that position in the numbered model listing, so a model copied from the listing selects what the listing showed; an index outside the catalogue is an error rather than a literal model name. Each component must be nonempty. Wrap a component in matching single or double quotes to keep embedded slashes, for upstream IDs such as OpenRouter's vendor/model form: openrouter/"openai/gpt-4.1-mini" or openrouter/"openai/gpt-4.1-mini"/high. Protect those quotes from the shell, for example -m'openrouter/"openai/gpt-4.1-mini"'. A quoted vendor/model designator still honours a configured provider prefix, so "openrouter/openai/gpt-4.1-mini" selects the same model. A provider-local model name in the configuration remains available for IDs used often. The provider/model/effort selection becomes the session preference until changed. It survives later turns and session resume, including when you exit before running the next turn. An already active turn or admitted pending input retains its saved selection through recovery; new turns use the selected preference. This option does not write the configuration file; use /model SELECTOR save to make a selection the configured startup default.
- --update-model-cache
- Refresh every configured provider's model catalog. The refresh runs before model selection and session startup, regardless of option order. Combine with -l for a noninteractive refresh without starting a turn. A refresh failure stops startup and leaves the previous cache intact. Help and version exit without refreshing. Uses the same authenticated endpoints as /model cache.
- --effort level
- Set the session's reasoning effort for the next request, until changed. It survives later turns and session resume; an already admitted request retains its effort. It does not write configuration.
- --dotdir directory
- Use an absolute private application directory instead of $HOME/.snajpagent.
- --config file
- Read an absolute configuration path. This does not move sessions or the model cache. Unlike the default path, an explicit file must exist.
- --provider name
- Select a configured provider. On resume this explicitly changes the saved selection after local recovery, without rewriting past events. A missing name is an error. Combine with -m MODEL when changing models too.
- -v
- Set verbosity to the number of flags. New sessions start at 0 without flags; resume retains the saved level when flags are omitted. More than six flags is an error. The runtime command /verbose N sets the same process-local level. See DISPLAY.
- --color[=when]
- Set color to auto, always, or never. Bare --color means always. The value may follow a space or =.
- --no-color
- Equivalent to --color=never.
- --markdown
- Render supported model Markdown on terminals. This is the default.
- --no-markdown
- Show model Markdown literally.
- --resume [session-id]
- Interactively attach to a running native session, or resume a stopped durable session. Live attachment retains the owner's configuration, model and draft; startup overrides are not applied. A command-line follow-up is not submitted on attachment; a notice asks you to enter it after reconnecting. One-shot -e execution still requires a stopped session. Without an ID or -N NAME or --last, interactively select a saved session. Omitted configuration path, documentation roots, color, Markdown, verbosity and IRC options retain the session's saved values. Explicit options replace only the corresponding setting; --no-listen and --no-client keep those roles disabled. The exit hint supplies only the session selector and a nondefault --dotdir. Default ~/.snajpagent is omitted, including when passed explicitly. Config files are reread through their saved paths; credentials are not copied into session settings. Older sessions recover IRC settings from their last recorded topology; an unrecorded custom config path or documentation root must be supplied once. Live attachment keeps the already-running owner's settings.
An ID is a unique prefix of 1 through 32 lowercase hexadecimal characters. Displayed short IDs work in every session ID selector, including both pickers and /session attach. Ambiguous prefixes list matching full IDs. The writer lock prevents a second owner.
- -A, --attach [session-id]
- Connect a terminal to a live native session owner on Linux. Without an ID, or -N NAME, select a running session. Accepts a unique 1..32-character ID prefix and --dotdir; the owner retains its existing configuration and model selection. Ambiguous prefixes list full matching IDs. An occupied terminal is never taken over. Attachment skips provider setup, including when the owner started with an explicit configuration file outside its state directory. Requires terminal stdin, stdout and stderr, with a nonempty TERM other than dumb. TERM and STY each accept at most 255 bytes. Unsupported replacement terminals are refused before activation. Ordinary startup with an empty or dumb TERM, or a known width below 20 columns, retains direct-terminal operation, as do other hosts until their native backend is available. An existing native owner keeps its control keys when a terminal becomes narrower, including after an editor or pager returns.
- -N, --name NAME
- Give a new session a saved name, including an empty interactive session. With --attach or --resume, select an existing session by its exact, case-sensitive name. Choose one selector: an ID, a name, or --last. Names use nonempty UTF-8 without control characters, within the 16 KiB CLI argument bound. Quote names containing spaces in the shell. Names are labels independent of the IRC model nick set by -n. Duplicate names are allowed; ambiguous selection prints matching full IDs and requires an ID to choose one. Use /session name NAME in an existing session to set or replace its name.
- --last
- Resume the most recently used saved session. Requires --resume.
- -e
- Execute one turn and exit. The prompt must follow -- or arrive on non-terminal standard input. Network options are not accepted.
- -l [N]
- List all attached sessions, then all detached sessions, then up to N stored sessions and exit. N defaults to 10 and must be a nonnegative integer; zero shows running sessions only. Put N immediately after -l, before other options. Each status group sorts by latest saved activity, newest first; equal timestamps sort by session ID descending. The count applies to stored sessions only. Resume pickers and /session list include all saved sessions. Only --dotdir, --config, color options, Markdown presentation options, and --update-model-cache may accompany it. Markdown presentation has no useful effect on session-list output. The table has headings for session ID, name, model, turn count, status, last prompt and IRC. The last two columns share the remaining terminal width equally. Long cells end with an ellipsis; session IDs stay intact and expand beyond eight characters when needed to distinguish sessions. Widths account for wide Unicode characters. Redirected output is tab-separated with previews of up to 80 display columns per cell. Whitespace and terminal controls become spaces; saved text retains its full contents. IRC lists comma-separated entries: s/NICK@ENDPOINT for the hosted server first, then c/NICK@ENDPOINT for each outgoing client. NICK is the agent nick recorded for that endpoint; older snapshots fall back to the recorded session nick. Later recorded nick changes update the displayed identity, including sessions whose owner retained an older nickname snapshot. An unknown nick leaves only s/ENDPOINT or c/ENDPOINT. A dash means no recorded endpoints. Stored sessions show their last recorded topology and nicks. An IRC prompt shows ENDPOINT: MESSAGE, resolving saved event references to the latest message in that prompt. Ordinary prompts show the latest saved user text. An unnamed session shows a dash in the name column. The STATUS column shows attached while a terminal client is connected, detached while the session runs without one, and stored when only saved history remains. A suspended terminal retains its attachment. A connection still reserving or repainting its terminal becomes attached after activation. Listing queries the owner without reserving a terminal or changing saved history. Use --attach to reconnect to a running native session. --resume attaches if running and starts the session if stored.
- -h
- Print short help and exit. It must stand alone.
- --help
- Open the installed snajpagent(1) manual with man where available. If the viewer or manual is unavailable, print short help. On Windows, print short help. It must stand alone.
- -V
- Print build identity and exit. It must stand alone.
An endpoint is host, host:port, [IPv6], or [IPv6]:port. The default port is 6667. In networked mode initial chat text must follow --.
Terminal↑
Editing keys
The prompt word-wraps while typing, moving fitting words intact to the next row and hard-wrapping words wider than the terminal. Wrapping preserves the submitted text. Long drafts scroll within the editor to keep the cursor visible.
- Enter
- An empty or whitespace-only draft leaves its prompt line in the transcript and opens a fresh prompt. This is local: it starts no model request, sends no chat, adds no history or queued turn, and leaves active work and goals unchanged. Queue editing and confirmation prompts retain their own blank-answer behavior. For nonempty text, submit while idle. In rollout during model generation, interrupt the response and steer from its delivered prefix. A running command is handed back alive; it is not killed. In chat, send to the selected room. A model-nick mention steers at the next safe response/tool boundary without truncating generation.
- Tab
- With an empty draft, switch chat and rollout views, including offline. Otherwise, completion requires one of these prefixes and cursor positions:
- A draft beginning with / (not //), with the cursor in or at the end of its first word: complete a command name or numeric room destination. For example, /sta becomes /status followed by a space.
- In chat, a nickname word beginning with @ at the cursor: complete a current room member. The word can occur later in a sentence. This also works in the text of explicit /N TEXT and /all TEXT sends, including from rollout. The cursor may be inside the nickname or immediately after it, but not past its following space.
- A unique completion adds a trailing space (or reuses an existing one) and moves the cursor after it. Multiple matches expand their common prefix; a second consecutive Tab lists choices while preserving the draft and cursor. No match leaves the draft unchanged. Completion never submits, sends or queues text.
- Outside a completion, Tab queues a later local turn while active, or inserts spaces while idle. Thus Tab at the end of a finished ordinary message queues it; Tab at the end of /sta or @bu still completes that word first. Queueing remains local even in chat. Use Enter to send to the room or run a slash command. Commands are not generally queueable: /ro QUERY is a read-only future turn and /verbose applies immediately. In the queue editor, a queue action saves the replacement instead of adding a new item; completion still applies and empty text is rejected. Enter on a slash command runs that command and retains the original queue item. Use // for a literal leading slash or /ro QUERY to replace the item with a read-only query.
- Ctrl-J
- Insert a newline without submitting.
- Ctrl-C
- Leave the visible draft in scrollback, append literal ^C, start a new line, and open a clean prompt. With an empty active composer, also interrupt the turn and pause automatic goal continuation. While the engine settles the interruption, the prompt shows "Cancellation requested; waiting...". Further empty-draft interrupts keep that pending prompt on the same line. Cancelling a queue edit restores the active prompt and leaves the turn interruptible. Context preparation checks cancellation between journal events. The next idle prompt reflects the settled goal state; its active goal flag is cleared before the pause notice redraws it. Empty Enter afterwards stays local and leaves the goal paused. Ordinary queued IRC updates remain pending across this boundary and session resume; only a direct mention remains eligible for urgent automatic admission. Five consecutive presses within two seconds exit, including when terminal output is stalled; other input resets the sequence. A transient empty read from an open raw terminal does not end input; an actual terminal hangup or end-of-file still does. Ctrl-D on an empty draft or /exit while idle also exits.
- Ctrl-D
- Delete at the cursor when text remains. On an empty draft, interrupt active work and exit without waiting for the model or command to finish.
- Up, Down
- Move within the displayed rows of a wrapped or multiline draft, preserving the preferred terminal column, including in recalled prompts. At the first or last row, move to the start or end of the text. Up at the start selects one older prompt; Down at the end selects one newer prompt. History visits the session's entries newest first, then the global archive newest first. Returning to the newest local boundary restores the unsent draft.
- Ctrl-P, Ctrl-N
- Move directly through prompt history, including from inside a multiline draft.
- Ctrl-Left, Ctrl-Right, Option-Left, Option-Right
- Move to the previous word start or next word end. Words are separated by spaces, tabs or newlines, as with Ctrl-W. Common PC control arrows, Mac option arrows and Meta-b/Meta-f sequences are accepted, including Escape-prefixed and application-cursor arrows; the terminal must transmit them.
- Ctrl-R
- Start case-sensitive incremental reverse search over local session history, then the global archive. Press it again for the next match in that order; search does not wrap. Searches scan files in bounded chunks so editing and cancellation remain responsive even with a large archive. A navigation or search keeps fixed file boundaries; starting another one can see entries merged by programs that exited meanwhile.
- Ctrl-G
- Abort reverse search and restore the exact original draft and cursor. Escape accepts the current match without submitting it; editing and movement keys accept it before applying their normal action.
- Ctrl-A, Home
- Move to the start of the whole prompt, across wrapped rows and explicit newlines.
- Ctrl-E, End
- Move to the end of the whole prompt. Ctrl-A/Ctrl-E use the Control key on both PC and Mac keyboards.
- Left, Right, Delete, Backspace
- Edit the UTF-8 draft.
- Ctrl-U
- Clear the draft.
- Ctrl-W
- Delete the preceding word.
- Ctrl-L
- Redraw the current composer, preserving completed output and scrollback.
- Ctrl-Z
- Suspend after restoring normal terminal state; redraw after continuation.
During output, editing opens a live composer without stopping the provider. Visible output resumes after typing_pause_ms of inactivity. Bracketed paste inserts pasted newlines without submitting.
Direct prompts accept up to 1 MiB of valid UTF-8; steering and queued turns accept up to 256 KiB. The queue holds pending turns within 16 MiB; pending steering text is bounded to 8 MiB in a turn. Interactive EOF interrupts active work and exits, preserving the saved goal and queue arming state. The session remains resumable.
Views and prompt
Startup without network roles opens rollout; startup with roles opens chat. Empty Tab, /chat, and /rollout switch views at idle or during work, including offline. Runtime networking changes preserve view, draft, history and verbosity. Offline chat retains its transcript; Enter rejects unsendable input and restores the draft, without starting a private turn. Hidden-view output is buffered for display on return. Both views append to terminal scrollback; neither is a full-screen TUI.
All default prompts share an activity slot, a goal slot, a space, the local HH:MM:SS timestamp, and another space, in that order. The rollout prompt continues with provider/model/effort, a space, the context percentage space-padded to four columns including its percent sign, an optional pending-queue count (N), and its idle or active marker: › while idle and » while active. The percentage uses the latest comparable provider-reported input count and resolved input budget, rounded up. A full-request server count or completed response input usage supplies this value; local byte/token estimates are never used. It describes the last measured request, not cumulative session usage, generated output, or text/tool results added since that request. A fresh session with no submitted turns shows 0%. Unknown counts or budgets after a turn, including an incomparable state, show ?%. Without exact preflight, the value stays unchanged while another request runs. A provider/source/model/effort change or compaction invalidates incompatible measurements. Actual reported usage can decrease without local compaction.
The default chat prompt is HH:MM:SS OPERATOR_NICK@MACHINE_HOSTNAME : when idle. Its first two spaces reserve the indicator slots; one literal space separates them from the clock in every activity state. Submitted command lines remain in scrollback with their output below them. This applies in both views, during active work and at idle, including invalid commands. Each line retains the prompt label visible at submission. Every nonblank prompt, command, chat message, steering instruction or queued submission is painted at this submission boundary, before engine admission can wait. The ready composer stays hidden until the engine acknowledges the action, while later input remains live as retained type-ahead and appears in the next real composer. Empty or whitespace-only ordinary Enter is presentation-only: it creates no turn, command, queue item, chat message, goal event or provider request. Modal confirmations and queue editing keep their documented blank answer behavior.
The timestamp is local time captured once per composer. Editing, view changes, and status redraws retain it; submission or Ctrl-C cancellation ends that capture. The goal flag is independent of the activity slot. Tool calls take priority over model/provider work in the same activity column; idle reserves one space. The provider indicator runs during response, token-count, compaction and model-list operations, including their transport waits. A retained turn or retry delay alone does not animate it. The active marker still identifies an unfinished, cancellable turn, including one parked after a policy stop. Empty-draft Ctrl-C cancels that turn; Ctrl-C with a draft clears the draft first. Unused context digits also remain spaces. Indicators stop when their work unwinds; there is no separate interruption glyph. At verbosity 1 and higher, terminals also print one-shot wait lines naming the wait and its bound: managed commands report the tool and wait cap, provider responses report the model. Verbosity 0 and one-shot execution omit these status lines. Wait and warning notices are separated from following model output by one empty row, including literal rendering with Markdown disabled. The template and spinners are configurable under [ui].
Startup/resume headers, history headings and counts, and installed-update notices wrap at word boundaries using terminal display-cell widths. A word, path or URL wider than the terminal can still hard-wrap; information is retained rather than truncated. Banner output preserves an existing draft and does not change saved conversation text. Redirected diagnostics keep their original bytes.
Verbosity
At level 1, tool rows include rejected and otherwise unexecuted attempts with a short status reason. These private rollout rows appear live and in history; full arguments and output stay at their existing higher detail levels. Provider-hosted web searches appear as those same private rows: the search action and query, then the provider's terminal status and retained sources. They stay display evidence executed by the provider, never a local call, and a resumed session replays them.
Each model request includes a snapshot of the local verbosity, current rollout/chat view and effective tool-detail visibility, derived from the same presentation policy as the UI. The snapshot also identifies the terminal output cap and one-shot stdout/stderr routing. It describes available detail, not proof that the operator read every row or an untruncated result.
When tools are hidden, the model is guided to provide concise commentary before meaningful work and at progress/outcome checkpoints. Brief rows or previews need explanations of their significance; fuller traces need less duplicate per-call narration. Important decisions, blockers, risks and final outcomes remain part of the conversation at every level. This changes progress communication, not task scope, diligence, permissions, requested answer length or private reasoning rules.
A new request snapshot reflects live display changes, including changes made during a tool wait. Already-built or in-flight requests remain unchanged; display changes alone neither interrupt the model nor start a new request. Resume uses the newly running process's settings. The current snapshot supersedes old display hints in summaries. Chat view hides private rollout regardless of verbosity; selecting it never authorizes publishing local details to IRC or changing destinations.
Verbosity controls local rollout and diagnostics, not chat-message visibility or which view is selected. All participants see the same room messages and retained history at every level, including their own sends. Each message carries up to 4096 UTF-8 bytes (at least 1024 Unicode code points). Longer text splits at word boundaries when possible. The extended IRC wire limit is 8192 bytes including CRLF, advertised as LINELEN=8192; peers must support that extension for long messages.
- 0
- Model text in the rollout; timestamped room traffic in chat. Rollout milestones include the colored bullet lines • Compacted, • Goal set, and • Goal cleared, as well as updates, pause/resume, blocked and wording-lock changes. An automatic pause retains its reason in that single notice. Consecutive bullet notices form a group with one empty row around it, and none between members. Errors and local-command results appear in either view.
- 1
- Compact tool start and outcome rows, each carrying the same short call reference in the same column, with a short generic argument prefix. No output body.
- 2
- Add previews of up to 1024 argument and 512 output characters per invocation. Count terminal-safe Unicode code points; a dim […] marks display content that was cut or hidden by the current level.
- 3
- Full retained tool arguments, effective execution context and results, subject to existing capture limits and [tool] max_output_bytes.
- 4
- Add runtime decisions, token accounting, synced event identities and connection diagnostics. Session identity and explicit command replies are always complete.
- 5
- Add sanitized provider protocol and IRC commands. This exposes prompts, source, tool output, and model content.
- 6
- Add transport diagnostics.
Levels 4 through 6 are live diagnostics in visible rollout; they are not queued behind chat. Unseen retained work uses the current level on entry to rollout. Increasing the level does not replay visited history; lowering it stops now-hidden detail between bounded slices. A dim […] marks display content that was shown and then cut or hidden by the current level; it is display-only, and complete command output remains in the durable session journal. Redaction of known credentials is not a guarantee that source, prompts or tool output contain no secrets.
Presentation
Terminal model text uses color and compact Markdown presentation when enabled. Headings, lists, quotes, fenced code, pipe tables, inline code, emphasis, strikethrough, and links are supported. Pipe tables retain aligned columns and wrap long cells at word boundaries within the terminal width. Short columns keep their natural width; long columns share the remaining space while preserving whole-word minima. Wrapped grids separate logical records with horizontal rules. When those minima cannot fit, the table becomes labeled vertical rows, with a left border on every continuation line. Inline styles, header emphasis and column alignment remain active across wrapped lines. Redirected and durable text retains the model's exact bytes.
Some providers wrap web-search references in private-use citation blocks. On a terminal each recognized block is painted as one compact reference listing its distinct integer turns in ascending order, with consecutive turns coalesced into ranges, for example [cite: turn 0-2] or [cite: turn 1, 4]. The page index within a turn is omitted and a repeated turn appears once. The rule is independent of Markdown. Unknown, malformed, oversized or unterminated blocks are painted unchanged, and redirected one-shot output, provider traffic and durable events keep the original bytes. Citation fragments remain buffered across streaming deliveries, including when the closing marker arrives on its own.
Plain-text fences labelled text, txt or plaintext omit the language label. Other fence labels remain visible. Indentation and Markdown-looking content inside a fence are displayed literally, including pipe tables.
Top-level prose paragraphs are bulleted, with one empty row above and below, even while a paragraph is streaming. Adjacent paragraphs share that row; headings, lists, code, tables and prompts use the same paragraph boundary. All terminals wrap streamed model prose explicitly at whole-word boundaries. The renderer holds an unfinished word until whitespace or the end of an item, keeping attached punctuation together across provider chunks and Markdown style changes. Words that fit a row wrap as a unit; longer tokens stream in bounded pieces. Intervening messages flush pending text in order. The editable draft also wraps whole words when they fit a terminal row; longer words use terminal wrapping. These displayed wraps do not insert submitted newlines. Explicit newlines and leading whitespace remain part of the draft. Any prompt after non-prompt content has one empty row above it. Consecutive submitted prompts may be adjacent.
Provider-supplied and renderer-generated prose continuation lines align two spaces below the paragraph text. These presentation newlines and spaces can appear in terminal copies, but are not part of stored model text. Code-block whitespace and the provider's original text are unchanged. The live prompt is displayed immediately on cursor-capable terminals, without waiting for a quiet period. Before new output it is erased and then redrawn with the unsent draft intact; work-in-progress prompts are not left in scrollback. Submitted input remains in the transcript.
Inside GNU Screen, a terminal resize can make Screen redraw recently visible lines. An outer terminal that saves those redraws in its own scrollback may show repeated completed lines even when snajpagent did not send them again. Screen can also redisplay after recovering from blocked terminal output. Its full-screen erase before redisplay can cause an outer terminal to save the old viewport into history before receiving the replacement viewport. The outer terminal's alternate-screen and status-region scrollback behavior can affect whether such redraws enter its saved history. This is separate from the session's durable transcript and from Screen copy mode.
Timestamped IRC chat messages do not receive the synthetic paragraph bullet; genuine Markdown list items retain their structural bullets.
Ordinary chat operator nicks use cyan and model nicks blue. A message mentioning this viewer's accepted room-local operator or model nick highlights its timestamp, sender nick and message separator in bold magenta. A sender mentioning only its own nick does not trigger highlighting. The message body and its Markdown styles are unchanged. This display rule applies to live and retained chat; it does not change mention admission or turn history into fresh instructions. Color-disabled output has no escapes.
Terminal file transfers↑
Remote terminal mode (snajpagent remote)
On the POSIX workstation, wrap the connection with the native client:
snajpagent remote ssh -t target snajpagent
snajpagent remote mosh target snajpagent
The workstation wrapper owns file selection and the download destination. Use /session detach in the agent to return to the shell while work continues. Reconnect to the running agent through the wrapper:
snajpagent remote ssh -t target snajpagent --attach SESSION_ID
snajpagent remote mosh target snajpagent --attach SESSION_ID
Omit SESSION_ID to choose from running sessions. Keep the workstation wrapper outside SSH when reconnecting so file transfers use the current workstation terminal.
The syntax is snajpagent remote [--config PATH] [--dotdir PATH] [--] [COMMAND ARG...] . Arguments after the client options are passed literally to a child PTY; shell expressions require an explicit shell command. With no command, the client starts SHELL, or /bin/sh when SHELL is unset. Resize, terminal signals and the child's exit status propagate through the wrapper. When the child exits, the wrapper drains ready output and restores the terminal, including when a background descendant retains the child terminal. Ordinary agent options and prompts retain their existing meaning outside this subcommand.
For a directly wrapped mosh command, the client inserts an option separator before the hostname so application options such as --resume reach the remote program. Put Mosh options before the hostname. Full option names, short options and their values are recognized; abbreviated or unfamiliar options retain Mosh's own parsing and require an explicit -- before the hostname. An existing separator is preserved. Argument text remains literal.
Client mode reads only the workstation configuration's terminal section. It starts no agent, provider, tools, IRC connection, media preparation or session journal. Configuration lookup uses --config, then --dotdir/config.ini, then ~/.snajpagent/config.ini. An explicit --config path must be absolute. Client mode does not use SNAJPAGENT_DOTDIR. A missing default file uses client defaults. Downloads default to ~/Downloads; override them on the workstation, for example:
[terminal]
download_dir = ~/Incoming files
Use an absolute destination or a path beginning with ~/. The client creates the final directory if its parent exists, resolves it before receiving, and preserves existing files through collision-safe names. Actual landed paths appear in download receipts in the remote conversation, with the prompt and editable draft restored afterward. Receipt paths also enter the saved tool result for model-triggered sends. Transfers keep the conversation visible. The native wrapper displays a single progress line with direction, byte count and percentage, then restores the prompt. Byte progress reaches 100% before final verification; the receipt confirms completion.
The wrapper negotiates a fast byte-stream client over SSH. Stock Mosh carries terminal screen state instead of a byte stream: with the same outer wrapper, the agent negotiates a short title-state frame and keyboard acknowledgement channel. File bytes, checksums and receipts use the existing terminal transfer protocol over that channel. The wrapper consumes the title frames before forwarding terminal output, so protocol data stays out of the conversation, draft and scrollback. Mosh transfers use smaller blocks and take longer than the SSH route; they use the current Mosh session, with no separate file connection. The executable name, path and TERM value do not select a mode.
Keep the outer workstation wrapper active across SSH hops and screen reattachments. An inner native wrapper probes for an upstream native client and relays to it, preserving workstation endpoint ownership. A local screen can contain the workstation wrapper. For two nested screen backends, place an inner native wrapper between them to relay passthrough to the outer client. GNU screen passthrough keeps transfer frames out of its display and scrollback; its settings remain unchanged.
Alternative workstation client: trzsz-go
The Go trzsz wrapper remains usable with the target's /receive and /send commands. On macOS:
brew install trzsz-go
trzsz --dragfile ssh target
If Homebrew's Python trzsz package is already installed, run brew uninstall trzsz first: both packages provide trz and tsz. The trzsz-go package also supplies the trzsz wrapper; --dragfile belongs to that wrapper. Set DefaultDownloadPath in the workstation's ~/.trzsz.conf for automatic saving. The target needs no trz or tsz executable. Native-client availability and automatic outbox delivery use the native wrapper; with trzsz-go, explicitly request /send PATH and remove any corresponding pending export afterward.
Receive from the workstation
Type /receive for native selection, then enter one regular local file path; an empty path, Escape or Ctrl-C cancels. With current native wrappers and receivers, drop one regular workstation file into the composer, or paste its absolute path. Literal, shell-quoted and bracketed-paste paths are accepted without shell expansion. The file becomes an unsent attachment; existing draft text and active work are preserved. Ordinary text, missing paths and input to other programs pass through unchanged. Update both endpoints for native drops; use /receive with older receivers. Drop files one at a time.
With trzsz-go, select files or drop regular files into the composer. A stock Go-client drag sends Ctrl-C followed by trz; that Ctrl-C can cancel a typed draft or interrupt active work. Save needed draft text first. Directory mode (trz -d), directories and zero-byte uploads are rejected.
The receiver verifies the complete batch before retaining and listing files as unsent attachments. Review /attachments and submit them with the intended private prompt, queued turn or steer. Receiving a file alone submits no message. Preparation failure removes new files from that batch and retains existing unsent attachments. A batch has at most eight files; retained files are at most 256 MiB each, and prepared image content uses the existing 12 MiB input budget. Native uploads use acknowledged blocks of at most 1 KiB to preserve input through GNU screen, including screen boundaries hidden behind a relay. The Mosh title-state mode limits blocks to 256 bytes, then divides each wire line into short checked title updates. A stale or repeated update cannot append the same bytes twice.
Send to the workstation
Type /send PATH or ask the model to send a file through its send_file tool. For example:
/send ./report.md
/send /home/user/report with spaces.pdf
/send asset:0123456789abcdef0123456789abcdef
Paths are literal, absolute or relative to the session's current working directory. Symlinks and directories are rejected. Empty regular files can be downloaded. An asset:ID must identify a file already accepted by this saved session; its retained bytes are verified before sending. Downloads stream in bounded blocks and do not inherit attachment-size limits.
The client receives the basename. Retained assets without a saved display name use their asset ID as the basename. Completion requires the client's digest acknowledgement and final EXIT. Native receipts identify actual landed paths; file bytes stay outside model tool output. Transfers intentionally send the selected file bytes to the workstation without tool-output redaction.
Pending downloads and reattachment
The model's send_file journals an export in the existing remote session before attempting delivery. An interactive session probes for the fast client, then tries a screen-state handshake when the fast probe has no reply. A positive reply permits immediate delivery through the selected mode. One-shot runs and detached sessions report “queued; not delivered yet” and emit no file-transfer frames. An unsupported interactive client leaves the export pending and reports queued intent; no file data starts before the screen-state reply.
Wrapped reattachment over the fast byte-stream route discovers the client and delivers pending files at an idle render boundary. A Mosh reattachment keeps previously queued exports pending; request their delivery again from the active session. Unwrapped attachment leaves exports pending. Only acknowledged completion removes an export. Interrupted or uncertain transfers remain pending, so a reconnect may produce a collision-safe duplicate if a prior client landed the file but its completion acknowledgement was lost.
Each export has a stable ID, source path, byte count, timestamp and SHA256. A changed or unavailable source remains pending and reports an error rather than exporting different bytes. Ask the model to inspect its download_queue with action=list, remove one ID with action=remove, or use action=clear. An optional reason records why an export was cancelled. Queue removal changes only export intent; source and completed workstation files remain intact. Read-only mode permits inspection and rejects send_file and queue mutations. Exports have no automatic age expiry.
Cancellation and recovery
Transfers take exclusive terminal input until completion, cancellation or error. Cancel the workstation selector or press Ctrl-C during the transfer. Ordinary commands and steering are unavailable while the transfer owns input. Per-frame I/O waits are bounded at 20 seconds; a missing wrapper returns a transfer error. Transfers through GNU screen use 1 KiB DATA blocks to keep input frames within its terminal input burst capacity; Mosh screen-state uses 256-byte blocks and checked, retransmitted short title frames. Other connections use up to 64 KiB blocks. An interrupted or lost screen frame fails without publishing an incomplete file. The native client removes unpublished temporary files on transfer failure; a process crash can leave a hidden .snag-upload-ID staging file. Other clients can leave partial files: inspect them before use. Only acknowledged completion reports delivery.
The terminal lease restores modes and display on success and failure, then replays keyboard bytes received after the final EXIT. Source files are unchanged. If the source's size or modification timestamp changes while being sent, the transfer fails. Interactive terminal transfers require POSIX hosts. One-shot send_file queues its export for wrapped attachment. Read-only model turns reject send_file.
Commands↑
Commands normally occupy a single submitted line. /ro, numbered sends and /all also accept multiline message text. Prefix text with // to submit a literal leading /. Commands can be submitted when the application displays a prompt. A foreground slash command completes before the next app command is accepted; typeahead is held without a second ready prompt. Model selection changes the next response in an active turn and remains selected for later turns and resume. Effort-only selection applies to later requests after a safe response boundary. Configuration, cache refresh, compaction, retry and delete run at safe request boundaries; their accepted intents in an established session survive restart and retain admission order. Configuration editing and cache discovery before the first work item preserve lazy session creation. Repeated requests for the same pending control coalesce. Delete still requires explicit confirmation. Commands typed while another foreground control runs are considered after it returns. A policy-stopped turn remains parked while commands run; provider continuation still requires task clarification. Presentation, steering and tool yield act on current work. Text received at an idle rollout prompt just before work starts retains its future-turn meaning; following commands remain responsive.
Public response text is saved incrementally before display. Recovery continues the original turn from saved inputs, public output and tool results. Started calls without durable results are reported as outcome unknown; unstarted calls are reported as not run. Saved process output remains available, while old process handles cannot be reused. Terminal drafts and submissions awaiting engine acknowledgement are not yet durable work; the submitted-text or control acknowledgement follows event-log acceptance.
Session and model
Informational command reports use the configured pager on an interactive terminal. This includes /help, /status, /state, goal status, /queue, /session lists, /history, /model lists, /context, /effort, /verbose, /banner, /attachments, /voice devices, /names, /topic, /nick, /steering and /server status, including their aliases. Each command opens one report; /state combines session status, goal status and goal actions in that report. Commands that display an updated report after a setting change also page it. Quit the pager to return to the composer. Brief acknowledgements, errors, view switches and ongoing model output remain in the normal terminal display. During active work, keyboard /verbose queries also stay in that display for an immediate response; idle queries use the pager.
With the default pager = on, set PAGER in the environment where snajpagent runs, for example export PAGER='less -X'. Over Mosh, set it on the remote host; use the pager's scrolling and search keys to read reports taller than the terminal. Disabled or unset paging and a pager that cannot start use direct output. Noninteractive CLI output stays direct. Paged reports contain plain text; /history retains its turn labels and displays Markdown source.
- /help, /?
- Show commands grouped by settings, models/context, goals/queue, sessions, files/media and network chat, followed by usage notes and essential keys. Square brackets mark optional parts, a vertical bar separates alternatives, and uppercase words are values to supply. Required goal and queue subcommands are listed explicitly. Interactive help uses the configured pager; when paging is disabled, unavailable or fails, it prints directly with ordinary foreground text and bold section headings where color is enabled. Long lines wrap at word boundaries. Pager text and redirected output are plain text. /state goal help shows the same goal forms separately. /goal is an alias for /state goal.
- /status
- Show session, selection, queue, verbosity, capacity, token accounting, requested network roles and actual connection state.
- /session, /s
- Print the full current session ID followed by running sessions. A new unsaved session is identified as not yet saved; listing does not start a model turn.
- /session list, /session l, /s list, /s l
- Print the current session ID and all saved sessions, with column headings and their attached, detached or stored status, in that order. Each group shows the latest saved activity first. This does not attach to or resume a session.
- /session attach ID, /session a ID, /s a ID
- Switch the terminal to a live session on Linux. A unique short ID is accepted. The source stays attached and accepts input until the destination accepts the connection. Input read alongside destination acceptance stays with the source. Queued source frames finish before new input enters the destination; refusal, timeout or an unavailable owner before acceptance leaves the source usable. After acceptance, the client waits for the source relay to consume queued input and display acknowledgements before releasing that connection. A connection failure during this final drain is reported and ends the terminal connection. The destination publishes its live attachment after the frontend confirms that drain and binds to it; new input and audio activation wait for this acknowledgement. With voice active, this command carries its public conversation and requested on or muted state. Voice requests use the same command and confirmation rules. The microphone pauses while history is prepared. The destination uses its own audio/provider settings and credentials, and starts after the source has retired its media. Copied originals keep their source identities; queued work and late source results stay with the source session. The destination's previous voice conversation is replaced by the carried conversation, followed by current destination context. Preparation failure keeps the source attachment and resumes its voice when still enabled. Before the transfer offer is sent, /voice off cancels preparation immediately. Afterward, a successful switch leaves voice off in the destination. Mute changes applied before source release determine the destination's initial microphone state. An incompatible private-protocol version is refused before acceptance. Use a compatible binary to attach to an older running owner.
- /session name NAME, /s name NAME
- Save or replace the current session's name, including while work is active. The remaining text is the name; shell-style quotes are unnecessary here. The name is retained across detach and restart. Use -N NAME with --attach or --resume to select it from the CLI.
- /session detach, /session d, /s d
- Return to the shell while the native session continues. Terminal loss has the same lifetime behavior. Use /exit to stop the session, or --attach to reconnect. File attachment commands /attach and /detach keep their existing meaning. An unfinished file exchange is cancelled when its terminal disconnects; reattachment restores ordinary input and verifies the new workstation client.
- /state
- Show session state including the goal and its actions: the /status report, the durable goal with status and blocker, and the available goal actions. /goal is an alias for /state goal.
- /history [N]
- Show up to N recent turns, oldest first, including unfinished, failed and interrupted work. Default: 1. Available during active work. N is a nonnegative decimal count; the bounded scan may show fewer turns or a marked partial oldest turn. Zero shows the total turn count without scanning. Completion counts cover only shown turns. Older original events remain available through read_session_history. Uses the same replay as resume_history_turns without changing configuration or model context.
- /model [list]
- Read the local provider/model catalog. No provider request is made. On an interactive terminal the displayed catalogue pages through the configured pager. The catalog is loaded at startup; ordinary requests, context meters and status use that in-memory copy. Model commands check for a changed cache file and load it only when changed. Missing or invalid replacements preserve the last valid copy. Selecting a numbered row through the model tool uses its listed snapshot; an explicit cache refresh obtains a new one. Startup also loads the catalog when resuming with the saved model. If a saved context selection requires unavailable catalog facts, the idle meter shows ?%. Use /model cache or change the context selection before submitting work that needs those facts.
- /model cache
- Refresh every configured provider through its authenticated model endpoint, replace the cache atomically, and report the new timestamp without printing the catalogue; use /model list to display it. A normalized API path ending in /backend-api/codex uses /models?client_version=0.146.0; other providers use /v1/models. There is no automatic catalog refresh or protocol fallback. Failure leaves the previous cache intact. During active work the refresh starts at the next safe request boundary. See Model selection and context.
- /model number, /model #number
- Select an exact displayed provider/model/effort row.
- /model selector [save|s]
- Select model, model / effort, or provider / model / effort. A final save or s writes the selection to the active configuration. Typed model IDs need not exist in the cache. Wrap a component in matching single or double quotes for an ID containing /, as with -m; a numbered row avoids selector ambiguity too. Selection is accepted during active work and changes the next response in that turn, then later turns until changed. An in-progress streaming request is interrupted; the new provider request receives the retained conversation and tool results, including still-running command handles. It persists across session resume without a save suffix. The final save suffix also works with numbered rows. A bare model name uses the highest recognized effort in its configured or cached effort list (or the first unrecognized effort if none are ranked), then the cached default, then the currently selected effort. This interactive selection differs from the startup -m option, which uses the first configured or cached effort. Two components mean model/effort; include all three components to name a provider explicitly.
- /config
- Open the active configuration with EDITOR. Valid changes are reloaded; invalid content remains on disk while the prior runtime configuration stays active. Only IRC components changed since the last successful file load override current process state. An unrelated edit preserves runtime additions/removals; editing the client list replaces that list. Startup networking flags are not reapplied over deliberately edited fields. Only affected owners restart. Failed restoration reports actual remaining roles. Reload preserves the process's current verbosity and view. During active work it starts at a safe request boundary. The editor owns terminal input while managed processes and IRC continue to be serviced. If the program dies during editing, resume reloads the saved file without launching the editor again. Presentation and changed network roles apply on reload; provider connection, authentication, capacity and tool defaults apply to later requests or tool invocations. Already launched processes retain their own settings. The current turn's working-document paths, read-only restrictions, parallel-call contract and automatic retry budget remain bound to that turn. Model and effort selections may change at a safe response boundary in the same turn. Update checking is a startup action; update settings apply on the next program launch.
- /effort [level]
- Show or set reasoning effort for the next request, including during active work. The session retains it until changed, across later turns and resume. Values are provider-defined; the host accepts structurally valid names rather than enforcing a fixed list. The value default resolves to medium when used for a turn.
- /context [default|max|N [s|save]]
- Show or set the context window for the selected model. A bare /context reports the selection with the window, its output reservation, the effective client percentage and the auto-compaction budget. default uses the configured model-limit window, or the provider's advertised normal working window when no context override is configured, max uses the advertised maximum, and an integer N selects an explicit total window of N tokens; it is refused above the advertised maximum or when the output reservation leaves no input. The selection overrides a configured model-limit context for the session, is recorded in the session log, and is restored on resume. The output reservation, client percentage and proactive compaction stay derived from the selected window, so shrinking it compacts before the next request. During active work the command ends the current response and rebuilds the turn under the new window, retaining conversation and completed tool results.
- Append s or save to a numeric selection, for example /context 900000 save, to also save context_window_tokens in the selected provider/model's exact [model-limit PROVIDER/MODEL] section of the active configuration. Other settings and file permissions are preserved. The saved window applies to new sessions using that model and to /context default in this process. A numeric selection without the suffix changes only the current session. Invalid values or a configuration save failure leave the session selection unchanged. Save requires a number; edit the configuration with /config to remove a saved override.
- /nick [NICK]
- Show the agent and operator nicks, or rename the agent nick. The new nick is shared through IRC so other clients see lead/worker renames; the config file keeps the configured default. Names follow IRC nick rules; invalid names are rejected and a server refusal arrives as a server notice.
- /steering [mentions|all|clear]
- Show or set which IRC traffic reaches the model as steering: only mentions, or every message. A session override wins over configuration until cleared; clear returns to configuration. Without configuration the default admits mentions only.
- Show, set, or clear the session banner: a short work cursor the model maintains for itself. The banner is restated in later requests, so it survives compaction and is present at the next turn. Keep it brief and specific to the ongoing work.
- /verbose [0..6]
- Show or set this process's current verbosity immediately, even during active work (Enter or active-turn Tab). Subsequent output uses the new level; already visited detail is not replayed. Idle queries use the configured pager; active-turn queries and all level changes stay in the terminal. It is local feedback, never chat or model input. The current level is saved on orderly exit and restored on resume unless explicit -v flags select another level.
- /compact
- Compact model context at idle or at a safe active-turn request boundary. The request survives session resume and waits when no complete context boundary is available. The full local event log is retained.
- /delete
- Confirm with the displayed session prefix, then permanently remove the session through private trash staging. Reopening an interrupted deletion finishes cleanup, including after the event log has been removed; unexpected trash content is preserved and reported. Shared prompt history is not erased. Commands at the confirmation prompt remain commands rather than confirmation text. Ctrl-C cancels deletion; pending controls can then run. Opening the queue editor cancels deletion and retains the queued item. /exit preserves the session and leaves it available to resume.
- /exit
- Preserve the session and exit. Active work remains available for same-turn continuation on resume. Ctrl-C instead records deliberate cancellation.
Yielding tool waits
- /yield
- Return the active managed-tool batch to the model immediately. The result identifies the operator as the source of the handoff. Started commands keep their handles, pending input and captured output; unstarted calls are marked not_run. The handoff sends no signal and does not restart commands. A previously requested termination continues in the background and is reported as pending. The model can inspect the results and choose its next action.
Available in both views while a managed-tool wait owns the foreground, including termination waits and prompt transitions, even without a steer prompt. Outside a tool wait the command reports that none is active; it does not queue a future yield or interrupt a provider request.
Attachments
- /dictate
- Start one microphone interval, up to 60 seconds, using the configured capture device. Requires a raw interactive terminal. Uses the selected provider's credentials and transcription defaults; [audio] can override them. The MIC ON prompt appears before capture starts. Enter finishes; Escape or Ctrl-C cancels. Capture also finishes at the limit. The microphone closes before transcription is requested. While transcribing, Escape cancels; a request already sent may have been billed and is never retried automatically. Keyboard editing remains available. Submission and queueing are held until dictation finishes; the transcript is inserted at the cursor, preserving typed text. Review it before pressing Enter to submit. Temporary PCM is bounded, discarded on completion/cancel, and never saved as an attachment or journalled. Returned API usage is displayed and retained in an audio_usage journal event with its provider, model and operation. It is separate from coding token counters and model input; unreported usage remains unknown. A journal write failure is reported and never triggers another paid request. Device loss, rerouting or capture backlog overflow stops dictation and discards the sound. Native terminal detach, loss, switching away or suspension also cancels dictation. A late transcription is not inserted after the attachment changes; a request already sent may still have been billed.
- /voice devices
- List available capture and playback device names without starting capture. Set capture_device and playback_device in [audio] to an exact listed name; empty values select system defaults. Ambiguous duplicate names are rejected. Stop local audio before reloading configuration or preparing attachments.
- /voice on
- Start a voice conversation using the selected provider and its credentials. Credential loading and refresh run in the voice connection worker while ordinary input and coding work continue. The microphone stays off during startup; /voice off cancels a pending credential wait or connection attempt. Direct Codex subscriptions and codex-lb use native subscription voice; compatible BYOK providers use their Realtime API. A codex-lb gateway must use its /backend-api/codex base for native voice; a bare /v1 base does not select it. Realtime follows the configured URL: HTTPS uses a certificate-verified secure WebSocket, and HTTP uses an unencrypted WebSocket. HTTP sends credentials and voice control traffic without TLS; use HTTPS or a secure tunnel outside a trusted network. Native voice resolves credentials at startup and uses the same credential for call setup and attachment. A configured gateway key needs no separate login. Native audio playback runs independently of caption timing; interrupting a reply clears queued speech while keeping the connection ready for the next reply. Native ASR and generated captions update as transcript segments arrive, with a separate preview for each speaker. Finalized transcripts retain their utterance identities; partial captions do not submit coding work. Conversation continues while accepted coding requests are queued or running. Ask for work status or quieter narration in ordinary speech. Narration preferences apply to spoken updates; work cancellation and microphone mute have separate controls. The session interface can read files, inspect current work, steer an exact active turn, queue a separate task, request turn cancellation, select a cached model, or change voice capture mode. Stale turn targets are refused without changing another turn. File changes and command execution run through the coding agent. Executable tasks can be described directly, including work outside the CLI's slash-command help. Relayed requests preserve the original transcript and mark the voice interface's interpretation separately from working-model results. Each result keeps its original request identity. The live connection tracks up to eight outstanding handoffs. At capacity, native voice reports that a new handoff was not submitted; public Realtime temporarily withholds the session tool. Conversation and accepted work continue. A slow result consumer delays delivery without stopping voice. [audio] settings are optional overrides. The prompt identifies microphone capture. Before capture starts, voice restores prior public working history, speech and interface outcomes from the saved session as identified historical observations. The microphone stays off until the initial history and fresh-state transport writes finish. Reply completion during this wait leaves device activation pending. Large histories can delay this stage; /voice off cancels it while coding work continues. Saved actions and audio are not replayed. Current speech already belongs to the live provider conversation. Microphone audio goes to the provider's media servers and replies are synthesized speech. Use a headset to limit speaker feedback alongside software echo cancellation. Keyboard editing and ordinary submission remain available.
Capture and incoming reply audio stream concurrently through bounded PCM rings. The current endpoint uses server-detected speech turns. Reply generation can start from committed audio before its separate transcription completes. Delegations wait for their matching finalized transcript. The interface uses separate text requests with the selected session provider and model, the session working directory, and existing effective instruction paths. These requests add ordinary provider usage and run independently of media and the coding request. The session owner validates tool actions and records their outcomes. Queued work and accepted steering are acknowledged separately from completed coding results. An unavailable transcript prevents action admission. Final transcripts and provider-reported usage are retained separately from coding token counts; raw microphone PCM is not journalled. Settled interface text requests also record provider/model identity, reported cached tokens when available, retries, and elapsed, response-ready and first-text times. Missing observations remain unknown. These client timings include authentication, transport and retries; they do not isolate provider prefill time. The records are available through session history. Turning voice off records the settlement of an outstanding interface request and marks its response as discarded. Received public output and pending handoff identities remain in history; shutdown executes no returned tool calls and leaves accepted coding work under the existing session controls. Interface text requests use their own stable prompt-cache and transport-affinity identity, separate from working-model requests. Connection renewal retains the outstanding helper request. Provider error records retain validated error codes, categories, available messages and reported context/input counts through the same secret-filtered history path. Validated native call HTTP rejections also retain status and accepted Retry-After hints. Partial or invalid metadata cannot publish partial failure facts. Missing counts remain unknown. A generic error message does not establish the voice model's context capacity. Recognized temporary failures renew automatically with the pacing described below. Classified capacity rejections reduce history before replacement. Other errors stop voice for review before restarting with /voice on.
While voice remains enabled, the text interface retains its ordered dialogue and tool outcomes across spoken handoffs. Its command/help prefix stays in place, and each ordinary request receives a fresh session-state snapshot, including requests that continue after a tool action. Public history and returned replies filter registered secrets, including credentials reflected in provider output. This protection follows credentials used by interface requests when a credential file changes during voice use, including tokens refreshed inside the HTTP transport and responses retained during shutdown. History filtering precedes JSON quoting and excerpting, including for old records whose text predates a configured secret. Saved records stay unchanged. Before its first delegation, the text interface reads working inputs, public output and outcomes from session history. It appends newly recorded working events before subsequent ordinary requests. This catches up in bounded steps while coding work continues; each observation identifies its session and source sequence. Pending audio delivery does not hold up this text-history catch-up. Both native voice observations and text history include command lifecycle, goal wording/lock/cancellation changes and model-selection changes. Recorded native voice transcripts and deferred spoken outcomes are also appended before ordinary text-interface requests, with their source speaker and identities. When the provider reports exhausted input, or request serialization reaches its staging limit, the interface summarizes older context and retries the pending request. If that prefix reduction is insufficient, recovery includes the active dialogue as well. Summary requests declare no action tools. A rejected summary input is retried in smaller source fragments with interruptible pacing. Summary adoption is recorded before replacing the live history; a failed summary leaves the source history available. These requests add provider usage separately from coding totals. Turning voice off releases the live text history. The next text-interface connection restores prior public voice dialogue and actual outcomes from the saved session as historical observations before handling new input. This also applies after session reopen. Saved actions and audio are not replayed.
Native capacity recovery keeps the microphone off while existing helper work settles. It summarizes the verified public history in bounded pages through a fixed sequence boundary, carrying the summary between tool-free requests. The complete summary must be smaller and saved before replacing any live history. The replacement connection receives that summary, the unsummarized tail and fresh session state. Original records and accepted coding work remain available; summary work does not replay speech or admit historical actions. Saved summaries pass through the current credential filter before restoration or reuse by another recovery. Turning voice off cancels maintenance and retains received public output without adopting a partial summary. Received public output is saved within the session's journal record limits even when it exceeds the live voice message budget. A refused, empty, executable, oversized or nonreducing summary stops voice with its history intact. Byte staging limits provide no model-window estimate.
Read-only access uses get_cwd, list_files, read_file and grep with the same path rules as coding tools. Spoken model selection uses the existing cached catalog; catalog refresh remains a foreground session operation. Interface request failures leave the voice connection active. Oversized spoken results are marked excerpts; the saved session retains the full result. Voice has no separate memory directory or documentation configuration. Initial instructions include the same command and keyboard reference as /help. The voice interface can enter slash commands and explicit replies to UI prompts through the same input queue as the keyboard. The typed draft is preserved. An acknowledgement reports admission; the command's normal output reports its result or asks for confirmation. While voice is on, acknowledged host/help text, errors, warnings and logical prompt changes enter its public history. UI-local command feedback follows the same session-owner history path. Registered secrets are filtered; terminal frames and clock/spinner redraws are not copied. Both voice histories consume the retained observations, including after reconnection. This path does not reconstruct UI output from before voice activation. Voice remains serviceable while a confirmation is open. Existing permissions still apply, including keyboard-only consent where explicitly required. A full input queue refuses admission. Input from an ended attachment is rejected, including queued input not yet dispatched. The voice interface can read older dialogue, controls and results with the same paged session-history tool used by the model. History inspection keeps existing work running and admits no new task. Session inspection includes paged queue identities, read-only flags and current text previews of up to 512 bytes. Pages default to 20 entries and support up to 100; continuation follows the immutable enqueue sequence, so removing earlier entries preserves the cursor. Each page reflects current queue state. Clipped previews are marked, and inspection leaves queue arming and execution unchanged. While voice is enabled, new model output, tool outcomes and control changes are forwarded in journal order as background context. Larger records continue in bounded fragments. These observations do not start model work or request speech. Textual host context, including initial and refreshed snapshots, is filtered for configured secrets before delivery to the voice provider. Context snapshots identify the current session-state sequence separately from the historical sequence they have verified. History catches up in bounded steps; an incomplete historical view is marked and continues on later service steps. The latest speaker excerpts include their journal sequences for older-detail retrieval. Updating this view leaves the working model and queue unchanged. Voice activity persists the session even before the first model turn. Voice interface actions appear as tools labelled "voice:" at verbosity 1 and above; verbosity 2 adds the ordinary argument and result previews. Each action's invocation is saved before execution, followed by its acknowledged outcome.
Playback initially waits for 60 ms of sound. A confirmed mid-reply gap increases this prefill by 20 ms, up to 200 ms; eight clean replies reduce it by 20 ms toward the initial value. Storage and microphone chunk size remain fixed. An audio-end marker releases short replies and tails immediately. Prefill waiting is bounded at 200 ms after the first queued sample; prolonged provider or network stalls can still produce silence. Received captions do not prove the corresponding sound was heard. Speech interruption flushes queued playback and reports a conservative played position to the provider. Native media waits up to 60 ms for a missing packet to arrive out of order. After that deadline, a known burst of lost packets is concealed together; each missing packet does not add another wait. Once capture is active, queued context or result messages do not pause native microphone forwarding: its media transport is separate from the control socket. Duplex capture reduces speaker echo using the samples actually rendered as its reference. The microphone remains available during replies. Echo reduction depends on the devices and room; headphones can help when speaker audio still reaches speech recognition.
- /voice mute, /voice unmute
- Pause or resume microphone forwarding. Unmute discards old capture samples, including a partial processing frame, and resets the echo filter. Native voice also clears encoder lookahead at mute and preserves the muted interval in subsequent audio timestamps. Audio already transmitted cannot be recalled. Speaker output and coding work are independent of microphone mute. The prompt shows starting or muting until the device owner applies the change. Duplex startup gates forwarding until old capture is discarded; a mute during startup cannot later release buffered speech. A rapid mute/unmute still clears the old input before fresh capture is admitted.
- /voice off
- Stop capture, playback and the voice connection. Accepted coding work stays with the existing session owner. Use the normal coding controls to interrupt that work. An advertised connection lifetime ending or a validated temporary provider-service error starts paced, cancellable renewal. DNS, connection and timeout failures before an HTTP response, abrupt WebSocket EOF and stalled outbound writes also renew. Native call setup renews recognized temporary HTTP rejections after validating the complete error body. Context-capacity rejections first reduce the retained history; policy rejections stop voice. Accepted Retry-After hints delay replacement; /voice off cancels that wait. The microphone stays off during replacement authentication and history restoration. Requested mute state, the interface helper and accepted coding work survive; old audio and native call results are discarded from the transport. Late helper outcomes keep their original source and reach the new connection as historical context. Voice-off also cancels a pending renewal. Device loss, overflow, authentication failure, unclassified HTTP or policy rejection, TLS failures, WebSocket close frames, protocol-integrity errors and other unclassified connection failures stop voice. Review the diagnostic and restart explicitly with /voice on. Native terminal loss, detach or job suspension also stops voice. Final transcripts and previously accepted coding work remain saved; unaccepted voice handoffs are discarded. Ordinary reattachment requires an explicit /voice on. An intentional /session attach while voice is active carries its conversation and requested mode through the attachment exchange described above. When the provider supplies a session expiry time, a warning appears within its last minute. Final notices already received are saved on shutdown and connection failure. Turning voice off does not accept a handoff that has not yet entered the coding queue. A journal write failure is reported; unsaved notices cannot be recovered. Repeated handoffs for an accepted utterance retain the original queue and turn identity. Queued or running work remains pending; completed, failed, interrupted or cancelled work returns its recorded outcome without another execution. On explicit start, voice receives excerpts of the latest finalized input and generated reply transcripts, the latest voice handoff's recorded state/result, and the active-turn identity and queue count. Excerpts are limited to 8 KiB each and marked when shortened. Task-state changes refresh that context. Historical text does not resubmit work or replay speech; a result from an older connection is context, not a function output for a new call.
Partial input captions and generated reply captions update above the composer when supplied by the provider. They show recent text and never alter the draft or authorize coding work. Final transcripts use the durable session record. Native transcript streams take precedence over turn-level previews, so the accumulated text is neither replaced nor duplicated. The rollout labels finalized microphone transcripts as "You [voice, ASR]" and voice-model replies as "Voice model [generated]" at every verbosity level. The labels remain with the text in scrollback. Partial captions distinguish the same speakers above the composer. Stopping voice clears these previews. Generated captions may precede audible playback and do not indicate that the words were heard.
Realtime voice is a development checkpoint. Device lifecycle and portable runtime qualification remain in progress. Native fixture tests do not qualify physical devices or live APIs.
- /play asset:ID
- Play the first 60 seconds (or until EOF) of an accepted audio asset in the current session, using the configured playback device. Other file paths and unaccepted attachments are rejected. Linked decoding pulls bounded stereo PCM chunks into the shared output ring; it does not buffer the entire selection. Playback opens no microphone or API connection. Keyboard input remains usable. Device loss or decoding failure stops playback; already played samples cannot be rolled back. Backend latency can delay the end of audible sound. Native terminal detach, loss, switching away or suspension stops playback; the accepted source asset remains in the session.
- /play stop
- Interrupt file playback. Dictation and file playback share one local device owner and return busy when the other operation is active.
- /receive
- Receive workstation files as unsent attachments through the trzsz wrapper. See Terminal file transfers for setup, drag behavior, verification and limits.
- /send PATH
- Send one regular file or retained asset to the workstation through the trzsz wrapper. The remainder is a literal path, including spaces; no shell expansion, quotes or globbing are interpreted. See Terminal file transfers.
- /attach path
- Prepare an image or retain a regular file for the next private message, queued turn or steering input. The entire remainder is a literal path, including spaces: no shell expansion, quotes or globbing are interpreted. Relative paths use the current working directory; symlinks are rejected. Recognized documents, audio and video retain their format; other non-image bytes are kept as a generic file reference. The input prompt shows the attachment count.
- /attachments
- List unsent attachments by number, format, size and retained asset identifier.
- /detach number|all
- Remove an unsent attachment from the composer. This does not remove media already accepted by a previous turn. The source file is never modified.
Enter in rollout submits text and attachments together; Tab or /queue preserves them with that queued input. Steering carries the same parts through restart and compaction. Automatic goal and IRC turns never consume a private draft. Chat messages remain text-only; staging an attachment does not broadcast it. Queued text edits preserve that turn's accepted attachments. Use /detach before submission to change the selection; unsent drafts are not restored on restart. The configured model/provider receives image bytes only when that input is sent. Other file types carry a retained reference, not their bytes, until explicitly inspected. A recognized extension is routing metadata, not proof the file is valid or supported by a decoder. At most eight attachments may be staged; non-image source files are limited to 256 MiB each. Current image bounds and provider requirements are listed under Image inspection.
Read-only queries
- /ro query
- Run one read-only query, including multiline text. Every request carries the complete native tool catalog. A read-only query permits inspection and supported provider web-search work; a call that would perform a side effect returns a read-only tool result and performs none. Normal execution permissions apply on the next ordinary turn. In networked mode the query opens rollout view and sends no IRC reply. During active work, Enter and Tab queue the read-only query as a future full turn. You can also use /queue /ro query to run it after the current turn. Ordinary steering inside a read-only turn cannot lift its restrictions. Empty queries are rejected; //ro is literal text. One-shot argument and stdin prompts also support this prefix.
- /retry
- After a turn fails, continue it from retained conversation and tool results. The original prompt is not duplicated and completed tools are not replayed. Read-only mode is preserved. Continuation of an unfinished turn keeps its frozen provider/model/effort; a new retry turn uses current next-turn settings. Available in both views and after session resume, until another turn starts. For an unfinished active turn, request continuation at the next safe boundary. Pending input that failed preparation can also be retried. Explicitly cancelled turns stay cancelled; retained completed or uncertain tool calls are never blindly executed again. Paused queues and goals remain paused. Ordinary turns retry failures automatically up to max_turn_retries additional times (default five), then display the manual retry hint. A repeated identical definitive provider rejection fails at once instead of replaying. Automatic retry counts and their original limit survive restart. A successful actionable response or manual retry after terminal failure starts a fresh budget. Active goals recover ordinary failures without an attempt limit. Same-process recovery retains live command handles; after restart their outcomes are settled without reusing old handles. Policy stops survive restart and require clarification.
Input timestamps
New prompts and steers carry separate host-generated timing metadata in model context. The original user text and its authority remain unchanged. received_at records acceptance of the submitted input; first_context_at records its first admission to an outgoing model request, including an attempted dispatch that fails. These UTC timestamps remain fixed through retries and replay. Queue delays can make first admission later than receipt; an edit uses its accepted revision's receipt time. Metadata is explicitly labelled as host text, and does not claim provider acceptance or model processing. Older missing timing data remains unavailable rather than being inferred from replay time. A submitted editor line retains its original local prompt clock; queued dispatch renders a new normal prompt with the dispatch-time local clock. UTC provenance and the displayed clock describe different events.
Goals
An active goal continues across ordinary turns until complete, paused, cancelled, or blocked; there is no default per-goal-turn working or step budget. Queued operator turns run before the next automatic goal turn. Ordinary errors retain the active goal and schedule recovery with interruptible backoff, without an attempt limit. Policy stops and provider refusals pause automatic continuation and require clarification. Turn-only cancellation also pauses automatic continuation. Quitting preserves unfinished work and its goal for resume. Session resume preserves saved stops. Goal status changes update the prompt indicator before their notices are shown, including pauses, completion and blocking. Clearing a draft in the interactive composer leaves its active goal running; Ctrl-C on an empty composer interrupts the turn and pauses that goal. Goal controls accept whitespace around the command word. Extra wording after a control is rejected; use quotes or /state goal set to start goal text with a command word.
- /state goal, /state goal status
- Show the durable goal with its actions. Queued prompts run before automatic goal continuation, without fresh goal reminders throughout those turns. A retained unarmed queue cannot be bypassed: use /next or explicit goal start/resume to arm it. An open queue editor prevents draining. /goal is an alias for /state goal.
- /state goal text
- Start a goal, or reword the unfinished goal. Quote the text or use /state goal set when its first word is a goal command.
- /state goal set text
- Explicitly start or reword a goal.
- /state goal pause
- Pause automatic continuation at the current turn boundary.
- /state goal resume
- Resume a paused or blocked goal.
- /state goal lock|unlock
- Freeze the goal's objective against model changes, or release it. A locked goal refuses the model's rewording, blocking and cancelling, in the tool before dispatch and again in the store wherever the event names the model as actor, while finishing or resuming a locked goal stays allowed, so it can still reach a successful end; the operator's own commands remain available.
- /state goal complete
- Mark the current goal successfully completed.
- /state goal clear|cancel
- Cancel the current goal and stop automatic continuation. An in-progress turn finishes normally; use Ctrl-C to interrupt it. The cancelled goal remains visible in status and history. A new goal can be started afterwards.
- /state goal help
- Show the complete goal grammar.
Queue
- /queue, /q
- List future FIFO turns.
- /queue text
- Queue a future full turn. During active work it is armed to follow the current turn; idle additions remain paused until /next. Tab during active work is the direct equivalent for a draft.
- /queue clear|pop, /q c|p
- Remove all queued turns or the newest one.
- /queue n delete, /q nd
- Delete queued item n.
- /queue n edit, /q ne
- Reopen queued item n in the composer.
- /next
- Durably arm the queue. Start the oldest entry at idle, or after the current full turn when work is active. Arming survives exit and restart.
Network
All network commands are discoverable and usable at idle and during provider streaming, silence, retries, token counting and running tools. They are local controls, not model input, steering, cancellation or future turns. Endpoints use the same syntax as startup flags; an omitted endpoint defaults to localhost:6667.
- /server, /server start [ENDPOINT], /server stop
- Report hosting, start it, or stop only the hosted listener and accepted peers. Outgoing connections remain unchanged. Repeating the current listener is a no-op; replacing it requires an explicit stop first. Bind failure leaves the session and unrelated endpoints usable. Hosting publishes only matching public room history, never private rollout or another room's history.
- /connect [ENDPOINT]
- Add an outgoing connection, up to 16. A duplicate is a no-op, including while reconnecting. Connecting is asynchronous and is not reported as a completed join.
- /disconnect [ENDPOINT]
- Remove the named outgoing endpoint, or all outgoing endpoints when omitted, including connections created by model IRC tools. Hosting is unchanged. Stop reconnect attempts and report discarded unsent bytes; do not transfer them to another endpoint. Already accepted model input remains eligible after final disconnection. Old model sends are not redirected into replacement rooms; they receive normal not-performed tool results.
- /chat
- Select the public chat transcript, including when offline.
- /rollout
- Select local model activity.
- /topic [text]
- Show the IRC snapshot, or change the topic in the selected room. Live room policy decides authorization: mode +t requires the local operator to have op status; without +t any joined member may change it. Text is at most 280 bytes, without newlines.
- /names
- Show numbered destinations, selection, endpoints, members, and channel-operator flags.
- /N [text]
- Select numbered destination N. With text, send there once without changing selection. Numbers remain stable for this process and are not reused after removal. A number names the same endpoint across reconnects: when an endpoint returns with a changed advertised room, or otherwise republishes its topology, the selection follows that number instead of staying unavailable. With a sole destination the same syntax remains valid.
- /all text
- Broadcast once to the destinations present at submission. This does not enable a persistent broadcast mode. A later connection does not receive it. An unavailable destination is reported locally; messages are not redirected.
Models and providers↑
Model selection and context
The provider, model, and effort are session preferences. A typed selector without a provider stays on the currently selected provider. An omitted effort uses configuration. The value default for the model resolves to the compiled default; default for effort resolves to medium. Operator-typed /model and command-line -m send other nonblank bounded values without checking a model catalog. The model's select_model tool is available when [agent] allow_model_change=true and uses only entries in the current cache.
Use /model cache to discover models and advertised capacities. Listing the cache is offline; there is no TTL and no dependency on an installed Codex. Cached limits and counting capabilities and observed ceilings apply only to their original provider, URL, protocol, and model. An explicit [model-limit PROVIDER/MODEL] overrides advertised capacities. Missing capacities remain unknown.
The direct DeepSeek catalog currently supplies model IDs without capacity or reasoning-effort metadata. Its Flash API model ID is deepseek-flash. Configure its documented 1,048,576-token total context explicitly; this remains a configuration override rather than an advertised cache value. For an existing deepseek provider, add:
[model-limit deepseek/deepseek-flash] context_window_tokens = 1048576
Select it with snajpagent -m deepseek/deepseek-flash/high or /model deepseek/deepseek-flash/high. The direct API accepts xhigh as an alias for high. Refreshing the catalog visits all providers in the loaded configuration, including those other than the selected provider. Reload edited configuration with /config or restart before refreshing or selecting a newly configured provider.
Token accounting prefers exact provider preflight when supported, then uses completed request usage, source-bound observations, or conservative byte bounds. These methods are not interchangeable exact counts. /status shows the capacity source and accounting method. Catalog refresh resets the count-capability probe and preserves observations only for an identical source.
/status also reports prompt-cache reuse and speed: input tokens split into cached and uncached, the same totals for the current process beside them (program totals restart with the process while session totals survive a resume), and tokens per second for the running turn and for the most recent response.
Prompt-cache routing uses a stable session/provider/model identity. Input timing, steering boundaries, recovery notices, response corrections and process closure travel as labelled host conversation data. Current goal, display, command, banner and work-note facts form a labelled snapshot at the request tail. Once sent, that exact snapshot remains before its response in later requests; unchanged snapshots are not duplicated and the latest snapshot supersedes older facts. Fixed execution and IRC policy remains separate, including through gateways that collect system and developer instructions. Completed history retains its projected bytes across turns until compaction. Instruction, model, tool-schema and compaction changes can alter the reusable prefix. A changed snapshot can require one new cache write; later requests extend that new prefix. Providers determine actual cache reuse. The labels identify host metadata, not new operator requests or approvals. Commands still require collection before a final answer or goal completion.
Automatic compaction uses the provider threshold and resolved input capacity; setting the proactive threshold to zero does not disable capacity handling. With a known hard input window, the default automatic threshold is 90% of that window. The prompt percentage shows measured input usage of the selected window. An exact outgoing count refreshes the composer when the response attempt starts, before provider output. Completed responses refresh it from measured usage. Compaction and recovery rebases replace the measured context; the meter shows unknown until a compatible measurement exists. Status retains a historical observation only with an explicit label when provider source, model, effort or compaction no longer matches. Context-selection advice uses the same compatibility check. A matching last observation is not a measurement of later unsubmitted tool output. Streaming output enters a later request when its input is measured. The percentage can fall after a completed compaction; each new attempt announces itself. A long turn can compact again after a completed response and later tool progress when its next request exceeds the hard budget. A recovery rebase clears the previous usage measurement until a new response supplies one. Native compaction is used when enabled. Set native_compaction=false to summarize through ordinary Responses; the application wraps the returned summary text in its stored message format. Direct ChatGPT/Codex also falls back once to Responses summarization when its native endpoint is unavailable; failed native requests may retry with a smaller complete-history source within the bounded attempt. Failures that remain report an error and retain the previous context. Manual /compact is accepted at idle and during active work. It reports when compaction starts, waits for a complete context boundary, finishes, or is interrupted. An idle request with no new context reports that it was skipped. Whitespace after the command is accepted; additional arguments are rejected.
An active goal keeps retrying failed compaction with paced, interruptible waits, including after eight consecutive failures. Each retry can attempt compaction again. Ordinary turns retain the eight-failure bound; fresh input or /compact starts another attempt. Successful compaction clears that failure count, including compaction performed after a turn.
A summary survives a model, provider, effort or account change. A switch by itself does not start compaction. When the selected binding needs a smaller context, that binding keeps the covered boundary, leads its source with the retained summary as plain text, and compacts only the uncovered tail. The previous binding is not selected again to prepare the new binding's request. Failed or cancelled turns and process exit retain the selection and compaction progress; resume continues from that durable state without replaying completed tools. When a provider rejects a request for capacity and no uncovered events remain, the merged summary is condensed once under the selected binding and the turn is retried; the covered boundary and predecessor chain stay unchanged.
Automatic and manual compaction announce that context is being compacted. During compaction, Ctrl-C with an empty draft interrupts preparation or the provider request; a nonempty draft is cleared first. Cancelling a turn does not cancel a later manual compaction. Text entered during idle compaction is saved as a future turn and runs afterwards; during active-turn compaction it remains steering. Interrupted or failed compaction retains the previous context. Provider errors leave the interactive session open; use /compact to retry. Policy rejections remain on the policy path even when their metadata also contains a context-limit code; they do not trigger capacity recovery. Accepted requests survive a process restart. Compaction changes model context and preserves the append-only local log.
The versioned session checkpoint is a record in the same journal. It stores both recoverable session state and the materialized provider conversation, with the summary and only the uncovered continuation after successful compaction. On resume, the final complete journal record locates the latest checkpoint; recovery validates that record and the recent suffix. Format-2 and format-3 sessions with a long history require one initial scan to gain an indexed checkpoint, without rewriting old history. Checkpoints from before the download outbox feature resume with an empty outbox. A damaged referenced checkpoint stops recovery with an error rather than silently starting a full-history replay. Explicit requests for old history still read those older records. The session lock, metadata and retained media are not alternate checkpoint files. Cache recovery uses the same checkpoint and recent suffix whenever a provider view has been saved. Repeated request preparation and reopening preserve one copy of the compaction or recovery prefix. Retry notices continue from the saved attempt count; older cached views rebuild from their retained events. Journal growth is limited by available storage and representable file offsets, rather than a lifetime byte or event quota. Existing sessions that reached the former closure reserve can continue with an updated executable; keep their journals intact. Per-record bounds and durable append validation still apply. Cancelled request preparation can be retried or resumed from a saved checkpoint; partially prepared context is rebuilt from retained events without duplicating the conversation.
If compaction leaves only instructions, outgoing requests include a labelled host continuation so gateways can keep a nonempty conversation input. It adds no user instruction or approval and does not change saved goal state. Failed compaction attempts are recorded before recovery so the session can reopen.
Login
Common options such as --dotdir, --config, and -m must precede the login/logout command.
- login [provider]
- Authenticate a configured provider or choose one of the codex, meta, openrouter, openai, and custom presets. Existing environment authentication remains explicit; there is no fallback to another provider or authentication method. Login does not change the default model in an existing configuration.
- --openai-device-auth
- Use the native device flow of a direct Codex provider. Open the printed verification page and confirm or enter the one-time code. ChatGPT device login must be enabled in the ChatGPT account or workspace. Waiting is bounded by the issued link lifetime; Ctrl-C cancels.
- --meta-device-auth
- Use the native OAuth2 device flow of a Meta provider (https://api.meta.ai with or without /v1). Open the printed verification page and confirm or enter the one-time code. MUSE_AUTH_URL and MUSE_CLIENT_ID override the Meta issuer and client identifier. Waiting is bounded by the issued link lifetime; Ctrl-C cancels.
- --with-api-key
- Read one API key from stdin for explicit login. No key argument is accepted. Noninteractive first setup also requires -m MODEL before login.
- login status [provider]
- Report configured credential sources and local state without contacting a provider or refreshing tokens. Provider entitlements require a remote check.
- logout [provider]
- Remove only the selected provider's stored login. If omitted, select the configured default provider. Environment variables and the Codex or Meta CLI credentials are not modified; other provider logins remain intact.
Configuring a provider probes the native compaction endpoint once and records the answer as native_compaction in that provider's section: a route that does not serve /responses/compact (for example a local gateway answering 502) is saved as disabled instead of being attempted on every later compaction. An endpoint that does not answer at all leaves the existing setting unchanged.
Automatic setup never consumes piped task input or runs for execution, resume, session listing, or a missing explicitly specified configuration. Invalid existing configuration produces an error and remains on disk for correction.
Configuration↑
The file is UTF-8 INI syntax. Lines beginning with # or ; are comments; there are no inline comments or general shell-style quotes. Unknown sections, keys, duplicate scalar keys, invalid values, and files over 64 KiB are errors. Booleans are true or false. The numeric forms 1 and 0 are also accepted for boolean settings. Command-line values override file values. A missing default configuration uses built-ins; an explicit file must exist. Configuration must be a regular file, not a symlink. Section names below go in square brackets.
agent
- provider
- Provider for new sessions. Default: first configured provider.
- model
- Model for new sessions. Default: default, which resolves to gpt-5.5-2026-04-23.
- reasoning_effort
- Default: default (resolves to medium). The value is provider-specific; common choices include low, medium, and high.
- max_goal_prompt_bytes
- Goal wording limit, 1 through 1048576. Default: 262144.
- max_turn_retries
- Additional automatic retries per consecutive failure sequence, 0 through 4294967295. Default: 5 (six attempts including the first). Zero disables ordinary automatic retries. A successful actionable response resets the count. The configured limit is captured at turn start, including an explicit /retry. Active goals ignore this limit and retain unlimited recovery. Paused and blocked goals keep their state while ordinary prompts retry.
- allow_model_change
- Permit the model to use the select_model tool to refresh its model catalog or change provider, model, or reasoning effort. Default: false. When disabled, the tool is omitted from provider requests and calls are rejected without changing the session selection or refreshing the cache. Set allow_model_change=true in [agent] to enable it. Operator /model and command-line -m remain available. The current configuration applies on launch and resume.
- read_agents_md
- Discover global and project AGENTS.override.md or AGENTS.md instruction-file paths before each turn. Default: true. The model chooses relevant files to read with tools; contents are not injected. Explicit -d roots are independent of this setting. See FILES.
- auto_update
- Check and install binary updates in a background thread on ordinary launch. Default: true in official stable binaries; false in development binaries whose version has a commit suffix. Set true to opt a development installation in. Ordinary local builds have no updater unless explicitly enabled at build time; setting this option cannot enable a capability omitted from the executable. Help, version, login and session-listing commands do not run the updater. Configuration changes apply to the next launch's update attempt.
- update_url
- HTTPS update URL matching this installation. The built-in URL uses the publisher's latest or latest-dev directory and the exact build target; universal macOS and legacy Linux remain their original variants. Stable letter-suffixed versions, such as 0.99.8b, follow the plain numeric version on the latest channel. Commit-suffixed development versions use latest-dev; an installed stable version never takes a development update. The updater reads the URL with .json appended, then downloads the immutable version URL it describes. Treat a URL override as a publisher trust decision. Only HTTPS transfers are allowed; verification checks the declared SHA-256, size, version and embedded product, target and publisher identity. Trust comes from HTTPS and its certificate roots, not a separate release-signing key.
An update does not delay ordinary startup or provider work. The existing process continues running its original version. After successful installation one local, distinct banner gives restart advice and a changelog URL. No automatic restart, modal confirmation or recurring reminder occurs. Script output is unchanged; the banner uses stderr. The running version remains unchanged until restart, including the version shown by the current process.
The executable and its directory must be writable and owned by the running user; shared-writable, privileged and hardlinked files are rejected. The updater resolves the running executable, including a launch through a symlink; a symlink substituted at the resolved install path is rejected. Prefer a user-owned standalone install. Do not enable updates for package-managed installations. The updater never elevates privileges. Failed checks/downloads leave the old executable in place and ordinary work continues; there is one attempt per launch. The bundled resolver is asynchronous; a custom libcurl without asynchronous DNS disables updating to preserve exit responsiveness. HTTPS proxy and SSL_CERT_FILE settings also apply to updates; provider API credentials are never sent.
A private sibling .EXECUTABLE.update-lock coordinates updater instances. A partial .EXECUTABLE.update-new is discarded under that lock on the next attempt. Unix installs use same-directory rename. Windows keeps the old executable as .EXECUTABLE.update-old.exe when the mapped file cannot be replaced directly. If interrupted between the renames, run that retained executable with auto_update enabled to restore the missing original name, or rename it back while the agent is stopped. Recovery uses the same lock as installation. Do not delete it while another instance is using it. There is no session-format rollback promise: keeping an older executable does not undo newer session data.
provider NAME
Ordered named sections may be repeated with distinct names. The spelling [provider] is not accepted. Names contain 1 through 63 ASCII letters, digits, dots, underscores or hyphens. Names have no privileged meaning. New sessions use the first declaration unless an explicit provider preference or --provider NAME selects another. Unqualified model changes stay on the current provider.
- base_url
- HTTP or HTTPS API base without query or fragment. Default: https://api.openai.com.
- api_key
- Credential source: ${ENV_NAME}, a double-quoted literal with JSON escaping, or an unquoted file path. A bare environment name means a file, not a variable. Relative files use the config directory; leading ~/ expands the user's home. Files may have one final LF/CRLF and may be symlinks for rotation. Literal secrets require a user-owned configuration with no group/other permissions; mode 0600 is the normal choice. Absent means use the provider's managed stored login; empty is invalid.
- auth
- Authentication method: api_key (default) uses the explicit source or, when absent, the provider's stored key; and chatgpt uses stored Codex OAuth credentials with automatic refresh. ChatGPT auth requires the exact base https://chatgpt.com/backend-api/codex and no api_key setting. meta uses stored Meta subscription OAuth credentials with automatic refresh while the issuer honors it. Meta auth requires the exact base https://api.meta.ai/v1 and no api_key setting. These tokens are never sent to a custom endpoint. Existing bearer-auth Codex-compatible proxies use auth=api_key. Missing/bad explicit sources never fall back to a stored login.
- connect_timeout_ms
- Connect timeout, 1000 through 120000. Default: 30000.
- idle_timeout_ms
- No-data timeout, 1000 through 600000. Default: 120000.
- request_timeout_ms
- Whole-request timeout, 1000 through 3600000. Default: 1800000.
- auto_compact_input_tokens
- Proactive input threshold. Default: auto. Auto uses 90% of the selected model's effective hard input budget, rounded down with a minimum of one token, after output reservation and safety limits. When one source advertises both a normal working window and a larger maximum context, the normal window supplies that hard budget; the maximum remains visible as a client ceiling that can be selected explicitly. A source that publishes only a maximum still uses it. An explicit context_window_tokens rule selects another context instead. The threshold follows model/context changes and learned lower ceilings. If capacity is unknown, it uses a labelled 120000-token fallback. An integer from 1 through 4000000 selects a fixed threshold; zero disables proactive policy, not the hard capacity guard. Existing explicit numbers remain fixed: remove the setting or use auto to enable scaling. /model and /status show the effective threshold and its mode without changing configuration.
- exact_token_count
- Input-token preflight policy. auto prefers the provider's Responses count endpoint and falls back only when it is definitively unsupported; true requires exact preflight; and false disables it. Default: auto. An absent endpoint (HTTP 405/501, or a route-level 404 without a semantic error code) uses the automatic fallback; strict counting still fails. Authentication failures and named model errors do not disable exact counting. Counting sends model input, tool schemas and input configuration. Response-only controls and prompt-cache routing remain on the generation request, including native and Responses-based compaction. OpenAI Platform and current llama.cpp servers provide full-request Responses counting. Direct ChatGPT/Codex, OpenRouter and other Responses servers without that endpoint continue using measured response usage and bounded overflow recovery; no local tokenizer or byte-to-token conversion is substituted.
Proactive compaction uses exact preflight or the last compatible measured input usage. Compaction covers complete older event prefixes; each completed summary records its covered boundary and later compactions start at that boundary, not at the beginning of the journal. Previously presented image payloads are omitted from summary requests. Native compaction output can retain user messages together with its summary; the complete output is accepted within the 12 MiB byte bound. New uncounted additions may exceed the token limit. If a provider rejects a request before any output, the next attempt retains the current turn and controls but omits the earlier provider transcript, compacted conversation, images and completed tool calls. The model can use bounded read_session_history and read_tool_output tools to recover a needed fact without replaying those calls.
History pages walk verified journal records backwards without replaying the full session. Use the returned exclusive next_before_seq cursor for older records. The optional nonempty event_types array filters exact event names; omitted or null selects all types. An uncached older boundary uses record-aligned search; consecutive pages reuse one in-memory position. After locating the boundary, a page scans up to 4 MiB plus at most one complete record under the existing record limit. Counts describe only that scanned range, excluding boundary lookup. A filtered page may be empty with scan_complete=false; continue with its cursor rather than treating it as the end of history. Checkpoint records show coverage metadata instead of saved transcripts; provider continuations and encrypted compaction payloads are omitted. History records are filtered with configured secrets and the request's credential before their details are shortened. Stored records remain unchanged. History and goal listing report an output-budget failure, not a false empty/end page, when no matching entry fits. Reduce history detail or increase the configured tool output budget as indicated.
This recovery survives interruption and resume, including a turn without an active goal. If that same active turn later needs another compaction, its recovery boundary remains valid even after the turn's start leaves the retained event overlap. For a current IRC update, the request also retains the referenced room messages from the durable journal; unrelated historical room traffic stays available through history tools rather than being replayed. A rejection of the minimal recovery request fails that attempt rather than starting another compaction loop. Known hard-budget overflow may compact one tractable prefix first. The rebuilt request retains that summary and is counted again; only if it remains too large does it take the same history-oriented recovery path. Later compactions recognize the covered active turn even after its start leaves the retained checkpoint overlap. Irreducible current input fails clearly; the durable history is retained. Normal same-model compaction remains available, and a provider/model/account switch retains the saved covered boundary and portable summary where it fits.
- native_compaction
- Use the provider's native compaction endpoint. When false, use an ordinary Responses summarization request instead. Default: true.
An unavailable native compaction endpoint falls back once to Responses summarization. Authentication failures do not trigger this fallback, and the configured provider/account and hard context guard stay intact.
- parallel_tool_calls
- Request native multiple function calls from this provider. Default: true. Set false for a provider/model that cannot batch calls. This does not disable local yielded-command concurrency or change provider/account selection.
- leading_instructions
- Set for an endpoint whose chat template only accepts instruction roles at the start of the conversation, as llama.cpp templates do. The trailing host continuation is then sent in the labelled user transport slot instead of a developer message. Default: false.
- openrouter_referer
- Optional printable ASCII HTTP-Referer value.
- openrouter_title
- Optional printable ASCII X-Title value.
audio
Optional audio preferences. Dictation and voice inherit the selected provider; explicit values below override its defaults.
- provider
- Name of a configured provider. Empty uses the selected coding provider. Credentials, endpoints and timeouts follow that provider. A codex-lb key authenticates to the gateway; direct Codex uses the subscription login.
- listen_model
- Audio-capable Chat Completions model for listen_audio.
- transcribe_model
- Transcription model for transcribe_audio, /dictate and public Realtime input captions. The default is gpt-4o-transcribe. Native voice supplies its own transcripts.
- realtime_model
- Conversation model for /voice on. Empty selects gpt-live-1-codex for native Codex voice or gpt-realtime for public Realtime.
- speech_model
- Speech-generation model for speak_text. Set together with voice.
- voice
- Voice identifier. Empty selects cove for native Codex voice or marin for public Realtime. A configured speech_model can use this value for speech generation.
- capture_device, playback_device
- Exact device names from /voice devices. Empty values use system defaults. These settings alone authorize no capture or playback.
Subscription audio uses subscription access; BYOK audio uses that provider's API billing. Failures do not switch billing routes. Unsupported operations are reported explicitly. File-audio calls carry the selected sound or synthesis text, without coding history or executable tools. Realtime voice also receives its attributed coding results through the existing owner. A failed or interrupted request is not automatically retried; a request already sent may have been billed. Media contents cannot receive textual secret redaction.
model-limit PROVIDER/MODEL
Rules may be repeated with distinct provider and model selectors. Set provider-wide defaults with [model-limit PROVIDER], patterned rules with [model-limit PROVIDER/PATTERN], and exact rules with [model-limit PROVIDER/MODEL]. The first slash separates the provider; remaining slashes belong to the model. Set at least one numeric field below, reasoning_efforts, or steering:
- context_window_tokens
- Total context tokens, 1 through 4000000000. This explicit rule overrides a catalog's larger maximum-context selection as well as its normal working-window value.
- max_input_tokens
- Maximum input tokens, 1 through 4000000000.
- max_output_tokens
- Maximum output tokens, 1 through 4000000000.
- image_tokens
- Provider-documented per-image input ceiling in tokens, 1 through 4000000000, for the matched local model. Local image-request bounding uses it on any provider route when exact counting is unavailable, superseding the provider-generic resized-image budget it otherwise applies.
For example, DeepSeek documents a 1,024-token ceiling per image for deepseek-flash:
[model-limit deepseek/deepseek-flash] image_tokens = 1024
Execution fields may also override the corresponding [tool] defaults for a matched model: default_yield_ms, max_wait_ms, max_parallel_commands, default_timeout_ms, max_timeout_ms, tool_output_bytes, and output_cache_bytes. Their ranges and meanings match the tool settings below, except tool_output_bytes is the per-model name for the model-context result ceiling configured globally as max_output_tokens. The effective set is frozen with each admitted turn and shown to the model.
- steering
- IRC mid-turn admission, honored only in an exact PROVIDER/MODEL entry: the default mentions admits a room message as steering only when it mentions the accepted model nick; all admits every room message. Non-admitted messages remain ordinary room context; local submissions and turn-wake behavior are unchanged. Provider-wide and patterned entries do not contribute to a model's steering, and there is no global setting.
Rules merge per field: provider-wide, matching patterns in file order, then exact model. The star matches zero or more characters over the whole local model name. Duplicate targets/keys are errors. Unspecified fields inherit catalog metadata or the global tool settings. Explicit values supersede the catalog, but output headroom and learned backend ceilings still constrain usable input. A default yield must not exceed its effective maximum wait, and a default timeout must not exceed its effective maximum timeout.
reasoning_efforts sets an ordered JSON array of effort names for the model picker, for example:
[model-limit example/fast-model] reasoning_efforts = ["max", "high", "low", "none"]
The list overrides the provider's advertised efforts, including when discovery supplies none. It uses the same provider/pattern/exact precedence as the numeric fields and may be the only setting in a rule. Aliases match their local names. Lists contain 1 through 32 distinct, nonempty UTF-8 strings of at most 63 bytes; ASCII whitespace, control characters and slash are rejected. Names are provider-defined. The program forwards selected names without provider-specific translation; the existing effort shorthand default still means medium.
Numbered selection follows list order. Startup -m without an explicit effort uses the first listed effort. Interactive /model MODEL chooses the highest recognized level, or the first listed name when all are unfamiliar. Explicit /effort and model/effort selections remain usable even when absent from the list. A configured list also applies to typed models before cache discovery; listing an undiscovered model still requires a model-alias declaration.
The array remains in configuration; /model cache stores only provider metadata. Reload configuration with /config or start a new process to apply an edited list. Invalid lists reject the reload while preserving the active configuration. Existing turn/session effort preferences remain unchanged until selected again.
model-alias PROVIDER/NAME
The local name uses the provider-name syntax. The required model key is a literal upstream model ID. These declarations define ordinary provider-local models with independent limits; several names may route to the same upstream model. Targets never resolve recursively. Selection, sessions and limits use the local name; provider requests use the upstream ID. Upstream names do not add another limit-rule matching tier.
For example, give one provider-local model a smaller context budget:
[model-alias openai/review] model = gpt-5.5 [model-limit openai] max_output_tokens = 16000 [model-limit openai/review] context_window_tokens = 128000
Select it with --provider openai -m review or /model openai/review/high.
ui
- color
- auto, always, or never. Default: auto.
- markdown
- Render supported terminal Markdown. Default: true.
- resume_history_turns
- Turns to show on resume. Default: 1. Accepts any nonnegative decimal count; large counts show all turns. Zero shows only the counts. Replay ends with shown, completed and total session turns. Unfinished, failed and interrupted turns include their saved public output. Completed refusals and goal turns are included. History before compaction remains available. Network chat also restores its room history.
- typing_pause_ms
- Output pause after an input edit, 0 through 5000. Default: 500.
- pager
- Interactive command-report and /cat paging: on (the default) runs $PAGER when a report is displayed on a terminal and the variable is set; off prints command reports directly and disables /cat; any other value is a command template. A template may contain %s where the quoted file path goes; without it the path is appended. Reports use a private temporary file, while /cat passes the requested file to the pager directly. Voice-submitted reference commands display their reports directly. Successful paged command reports remain available to voice as UI observations; /cat contents remain operator-only. The command runs only in interactive mode with terminal input and output; a command that cannot start leaves the report to the normal display, while /cat reports an error. Example: pager = less -R %s.
- prompt
- Data-only prompt template containing exactly one {chat:TEXT}, {rollout-idle:TEXT}, and {rollout-active:TEXT} case. Ordinary fields are {provider}, {model}, {effort}, {operator}, {model_nick}, {session_name}, {host}, {context}, {mode}, {queue}, {hour}, {minute}, and {second}; optional status fields are {goal_spinner} and {activity_spinner}. The old separate provider/tool placeholders are removed pre-1.0; explicit templates must replace them with one shared activity placeholder. Backslash escapes literal braces and backslash. No shell, environment, or command expansion occurs. snajpagent appends one space after the selected label.
{operator} and {model_nick} show the local operator and agent IRC identities. Connected endpoints supply their accepted nicks, including server-assigned suffixes and later renames. Chat uses the selected endpoint; rollout uses the first endpoint. Without an endpoint, the local nick values are used. {model} continues to mean the provider's model identifier. {session_name} shows the saved name set by -N or /session name NAME, and expands to empty text for an unnamed session. Renames update the prompt, and resume restores the saved name. Names are inserted literally, including spaces and braces. These optional fields do not change the default template.
{queue} expands to the plain number of pending future prompts, including 0 when empty. It includes paused prompts, excludes the running turn, and updates as prompts are queued, started or removed. {context} expands to the plain percentage number, or ? when unknown; it includes no percent sign. Punctuation and spacing belong to the template. The optional {queued:TEXT} section renders TEXT only when the queue count is nonzero. It accepts ordinary fields and may appear inside a mode case or in shared text. Its contents are validated even when hidden. Default rollout prompts use {context:3}% {queued:({queue}) } after provider/model/effort, before the idle/active marker. Default chat and queue-edit prompts are unchanged. Explicit pre-1.0 templates must replace the old decorated fields: use {context:3}% instead of {context:4} and {queued:({queue}) } instead of a bare {queue}.
Numeric fields accept a minimum space-padded width, for example {context:3}% renders " 0%", " 9%", " 10%", "100%", or " ?%". Queue counts also accept a minimum width, such as {queue:3}. Clock components are natural decimal digits and additionally accept zero-fill: {hour:02}:{minute:02}:{second:02} is the default local clock. Bare fields are unpadded. Failed time capture renders each component as "--", padded with spaces even for a zero-fill format. Widths never truncate, exclude literal punctuation, and must be positive decimal integers at most 510. The complete label, input space, and largest spinner frames must still fit 512 bytes including NUL. Signs, whitespace, extra leading zeros, empty widths, and formats on text/spinner fields are rejected in every mode. Context and queue zero-fill are invalid. Invalid reloads retain the previous config. The pre-1.0 combined {time} field is removed: manually replace it in explicit templates with clock components.
For a minimal prompt without spinners, use this single-line value:
prompt = {chat:{operator}:}{rollout-idle:›}{rollout-active:»}
To show both nicks and the session name in every view, use:
prompt = [{session_name}] {operator}/{model_nick}{chat: :}{rollout-idle: ›}{rollout-active: »}
The full expanded label must fit the limit described above. An interactive session rename that would exceed it is rejected and keeps the previous name. A long name remains valid when the template does not display it.
Templates must be printable valid UTF-8, at most 1024 bytes. All three cases are required and nonempty; text and fields outside them are shared. A case may omit fields. Each spinner may occur at most once in each mode after combining shared fields with its selected case. The default appearance is described under Terminal, Views and prompt.
- prompt_spinner_goal, prompt_spinner_provider, prompt_spinner_tool
- Quoted inactive-item plus active-frame strings. The first safe one-column Unicode item is the inactive rendering. Exact leading file characters \0 instead select zero-width inactive rendering; no NUL byte is stored. A space therefore reserves a stable column, while "\0" removes a present field and "\0⚑" appears only while active. The remaining one-column items are active frames, bounded only by the value's byte limit. One active frame is static and schedules no periodic refresh; two or more animate. Adjacent active frames must differ. A blank active frame still occupies one column. Omit a placeholder to remove it in one mode. Literal spaces are never trimmed, and numeric padding never restores an absent spinner. The activity slot uses the tool sequence during a tool call, otherwise the provider sequence during provider operations and its inactive item while no provider operation is running, including a parked turn. A selected sequence with no active frames uses its inactive item. Defaults:
prompt_spinner_goal = " ⚑" prompt_spinner_provider = " ◴◷◶◵" prompt_spinner_tool = " ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
- prompt_spinner_per_second
- Shared animation rate, 1 through 60. Default: 8. Hidden and static indicators do not animate.
- prompt_tool_spinner_off_delay_ms
- Keep the tool indicator visible for this many milliseconds after a tool call finishes. Default: 500; range: 0 through 60000. Zero disables the delay. A new tool call resets the delay when it finishes. After expiry the current provider, goal and idle state is shown normally. Uses the existing spinner refresh timer, including when the configured tool frame is static. Only presentation is delayed; tool execution and result delivery are unchanged.
tool
- shell
- Absolute executable used by exec_command. Default on POSIX: /bin/sh; Android uses /system/bin/sh. Experimental native Windows builds use the system cmd.exe path. Commands must use the configured shell's syntax; changing shells does not translate them. The target must resolve to an executable regular file, but its configured path is preserved so symlink-selected shell behavior (such as BusyBox sh) is retained.
- default_yield_ms
- Default per-invocation yield interval, 0 through 600000. Default: 10000. Zero waits for completion subject to max_wait_ms and operator input.
- max_wait_ms
- Maximum time each managed-tool invocation waits before returning control to the model, 1 through 4294967295 milliseconds. Default: 60000 (60 seconds). Applies to exec_command and every write_stdin call, including termination, zero yields and longer model-requested waits. Shorter yields still apply. The budget starts anew for each invocation; it measures elapsed wait time, including time with output, rather than inactivity or total command lifetime.
Expiry returns status=running, reason=wait_timeout, the live handle and captured output. The model text includes elapsed wait time, the configured limit and pending termination when applicable. The process stays owned by the session; the model decides whether to wait again, inspect, send input or terminate. The limit sends no signal. Command-specific timeout_ms remains a separate one-shot handoff deadline. Interactive operator cancellation retains its existing shutdown behavior.
- max_parallel_commands
- Maximum unsettled command handles, including yielded and ready-but-uncollected jobs. Range: 1 through 4294967295. Default: 4. One runs commands serially without restoring the former tool-catalog lock. This is not a subprocess or CPU limit.
- default_timeout_ms
- Foreground handoff deadline when the model supplies none, 0 through 4294967295. Zero means no deadline. Must not exceed max_timeout_ms. Default: 0. Reaching the deadline does not kill the command.
- max_timeout_ms
- Maximum positive model handoff deadline, 1 through 4294967295. Default: 86400000. Out-of-range tool arguments are rejected before execution with the allowed range; they are not silently reduced.
- max_output_tokens
- Command-result model-context ceiling in retained UTF-8 bytes, 1 through 4000000000. Default: 6000. The legacy key name is not a token-count unit; provider/model output limits with the same name are separately token-based. A tool call selects a smaller positive bound or null for this ceiling. Larger requests are capped at this value, with requested and applied values reported. A separate per-result context-safety ceiling may reduce the excerpt further and is also reported. These limits leave durable capture unchanged. Host argument corrections and applied-limit feedback are supplied separately from the bounded command-output body, including with very small output budgets.
- output_cache_bytes
- Maximum command-output bytes kept in the in-memory paging cache, 0 through 67108864. Default: 1048576 (1 MiB). Zero disables the cache. Complete redacted stdout and stderr remain in the durable session journal independently; cache misses reload bounded ranges for read_tool_output without rerunning the command.
- max_output_bytes
- Maximum result bytes displayed per tool call, 0 through 4294967295. Zero means unlimited. The durable session retains complete redacted output; model context has its own tool-call bound. Default: 0.
- secret
- Repeatable additional value to protect, using the same source syntax as api_key. Environment references are removed from child environments; files/literals are not exported. Missing required sources fail before protected work. The obsolete secret_env key is rejected.
irc
- listen
- Enable the server and set its listener. Absent by default. A bare command-line -s uses localhost:6667.
- client
- Outgoing endpoint. Repeat up to 16 distinct endpoints. Command-line -c occurrences replace this entire list, preserving their order.
- model_nick
- Preferred model nick. Default: agent0.
- operator_nick
- Local operator nick. Default: valid USER plus 0, then a distinct operator0 or localop0.
- room_name
- Preferred room for current or future hosting. Default: hostname. Leading # is optional.
- history_lines
- Server and snapshot history, 1 through 1000. Default: 200.
snajpagent peers negotiate incremental reconnect history using durable stream IDs and event sequences. Reopening the same server session preserves its IDs; new server sessions have a new stream. A client's saved cursor requests only missing events. Duplicate events are not appended, displayed or introduced as new model input. Current topic, membership and accepted nicknames still refresh. An expired cursor reports a retention gap and replays the available newer tail. New clients receive one bounded initial history. Interrupted delivery is recovered from the client's session until a model response consumes it.
rule NAME
Filter model tool calls before they run. Each [rule NAME] section declares one rule in a single ordered list; the first matching rule decides. Rules are validated when the configuration loads: unknown keys, invalid regular expressions and duplicate names fail startup instead of becoming silent policy. Keys from the 0.99.7 chains, jumps, thresholds, templates and helper verbs are refused at load; see the migration table below.
- action
- allow or deny. Put allowed tools first and a match-all deny last to build an allowlist.
- match
- A JSON object mapping each /json/pointer to a POSIX extended regular expression; every pair must match.
- message
- The message shown to the model for deny. A value that starts with a double quote is decoded as a JSON string. Refused on allow.
The envelope exposes the immutable facts boundary, kind, surface and tool, with the tool argument object as value and its canonical JSON as text. A rejected call answers a factual not-run result; it is never silently dropped. Rules are filtering, not containment: matching a command string is not a sandbox, so use read-only turns and separate accounts for real isolation.
[rule deny-recursive-delete]
match = {"/tool":"^exec_command$","/text":"rm[[:space:]]+-[^[:space:]]*[rf]"}
action = deny
message = "Recursive force-delete is disabled; delete explicit paths."
[rule allow-commands]
match = {"/tool":"^exec_command$"}
action = allow
[rule deny-everything-else]
action = deny
message = "Only the audited command tool is permitted."
Rule count is bounded by the configuration file; 63-byte names and 64 KiB per message. The engine is stateless: no rule fires once, rate-limits or counts across evaluations. Every matched rule records one fixed audit line, rule=<name> decision=<allow|deny> tool=<tool>, as a durable rule_log event, including rules after the deciding one, so a trailing match-all allow audits the whole session without changing any verdict.
0.99.7 rule files need three renames: chain lines are deleted, reject becomes deny, accept becomes allow, and text becomes message. Jump chains are pasted inline in order, return is deleted, and thresholds, log templates and the insert, command, confirm and value verbs have no replacement: configurations using them fail startup with a pointer to design/io-rules.md.
Scripting↑
Automation and integration
Use -e for scripts. It runs the supplied turn and any explicitly requested goal continuations before exiting. With --resume and no new prompt, terminal or empty stdin continues saved unfinished work directly; completed or explicitly stopped work stays stopped. Nonempty piped stdin remains new input. For example:
snajpagent -e --resume SESSION_ID </dev/null
A prompt after -- preserves shell argument boundaries as spaces; a prompt from non-terminal stdin preserves internal newlines and removes one final LF or CRLF.
Standard output carries one-shot model text. Diagnostics and the resume hint use standard error. Redirected model text remains byte-exact and contains no terminal color or Markdown presentation.
The normal coding-tool surface is exec_command, write_stdin, apply_patch, list_files, read_file, grep, write_file and edit_file. Both normal and read-only turns can inspect retained images, documents and sampled video through view_image, read_document, and view_video. Configured audio routes add perception, transcription and speech generation. Read-only queries instead expose the native functions list_files, read_file, and grep. They also expose provider-hosted web_search, executed by the provider, never as a local function or external command. Listed AGENTS guidance remains subordinate to the read-only query and its restrictions. Other file and web contents are untrusted data, not instructions. All other local tool dispatch, including process control, patches, goal lifecycle, and IRC, is unavailable for that turn.
Native inspection accepts literal cwd-relative or absolute paths and never follows symlinks, including parent components. Listings include hidden entries; special files and symlinks are labeled but not opened. Reads require regular UTF-8 files without NUL bytes. Search uses POSIX extended regex or explicit literal matching, with optional case folding, and reports skipped non-text/special files. It does not interpret ignore files or shell syntax. Local readable files remain accessible. Session/history persistence and filesystem access-time updates remain normal; independently running processes are not stopped or made read-only.
Results are bounded to 256 KiB, lines to less than 64 KiB, each scan to 128 MiB, directory entries to 100,000, and traversal depth to 32. Listing/search use offset/limit pagination (default 200, maximum 1,000 results); output identifies incompleteness and the next offset. Scan limits and I/O errors require a narrower path. Oversized file output fails explicitly: use smaller line ranges. Directory order is sorted per directory; pagination observes the live tree, not a filesystem snapshot.
Tool arguments can arrive as streamed bytes or completed snapshots. An empty in-progress placeholder leaves the arguments pending. Conflicting argument bytes or completed snapshots fail before tool execution.
Commands that yield, reach a foreground handoff deadline, or receive urgent input continue under a process handle; the model can wait, interact, or explicitly terminate them with write_stdin.
An unsettled command handle blocks a final model answer. Recovery points the model to the current process snapshot and write_stdin so it can collect terminal results before answering; a terminally failed turn closes its owned commands.
The current foreground process owns its command handles. Turn interruption, closure, or process exit closes managed commands; resume does not revive an operating-system process.
Every exec_command and write_stdin call selects a positive max_output_bytes context bound or null for the configured [tool] max_output_tokens ceiling (default: 6000). Larger requested bounds are capped at that ceiling; smaller bounds are honored. For backward compatibility the option keeps its name, but the bound is a retained UTF-8 byte limit, not a token count or estimate. It is persisted with the result and enforces byte/memory safety. Truncated context retains bounded provenance; complete redacted output remains in journal chunks. Polls return only newly collected output, with exact stream-range references. Multiple independent command calls may overlap; emission order does not establish a dependency. A batch shares the smallest positive yield interval. Steering does not discard valid calls already emitted in an accepted response: those calls cross their durable admission boundary, then running commands are handed back alive immediately. Capacity-blocked calls retain that factual reason. Invalid or repeated same-handle interactions are not run. All handles must be settled before a final answer or goal completion. Existing commands keep their tool catalog; unrelated work does not require an immediate poll.
Each command result identifies its durable output handle and exact stdout and stderr byte ranges. read_tool_output accepts that 32-character handle, one stream, a zero-based byte offset and an optional model-facing page ceiling. It reports total and next offsets, EOF, cache disposition and either UTF-8 text or base64 for binary bytes. Pages are bounded by the effective common result ceiling; settled output remains readable after compaction, cache eviction and session resume. Unknown handles, streams or offsets fail without running a replacement command. Failed or cancelled cache fills preserve the previous cached page and its handle. Unreadable or corrupt history is reported as a read failure, not as proof that output does not exist. Uncollected output can use the live process's verified journal cursor; older and settled ranges still require historical lookup.
set_command_shell selects an absolute executable regular file for later exec_command calls in this session. The event survives resume and preserves a symlink path whose name selects shell behavior. It does not translate command text, alter already-running commands, expand authorization, expose secrets or replace the existing workdir and environment rules. Hosted web search is a provider tool. When the active provider URL has the exact hostname openrouter.ai, snajpagent automatically uses openrouter:web_search; other providers retain web_search. Hostname matching is case-insensitive and accepts an optional terminal DNS dot and port, but not userinfo, subdomains, or lookalike hosts. The provider section name and model name do not control detection. Use the usual https://openrouter.ai/api/v1 base URL, model, and API credentials; no extra search setting, web plugin, :online suffix, or separate search credential is required. This applies equally to normal and read-only turns. OpenRouter chooses the search engine by default; search incurs provider charges in addition to model usage.
Networked mode adds irc_state, irc_send, irc_nick, irc_topic, irc_connect, irc_host, and irc_disconnect. Sending, live nick changes and topic changes require a nullable string destination argument: a number from irc_state, all for explicit broadcast, or null only for a sole destination. Model routing is independent of the operator's selection. Results distinguish queue acceptance from failures, not remote receipt.
Explicit goal requests expose create_goal when no unfinished goal exists. An active goal exposes update_goal for rewriting, completion, or blocking. A paused or blocked goal exposes neither. A locked goal refuses the model's rewording, blocking and cancelling; completion and resume stay available, so a locked goal can still finish.
IRC is the network integration. The event log is durable internal state, not a stable external API while snajpagent remains pre-1.0.
Signals
- SIGINT
- Interactive Ctrl-C follows the composer rules above. Outside that handler it requests graceful shutdown. On POSIX hosts, spawned commands start with SIGINT unblocked and its default action, independently of the parent's signal state.
- SIGHUP, SIGTERM
- Request graceful shutdown, terminal restoration, IRC closure, and a resume hint. Exit status is 128 plus the signal number. For a Linux native terminal frontend these signals disconnect only that frontend; its session owner continues. /exit stops the owning session.
- SIGTSTP
- Ctrl-Z restores the terminal before suspension and redraws after continuation.
- SIGPIPE
- Ignored; write failures are handled as errors.
Exit status
- 0
- Success or normal interactive exit.
- 2
- Command-line, configuration, locale, credential, prompt, or selection error.
- 3
- Durable state, local runtime, or IRC failure.
- 4
- One-shot provider, protocol, context, or tool-cycle failure.
- 6
- Terminal/output failure or one-shot interruption that cannot return to a prompt.
- 128+N
- Graceful exit after signal N.
Model tools↑
Visibility and execution
These are model-callable tools, not slash commands to type into the composer. Every provider request advertises the configured native catalog. The allow_model_change setting controls whether select_model is available; it defaults to false. Read-only mode, goal and queue state, network configuration, continuation mode and current endpoint state do not remove a schema. A declaration describes a call shape; runtime validation and tool results report whether the current turn can perform that call. Arguments use strict JSON objects: required fields are present, with null where a nullable field should use its default. Unknown fields are rejected. Pre-1.0 tool and event formats are not a stable third-party automation API.
Tool catalog
The following names are the functions shown to the model. Their operands and results are documented below. Slash commands are operator input; they are listed under Commands. Hosted search is advertised in the provider's native form.
- Working directory and model
- get_cwd, cd, select_model.
- Commands and process control
- exec_command, write_stdin, read_tool_output, set_command_shell.
- Filesystem exploration
- list_files, read_file, grep.
- File modification
- write_file, edit_file, apply_patch.
- Media and workstation delivery
- view_image, read_document, view_video, listen_audio, transcribe_audio, speak_text, send_file, download_queue.
- Saved history and continued work
- read_session_history, list_goals, create_goal, update_goal, timer, defer_steering.
- IRC
- irc_state, irc_send, irc_topic, irc_nick, irc_connect, irc_host, irc_disconnect.
- Search
- Provider-hosted web search, when supplied by the provider request format.
Argument validation and applied limits
Every provider receives the same native tool contract: required operands, optional controls, types, ranges and parameter descriptions. Object key order is irrelevant. Omitted optional controls use their documented defaults; nullable fields also accept JSON null. The string "null" remains literal data. Fixed policy uses system messages for every provider. Each model request ends with an explicit developer-level host continuation, separate from operator input and approval. Automatic goal turns additionally carry a labelled host request in the user-role conversation slot. It keeps the current goal request alongside retained history when a gateway moves instruction messages out of the input. This transport marker creates no operator input, approval, receipt timestamp or goal transition; paused goals remain paused on resume. Saved reasoning stays in its compatible order before the developer boundary; history with non-thinking replies also remains resumable. Summary requests use the same separation. User requests and tool results retain their provenance. Media parameter descriptions include crop and sheet coordinates, index origins, interval defaults and configured audio behavior. Coding requests also identify the current working directory, configured shell, wait/deadline defaults, parallel-command maximum, output ceiling and goal text limits.
The canonical command fields are command, yield_ms and max_output_bytes. The legacy spellings cmd, yield_time_ms and max_output_tokens respectively remain accepted. Supply one spelling per field: supplying both is rejected, even if the values are equal or null. Other unknown fields, including shell, are rejected. Original arguments remain unchanged in the journal.
Argument errors identify missing operands, unexpected fields, invalid types, ranges and path or cross-field restrictions. A not_run result records an unexecuted attempt with no new process or handle. At verbosity 1 and above, private rollout shows its tool name, status and short reason in both live and history views. Full arguments and output remain governed by their detail levels. Skipped calls also explain slot limits, stale/busy handles and control handoffs.
Each request identifies the current working directory, configured shell, wait/deadline defaults, parallel-command maximum, output ceiling and goal text limits. Output requests above the configured byte ceiling are capped with requested/applied feedback. Excessive waits, timeouts and other out-of-range controls are rejected rather than capped. Latest-batch corrections and applied-limit feedback remain visible to the model even under a tiny command-output budget and after interrupted provider requests. Complete redacted output remains in the session journal.
Working directory and model selection
get_cwd has no arguments and returns the session's current absolute working directory. New sessions start at the home directory; a saved session continues from its last recorded directory.
cd requires path to an existing directory. Absolute paths and relative paths, including ./, resolve against the current directory. A successful change is saved before later tool calls, including calls in the same response batch; existing processes keep their own launch directory.
select_model requires selector, either a configured provider/model/effort, a shorter model selector, or a numbered row from the cached model listing. It is available when [agent] allow_model_change=true (default: false). When disabled, the tool is absent from the model's catalog and calls fail without side effects. The provider, model and effort must match a row in the current model cache; an empty or unreadable cache leaves the session selection unchanged. Selector cache refreshes every configured provider and returns the available cached rows. A failed refresh retains the previous cache and model selection. Operator-typed /model and command-line -m may still choose uncached identifiers. A model choice selects the next response in the current turn and remains selected for later turns. An already running command handle stays live; completed tool results are retained in the next request's context. This tool does not modify the configuration file. The /model command uses the same persistent selection and can interrupt a streaming response to start a request on the new model.
send_file
Requires path, a literal cwd-relative or absolute regular-file path without symlinks, or asset:ID for an accepted retained file. Journals a workstation export in the current session and sends it when a live native wrapper replies. Returns a terminal result distinguishing queued intent from delivery; there is no managed-process handle to poll. Completion requires the client's digest acknowledgement and final EXIT. Native completion receipts list actual local paths. File bytes are outside tool output and never uploaded to the model. See Terminal file transfers for setup, destination, cancellation and recovery.
download_queue
Requires action=list, remove or clear. List returns pending IDs, source paths, byte counts and SHA256 digests. Remove also requires the item's id; clear removes all pending exports. An optional reason is saved with mutations. Stale sources can be removed without opening them. Removing an export changes neither its source nor any landed workstation file. Read-only permits list and rejects remove and clear. Pending state belongs to the existing saved remote session and survives resume and checkpoint replay.
exec_command
Requires command, the source text for the configured shell. Workdir is optional: omission or null uses the session's current working directory; a supplied existing directory may be absolute or start with ./ relative to it. Other controls are optional. For example:
{"command":"pwd"}
stdin is omitted/null to leave input available for later interaction, or UTF-8 text up to 1 MiB to send and then close input. An empty string sends immediate EOF. pty is true for a pseudo-terminal; false or null uses ordinary pipes. PTY output is a terminal stream rather than separate stdout/stderr, and terminal line editing/echo rules may affect interactive input.
yield_ms is 0 through 600000, or omitted/null for the configured default. It bounds the foreground wait before returning a handle; it is not a process lifetime. An explicit 0 yields after the accepted call wave starts; a configured default of 0 waits for completion subject to max_wait_ms and operator input. timeout_ms is a positive foreground handoff deadline, bounded by the configured maximum, or omitted/null for the configured default. The built-in default has no deadline. A deadline yields control without terminating the child.
max_output_bytes is a positive retained UTF-8 byte bound for result text sent to model context, or omitted/null for the configured ceiling (default 6000). The legacy input spelling max_output_tokens retains these byte semantics. This argument selects the model excerpt; the similarly named [tool] max_output_bytes setting controls terminal display only. Larger requests are capped with requested/applied feedback. Complete redacted process output remains in the session log; the bounded excerpt identifies truncation and its source. Returned status distinguishes running, successful/failed exit, and termination by signal. A running result means the invocation returned, not that the command completed. Keep its handle and use write_stdin; do not start the same command again merely to wait for it. The turn cannot complete with a running handle: collect its terminal result or close it through an authorized cancellation/failure transition. A rejected premature final does not erase the process or claim completion.
After command exit, output capture drains for at most two seconds. If another process still holds a writer open, remaining capture streams close and the result preserves the command's exit code or signal with reason=output_drain_timeout and an explicit warning that output may be incomplete. Already captured output is retained and secret-redacted. The completed handle is released; unrelated processes holding passed descriptors are not signalled. Continued output does not extend the drain limit. A live command remains governed by its ordinary wait and termination controls.
write_stdin
Requires a live handle. Omitted data is an empty string: it polls without sending bytes or closing input. Supplied data must be a string, not null. The eof and terminate flags default to false; optional wait/output controls use the same defaults and legacy spellings as exec_command. Minimal polling:
{"handle":"<live handle returned by exec_command>"}
handle identifies a currently managed command. data is UTF-8 input up to 1 MiB; use an empty string to wait without writing. eof true closes input after the accepted data; false or null leaves it open. terminate true requests termination and requires empty data with eof false or null. A yield, wait limit or operator /yield can return a running result and the same handle while termination or output drain remains pending. Yield and output bounds follow exec_command.
Pending input and output are bounded. Accepted input may still be waiting to reach the process; output references distinguish accepted, written, pending and open-input state. Do not resend accepted data or send to a closed input. Each handle can participate at most once in a model response's call batch. The runtime rejects invalid, stale or concurrently occupied handles rather than guessing which process was intended.
Unsettled commands count against max_parallel_commands, including commands that have exited but whose final result is not collected. The model may perform unrelated work while a command runs, but must settle all handles before its final answer or goal completion. Shutdown cleans up managed commands and descendants; saved session history cannot revive them.
apply_patch
The patch is required. Optional workdir defaults to the current working directory on omission or null; another existing absolute or ./ directory is accepted. patch contains one bounded patch with this structure:
*** Begin Patch *** Update File: example.c @@ -old line +new line *** End Patch
Also supported are *** Add File: with added lines prefixed by plus, and *** Delete File: without a body. File paths may be absolute or relative to the current working directory, including ./; parent traversal, duplicate targets and symlink traversal are rejected. Update hunks locate changes by matching context. There is no move/rename operation in this patch grammar.
The tool validates targets and computes replacements before installation, checks for concurrent changes, and reports rejection or I/O failure explicitly. Do not assume a multi-file transaction survives every crash atomically. Inspect the result and current files after failure before retrying. Limits are 2 MiB patch text, 1 MiB per patch line, 256 file operations, 2048 hunks in total, 16 MiB per regular file and 48 MiB aggregate file data. Result text and diff previews are bounded. Use normal command tools for work outside this patch grammar. Their authority is the launching user's OS permissions.
Native exploration
list_files accepts path, recursive, offset and limit. Path is required; null recursive means false. It lists hidden entries and labels directories, symlinks and special files without following them.
read_file accepts path, start_line and end_line. Non-null line bounds are inclusive, 1-based and at most 2147483647. Null bounds select the start/end of the file. Text is returned with line numbers; use narrower ranges when output is too large.
grep accepts path, pattern, recursive, ignore_case, literal, offset and limit. Patterns are at most 4096 bytes. Null recursive means true; null ignore_case and literal mean false. The default is POSIX extended regular expressions; literal true disables regex interpretation. Matches use path:line:text.
For listing and search, offset is zero-based (default 0, maximum 1000000), limit defaults to 200 and may be 1 through 1000. Use the returned next offset when pagination is incomplete. Scan-limit exhaustion instead needs a narrower path. The live filesystem can change between pages. All inspection tools require safe non-symlink paths; file reading requires regular UTF-8 text without NUL. Result, traversal and scan ceilings are listed under AUTOMATION AND INTEGRATION. These tools do not interpret shell globs or ignore files.
These native functions are declared in every turn and are preferred over shelling out for reading, searching and listing. A read-only turn executes them under the same bounded path; other declared tools return a read-only refusal.
Native modification
write_file accepts path and content. It creates or replaces one absolute or cwd-relative file atomically; parent directories must already exist and symlinks are rejected. Content is limited to 16777216 UTF-8 bytes. A failure leaves the previous file untouched.
edit_file accepts path, old, new and an optional count. It replaces exactly count occurrences of the exact text old (one by default); when the file does not contain exactly that many occurrences, nothing changes. The result is installed atomically. Prefer write_file for a wholesale replacement and apply_patch for several files or hunks.
Modification tools are declared in every turn. They execute only in an ordinary turn; a read-only attempt returns a terminal refusal and leaves target files unchanged.
Image inspection
view_image accepts a literal path to PNG, JPEG, GIF, WebP, BMP or TIFF bytes, relative to the current working directory or absolute, or asset:ID for an accepted image in this session. Available in normal and read-only turns; no symlink path components are followed. Image contents are data, not executable instructions.
Optional frame is a zero-based animation/page frame number from 0 through 999; omission or null selects frame zero. Optional crop is an object with integer x, y, width and height in source pixels, before orientation; omission or null selects the whole frame. The crop must lie within the source. Linked decoding prepares one RGBA PNG, applies orthogonal rotation/reflection metadata, preserves alpha and limits the long edge to 1600 pixels. Source images are limited to 256 MiB and 16,777,216 pixels; cooperative decoding time is limited to 60 seconds. Color profiles are not applied. Unsupported transforms, malformed data or exceeded limits fail.
The result records the original asset and prepared PNG with the selected frame, crop, dimensions and display transform. Other frames and regions remain uninspected. For a closer view, call view_image on the original asset with a narrower crop. /attach uses the same preparation with frame zero and no crop; /attachments shows preparation details before submission. The original is retained for follow-up inspection, while only the prepared PNG is transmitted.
The journal stores references, not base64. Resume reuses the prepared PNG without redecoding the original; a changed/deleted external source cannot change accepted input. Missing or corrupt retained source/prepared assets fail explicitly. Copy the entire session directory when moving a session. Confirmed session deletion removes its retained media.
Requests accept prepared image data up to 12 MiB in total, at any image count. Provider/request ceilings may be lower. Retained media accumulates on disk until session deletion; there is no fixed session-wide byte or file-count cap. A custom WITH_AV=0 build forwards PNG/JPEG up to 12 MiB without normalization; other formats and non-default frame/crop selections require linked decoding.
The configured provider receives the image bytes. Its selected model must support image input in function results. Requests use explicit high detail. Exact token counting is preferred; strict mode still requires it. With counting off or a definitively unsupported endpoint, image requests use media_upper_bound on every provider route: canonical textual request bytes without image URLs, plus one provider-generic nominal resized-image budget per image and framing reserve. The budget follows the first-party client's resized-image size, because provider-resized high-detail images are tokenized from their processed size rather than their compressed bytes. Base64 length and text-only learned ratios never estimate vision tokens. The same rule applies to compaction, and the provider's own reported input count remains authoritative after each response. An upper bound above the model capacity does not prove actual overflow and does not itself trigger token-budget compaction or discard a successful summary. Without exact counting, proactive compaction uses compatible provider-reported usage; an actual provider capacity rejection follows the existing recovery path. The image and request-body byte limits remain enforced independently.
A [model-limit] rule may declare a provider-documented image_tokens ceiling for the matched local model, which supersedes the generic budget; DeepSeek documents 1,024 tokens per image for deepseek-flash. The generic budget is deliberately conservative, not an exact count or a billing quote, and no model name, specialized variant, custom base URL or proxy route is refused for image input.
Protocol display omits media-bearing request bodies rather than printing data URLs. No microphone capture, automatic upload, or remote image fetch is implied.
Document and sampled video inspection
read_document accepts a local path or an accepted session reference of the form asset:ID. It supports PDF, UTF-8 text/Markdown, CSV and TSV, plus selected rendered Office pages from DOCX/ODT, PPTX/ODP and XLSX/ODS. The first and last selectors are optional, inclusive and 1-based. Omitted or null first selects page/record one; omitted or null last matches first. Select at most four PDF pages or 200 text records per call. PDF results contain extracted text followed by a rendered page, including scans with no extractable text. Visible text requires an embedded or suitable system font; an unavailable or unusable font fails the page without retaining a partial rendering. Blank pages and invisible OCR text remain supported. CSV quoted multiline fields count as one record; TSV uses lines. Text selections and individual records have a 256 KiB bound. Only selected content is inspected.
Office import runs in a bounded child of the same linked executable, with a fresh private profile and no inherited API credentials. The worker rejects scripts, macros, encrypted packages, external resource relationships, XML entities and embedded OLE. Before import, the private profile disables macros, empties trusted locations, blocks links from untrusted documents and sets Writer/Calc link updates to Never. The load call also disables macro execution. These importer settings complement package checks; they do not prove every possible external-resource access is blocked. Linux network and process-execution syscalls are denied; available Landlock filesystem restrictions are applied and their availability is reported in the result. On other targets the result reports unavailable OS filesystem/network/exec confinement; package checks and worker resource limits still apply. This is not a general cross-platform parser sandbox. The worker has a 60-second CPU/elapsed limit, 2 GiB address-space limit (data-segment limit on macOS and targets without RLIMIT_AS), 32 MiB per-file/output limit, 4,096 ZIP-entry limit, 16 MiB per XML entry and 256 MiB expanded-input limit. The resource limits and a fresh credential-free environment are also installed by the POSIX worker itself. Windows verifies the direct-argv child's 2 GiB job memory and 60-second user-CPU limits, installs a 60-second elapsed timer, locks the private directory and replaces its environment while retaining system directory variables. A direct internal invocation creates its own job when unassigned; an existing job lacking these limits causes an error. On Linux it exits if its parent disappears; other POSIX workers can survive the parent until their own elapsed limit expires. One private temporary profile/output directory is limited to 256 MiB for the current conversion. The existing conversion loop checks cumulative file sizes, 4,096 entries and 32-directory depth at 50-millisecond checkpoints and before accepting output; crossing a bound terminates the operation. This polling bound can overshoot between checks. POSIX per-file limits apply at writes; Windows enforces output and scratch sizes at parent checkpoints. Ordinary completion removes the directory; session reopening removes crash residue after its worker releases ownership. A surviving worker's directory is preserved and another Office import must wait for it to exit. Session deletion includes this temporary directory and preserves a live worker's data on failure.
Selected Office pages become a retained PDF and are inspected as text plus page images. First/last select importer page numbers for DOCX/ODT, workbook print pages for XLSX/ODS, and source-order slide numbers for PPTX/ODP. Selected hidden slides are included and retain their original slide numbers; speaker notes are excluded. Results identify the numbering used. Page/slide selection must produce exactly the requested number of pages or it fails. Layout, substituted fonts and computed values may differ from the originating application. Other pages, slides and speaker notes remain uninspected. Select at most four pages per call. A failed/cancelled import publishes no partial result and removes its own temporary profile; retained originals remain available.
For XLSX and ODS, sheet_range selects a cell rectangle with integer members sheet, row, column, rows and columns. Sheet, row and column are 1-based; rows and columns are counts. Select up to 200 rows and 32 columns, within row 1,048,576 and column 16,384. sheet_range is optional; it and non-null first/last are mutually exclusive. With all selectors omitted or null, a workbook starts at sheet 1, A1:H20.
Sheet results include its name/count, absolute cell coordinates, JSON-quoted values, blank cells, merged ranges and covered-cell references, plus a rendering of that same selected rectangle. Newlines inside values are escaped as \u000a. Output text is bounded to 256 KiB; the image fits within 1600 pixels on its long edge. A selection beginning inside or cutting a merged cell fails when its coordinates cannot be matched; select the whole merged range. The importer's computed values may differ from saved Excel values. Hidden cells may appear in textual selection but not the rendering; formulas, macros and speaker notes are not executable instructions. Other sheets/cells remain uninspected. Request a new range or explicit print pages for layout elsewhere.
view_video accepts MP4/MOV and Matroska/WebM paths or asset:ID, with integer start_s, end_s and frames selectors. Omitted or null selectors default to 0 through 30 seconds and eight samples. Intervals must be at most 30 seconds; frames must be 1 through 8. Output labels the selected coverage, seek positions and decoded source frame PTS. Linked FFmpeg libraries scale frames to fit 1600 by 1600, normalize pixel aspect ratio, and apply orthogonal rotations/reflections with labelled transforms. Other display transforms fail explicitly. With multiple samples, the interval includes the first decodable frame and the last frame before its exclusive end. Samples can miss intervening events; this is not continuous video perception. When an audio stream and a configured transcription route exist, the same interval is transcribed once using the retained source, with separate API billing. Missing, failed or interrupted transcription is explicitly labelled as uninspected audio. Acoustic questions can use listen_audio on the same source and interval; video inspection does not automatically pay for both audio operations.
listen_audio requires path and question; start_s and end_s are optional. A path may name a supported audio or video file, or asset:ID from a retained result. question contains 1 through 16384 UTF-8 bytes. The configured audio model receives the extracted sound and question; its attributed answer is returned as derived tool data.
transcribe_audio requires path and accepts optional start_s and end_s. It returns speech text from the configured transcription endpoint. Both input tools accept integer seconds: omitted or null start selects zero, and omitted or null end selects start plus 60. Start must be 0 through 86400, and the interval must be positive and at most 60 seconds. Linked FFmpeg libraries extract the first audio stream as 24 kHz stereo PCM. Mono is duplicated into stereo; more than two source channels fail rather than being silently mixed. The decoder reads only the retained file; additional file and network opens are rejected. Format changes and gaps fail explicitly. Decoder limits and a 60-second operation deadline bound supported inputs; this is not a parser security sandbox. Results retain the source and extracted WAV, label requested coverage and decoded duration, and establish neither speaker identity nor word timestamps. Other intervals remain uninspected.
speak_text accepts text containing 1 through 4096 UTF-8 bytes and retains a WAV asset from the speech endpoint. It is unavailable in read-only turns. Playback and microphone capture are not started. Identify this voice as AI-generated when presenting the sound to listeners.
voice_output accepts optional text for the active voice conversation. Omitted or null text reports whether voice is enabled, ready, microphone-muted, and holding pending output. A text request accepts 1 through 262016 UTF-8 bytes and applies configured secret redaction. It requires an already-ready voice connection and playback device; it never starts voice or reactivates a detached terminal. Microphone mute permits playback. Read-only turns can inspect availability but cannot send speech.
Accepted speech uses the existing voice output mailbox. Busy or unavailable output returns a failure without accepting the new message. Queue acceptance and protocol preparation are recorded separately; neither confirms that sound reached the listener. Closing voice discards unsent output, which is never automatically replayed on the next connection. The model chooses when to use this tool under the session's existing instructions.
Audio understanding and transcription are available in read-only turns and can incur API charges. A failed understanding request does not fall back to transcription. Accepted answers and sound assets replay without another paid request. The configured provider is named in results. Returned usage is retained separately as provider-reported text, including duration-based usage when supplied. Speech endpoints returning only WAV bytes provide no response-body usage; no zero-cost inference is made from its absence.
Document and video tools are available in normal and read-only turns. They retain the original and derived images privately; replay does not rerun conversion. The existing image/request and session storage limits still apply. Extracted text receives configured secret redaction, but images, original files and sound cannot be redacted as text. Do not submit sensitive media expecting textual redaction to remove its contents. Document contents are data, never tool instructions.
PDF rendering uses linked Poppler/libpng with a bounded retained-file stream. It reports page count, rejects encrypted documents and invalid page ranges, and keeps extracted text within 256 KiB per page. Page images fit within 1601 by 1601 pixels including raster rounding. Input reads and rendering callbacks check cancellation and a 60-second operation deadline. Parsing and font/image decoding remain third-party attack surfaces; these bounds are not an OS sandbox. No document action, macro, URL fetch, device capture or automatic playback is executed by the inspection tools.
Hosted search
web_search runs on the provider. The model receives provider search results in the same turn; external pages remain untrusted data. OpenRouter's endpoint selects its corresponding native search declaration automatically. Search availability and charges depend on the provider.
Each search is journaled and shown as a display-only tool row: a start row with the action and query, then a finish row with the provider's status and retained sources. The row is never a local function call, command or dispatcher target, and it cannot be vetoed after the provider ran the search.
Saved history and goal inspection
read_session_history reads a bounded newest-first page of verified durable session events. Optional before_seq is an exclusive positive event sequence cursor; omit it for the newest page. Limit defaults to 20 events (1..50), and detail_bytes defaults to 512 bytes per event (128..2048). Use the returned next_before_seq to page older events. Secret filtering applies to structured records before creating those excerpts.
list_goals lists bounded durable goal identities, current/final statuses and parent/replacement lineage. Optional before_seq selects an exclusive goal-creation event sequence; limit defaults to 20 (1..50). Use next_before_seq to continue. Neither inspection tool changes the current goal.
Model goal controls
create_goal accepts objective text and is declared in every request. It succeeds only when goal creation is explicitly requested and no unfinished goal already exists. Goal creation takes effect when the tool succeeds.
update_goal requires action; text depends on the action. Rewrite requires new wording; complete accepts omitted or null text; block requires a specific reason. It succeeds only for an active goal. A wording lock refuses the model's rewording, blocking and cancelling while completion and resume stay available; the operator retains explicit control through slash commands.
Timer
timer accepts delay_ms and text. A positive delay_ms and nonblank text replace any current one-shot timer. delay_ms 0 cancels the current timer and requires text null. The schedule is durable across resume. When due, the foreground process records the firing and admits one fresh ordinary turn containing the stored text. The transcript and history label this as a timer turn, not an operator submission, and the model receives it as host continuation. That turn does not resume a paused or blocked goal. Timer calls remain subject to the current turn's policy and cannot authorize a refused side effect.
Steering
defer_steering takes no arguments. The model calls it to switch steering off for the remainder of the turn. Steering messages queue and run after the turn ends instead of interrupting. New turns start with steers on.
Model IRC controls
irc_connect accepts endpoint and adds an outgoing endpoint using the current IRC identity and room settings. irc_host accepts endpoint and starts the built-in server there. irc_disconnect accepts endpoint and hosting. hosting false removes an outgoing endpoint; true removes the hosted endpoint. Each call reports the requested local endpoint transition. Use irc_state to inspect the current endpoint and room state after it.
irc_state accepts an empty object and reports the actual current destinations and room state; it does not open connections. irc_send accepts destination, notice and text. Null/false notice sends a normal message; true sends an informational notice, which does not satisfy a required reply. irc_nick accepts destination and nick and changes the agent identity on one endpoint or all explicitly selected endpoints. Successful changes update later state and slash-command control of those live connections. irc_topic accepts destination and topic. Runtime authorization follows the room's live policy: mode +t requires channel-operator status; without +t any joined member may change the topic.
Destination is a numeric string from irc_state, all for an explicit broadcast, or null only when exactly one destination exists. The model never implicitly follows the operator's selected room. Text and topic are bounded UTF-8; topic is at most 280 bytes with no newline. Successful send acceptance is not proof that every remote participant received it. The runtime owns joining, history, reconnecting and room traffic after an endpoint transition. A destination number keeps naming the same endpoint across reconnects: a send with a stale revision follows the number to its current revision, while a claimed future revision still fails.
Sessions and files↑
Session durability
Resume continues unfinished model/tool turns from their durable state, without requiring a fresh prompt. Already completed calls stay completed. The next model request includes the original calls and arguments even when their results are missing: a started call with no durable completion is outcome_unknown, with any captured output; a proven unstarted call is not_run. The model can inspect these facts before acting. Recovery never blindly re-executes a journaled command or revives a lost process handle. Interactive and one-shot resume share this path.
New sessions remain in memory until accepted work or durable state needs a log, such as a prompt, queued turn, goal or retained network event. Pre-work inspection, configuration editing and model-cache discovery alone preserve lazy creation. Preparing an attachment or starting dictation/realtime voice also saves the session for durable asset and voice ownership. Exiting before then creates no saved session, needs no deletion, and prints no resume hint. Startup activity and selection changes are retained if a prompt is later submitted. Existing resumed sessions remain saved. IRC startup and ordinary background events stay buffered until then; a direct mention of the model or a submitted operator chat message starts saved work.
Every accepted transition is appended and synced before it is treated as durable. Resume reconstructs turns, tool state, queue, goals, provider/model selection, and admitted IRC state.
Normal exit prints a two-line resume hint for every resumable session. It includes the exact session and a nondefault dotdir. Saved configuration paths, documentation roots, presentation and IRC settings, and model/provider/effort preferences are restored from the session. The command contains no prompts or credential values. Disabled roles stay disabled across changed config defaults; temporary connection outages retain the desired endpoints.
On experimental native Windows, the resume header names a normal cmd.exe prompt with delayed expansion off. Paste the printed command there, not into PowerShell or a batch file. A scoped child interpreter preserves literal percent signs, exclamation marks, quotes and other argument characters without changing the caller's environment. On POSIX, use a POSIX-compatible shell.
Resume preserves the saved goal state: active goals continue, while paused, blocked, completed and cancelled goals keep their recorded states. A retained provider-policy stop pauses an active goal and waits for operator clarification; reopening or resuming the goal alone does not clear that stop. Other sessions gain no goal pause or resume transition merely from reopening. Resume automatically displays the saved goal and wording, including its status, revision, lock and blocker, even with history disabled. Paused and blocked goal wording remains in ordinary model context after resume and compaction; it need not be re-entered. Inactive goals do not restart work. Queued turns retain their saved arming state. Armed queues continue after recovered active work; use /next to restart a paused queue. Interrupted active turns are recovered from their durable prefix.
Model requests preserve the distinction between intermediate assistant updates and final answers across tool calls, goal continuation, resumed sessions and compaction input.
History replay ends with shown, completed and total turn counts. Replayed chat timestamps have no per-message "history" label; stored historical flags and model-input handling are unchanged.
SIGKILL, power loss, and fatal process corruption cannot guarantee cleanup or a printed resume command. Already synced events remain available.
Files
The paths below use POSIX home-directory notation. On native Windows, HOME takes precedence; when it is unset or empty, substitute USERPROFILE. The --dotdir option replaces the .snajpagent root on every platform.
- $HOME/.snajpagent/config.ini
- Default configuration.
- $HOME/.snajpagent/auth/PROVIDER.json
- Private credentials bound to provider, endpoint, and auth method. Files must be user-owned regular files with no group/other permissions. Per-provider locks coordinate refresh rotation and atomic replacement across processes. Do not share or commit these files. An alternate dotdir has independent logins.
- $HOME/.snajpagent/models.json
- Atomically replaced versioned provider/model registry containing advertised token capacities, counting capabilities and observed context ceilings. Refresh only with /model cache.
- $HOME/.snajpagent/prompt_history
- Global plaintext prompt-history archive. On orderly exit (including handled SIGTERM or SIGHUP), only submissions accepted by this process are appended under the writer lock. Their order and deliberate repeats are preserved. Resuming and exiting without new submissions does not merge saved entries again. Submitting /exit is itself a new history entry; empty Ctrl-D adds nothing. Running programs do not publish their submissions immediately. Entries from exited programs become available to later navigation and searches in other running programs, after each program's own local entries. Another --dotdir selects another global archive.
Files are private mode 0600, one escaped physical line per submission. Navigation and search read the files on demand rather than copying the archive into memory. Neither file is automatically pruned. Malformed or overlong records are skipped; a torn final record is removed before appending. History failures warn without rejecting accepted input. Before a session is persisted, or when its history cannot be written, only the newest 100 unsaved entries within 4 MiB are retained in memory. Normal persisted submissions are not subject to this fallback limit.
- $HOME/.snajpagent/sessions/SESSION/prompt_history
- The session's own interactive history, restored on resume and searched before global history. It is created when the session is first persisted and appended on accepted interactive submissions. The global archive is not copied into it. An older session without this file starts with empty local history and still has access to global history. Existing local files remain readable, including entries copied by older builds; those entries are not merged again on resume. Linux native terminal sessions create their directory before the first prompt, so their ID and attachment endpoint remain available through terminal loss. Direct-terminal sessions on other hosts keep empty-session history in memory until accepted work requires persistence.
Abrupt termination can skip the global merge; already saved local history remains available on resume. Previous unmerged entries are not retroactively published. Deleting a session removes its local file, while global entries remain until removed manually. A session's entries can occur again when navigation reaches the global archive; intentional submissions are not deduplicated by text. Do not run older builds against these files: versions that bounded global history can truncate the archive. Back up history before manually editing or removing it.
- $HOME/.snajpagent/sessions/SESSION/events.jsonl
- Append-only durable session log.
- $HOME/.snajpagent/sessions/SESSION/lock
- Live session lock.
- $HOME/.snajpagent/trash/
- Private deletion staging.
- $XDG_CONFIG_HOME/snajpagent/AGENTS.override.md
- $XDG_CONFIG_HOME/snajpagent/AGENTS.md
- Optional global instructions. If XDG_CONFIG_HOME is unset, use $HOME/.config.
Project instructions are discovered from the repository root through the current working directory. In each directory, AGENTS.override.md takes precedence over AGENTS.md. Outside a repository, only the current working directory is searched. Global instructions precede project instructions, followed by explicit -d entry points. Duplicate canonical paths are included once. Files may be symlinks whose canonical targets are regular files; discovery does not read their contents. Broken links, symlink loops and non-regular targets report an instruction discovery error. The model is given safely quoted paths and reads relevant guidance with normal tools, subject to those tools' file and output limits. An empty override still takes precedence. Per-turn metadata records advertised paths, not content hashes or proof of reading. This replaces the earlier pre-1.0 instruction metadata format in place: sessions containing populated old instruction metadata cannot be resumed by this build. Their existing logs are not deleted.
Set read_agents_md=false under [agent] to disable both global and project discovery, but not explicit -d roots. The default working-document search location is the current working directory. Useful notes retain findings, decisions, scoped corrections and unfinished work. Goal state is controlled through the goal commands and tools. Verify changeable facts when resuming, keep proposals distinct from approval, and preserve local scope on each host. No notes layout, recursive Markdown scan, or memory service is required.
A WORKNOTE.md file beside the AGENTS pair is the session's work note. It is resolved from the current working directory first, then the global root; the first found wins. It is not an instruction source. Its content is read fresh before each request and injected as bounded context (at most 64 KiB, keeping the tail with an omitted-prefix marker) at turn start, after compaction and on recovery, under a "context, not authority" caveat; a missing or empty note injects nothing.
Environment
- HOME, USERPROFILE
- HOME supplies the default home directory. Native Windows falls back to USERPROFILE when HOME is unset or empty. Use --dotdir DIR when neither provides a suitable private state location.
- HOME
- Builds the default dotdir and fallback global-instruction path. Native Windows uses USERPROFILE when HOME is absent or empty.
- XDG_CONFIG_HOME
- Selects the global-instruction directory. It must be absolute.
- EDITOR
- Command used by /config.
- USER
- Supplies the default operator nick when valid and distinct.
- NO_COLOR
- Disables color in auto mode when present.
- SSL_CERT_FILE
- A nonempty value selects a CA file for provider, login and refresh HTTPS connections, including HTTPS proxies. It replaces the standalone build's embedded CA data, or the native build's default CA file. Certificate and hostname verification remain enabled. Keep this file trusted and current.
- TERM
- Enables terminal capabilities and color detection.
- LC_ALL, LC_CTYPE, LANG
- On POSIX, select a UTF-8 locale using normal locale precedence. Android selects Bionic's built-in C.UTF-8 locale directly. Android character widths and regular expressions use statically linked Unicode libraries. Native Windows and the FreeBSD 5.1-based production target use explicit UTF-8 processing and do not require installed UTF-8 locale data.
- PATH
- On POSIX, used to resolve an executable invoked without a slash for the printed resume command. Native Windows obtains the running executable from the operating system instead.
- api_key = ${ENV_NAME}
- An explicit environment credential source must contain a nonempty value of 1 through 16384 ASCII bytes without whitespace or controls.
- secret = ${ENV_NAME}
- Names an additional value hidden from tool children and redacted from output.
Troubleshooting↑
Common problems
- The downloaded program will not start.
- Match both CPU architecture and OS/ABI to its download row. On Unix, check execute permission and filesystem execution policy; an existing ELF file may still report "not found" when its required loader is missing. BSD legacy and current binaries use distinct base-library ABIs. On Windows, keep .exe and use .\snajpagent.exe or a full path; do not run Unix shell commands in cmd.exe. For macOS quarantine, follow Install on macOS after verifying and trusting the file. Keep system-wide security protections enabled.
- The checksum command is missing on a legacy system.
- Use a SHA-256-capable trusted machine to verify the download, then transfer it over a trusted channel. NetBSD 2.0 predates base-system SHA-256 support; a CRC from plain cksum is not the published SHA-256 digest.
- A documented fix is missing in an open session.
- Check the startup identity and the output of snajpagent -V for the executable being launched. Replacing a file on disk leaves already-running processes on their old code. Exit normally and use the printed resume command. Stable release downloads and the current-source manual can describe different revisions; use the manual shipped with a release when checking that exact release.
- /history appears to contain only one exchange.
- The default replay count is one, independently of total history length. Read the total and completed counts in the opening header and footer; /history 0 shows counts alone and /history N requests more turns. An old binary may lack the current count display. The Up/Ctrl-R prompt-entry history is a separate list.
- A command is acknowledged but has not taken effect.
- Read its boundary acknowledgement. Model/effort changes affect a full turn; configuration editing, catalog discovery, compaction and lifecycle controls may wait for the current response/tool boundary or confirmation to finish. Deferred intent in an established session survives resume. /exit leaves unapplied controls pending rather than opening an editor during shutdown. An active turn alone is not a reason to reject a valid command.
- The prompt shows ?% for context.
- Usage and capacity are different facts. A comparable request bound exists, but the selected provider/model has no known effective input budget. Inspect /status and /model. Refresh advertised limits with /model cache or configure a justified model-limit rule. The rule's provider name must match the selected local provider. Do not invent a capacity or confuse bytes, provider usage, the conservative request bound and the total context window. The percentage is rounded up against the effective input budget, which can be smaller than the configured total after safety/output reserves.
- Tab did not queue my text.
- Tab queues during an active turn; while idle it inserts spaces. Use /queue TEXT for an idle, initially paused addition. An empty draft switches views. A leading /command word or a chat @nickname at the cursor invokes completion instead, even when the cursor is at the word's end. A unique match adds a space. Continue writing, then press Tab at the end of the finished message to queue it locally. If there is no match, correct the name or move past the word; it is not silently queued. Use Enter for slash controls and room messages. During active work, /queue TEXT explicitly queues a future prompt. An open queue editor updates the selected item rather than appending a new one. The saved entry survives restart; unsent editor changes do not. If the queue was explicitly armed while editing, resume can run the saved entry.
- There are queued prompts but nothing is running.
- Idle additions, explicit turn cancellation, terminal failure, a new direct prompt and an open queue editor can pause the queue. Inspect /queue and use /next to arm it at idle or during active work. Arming persists across resume. An open queue editor prevents automatic draining. A paused queue cannot be skipped by automatic goal continuation.
- A goal continued when I resumed.
- Resume preserves its saved state, and foreground exit does not pause an active goal. Use /state goal pause when you want automatic continuation to stay paused, then exit. Use /state goal status to see the saved wording, status and any blocker. No background daemon runs after the process exits.
- My message was not sent to the room.
- Ordinary rollout text is local input. Select chat or use an explicit numbered send. Offline chat restores an unsendable draft instead of silently running a private turn. Check /names and /status for actual connection/join state; a requested connection is not an established one. Use accepted nicks after collisions, not the original preference.
- I cannot see the tools or the model's answer.
- Switch to rollout. Chat only contains public room traffic; ordinary assistant responses are not automatically published. /verbose 1 shows compact tools, 2 adds previews, and 3 shows retained arguments/results subject to their capture limits. Avoid higher diagnostic levels on shared terminals unless their sensitive contents are acceptable.
- The model name is not listed or the cache is old.
- Typing a model ID is allowed; the provider decides whether it is usable. /model reads only local cache. /model cache makes an explicit authenticated discovery request. A failed refresh does not replace a working cache. For IDs containing slashes, quote the component or use a numbered row to avoid selector ambiguity. A different local provider name can also change which configured limits and source-bound observations apply.
- Authentication or token counting failed.
- Use snajpagent login status to inspect the configured source without a remote request. An explicit missing key or file does not fall back to stored login. Check the provider URL, auth method and selected provider before changing anything. A count endpoint failure is not necessarily a generation failure: auto falls back only on definitive unsupported capability, while true requires exact counting. Choosing false disables preflight, not the hard capacity guard.
- A tool result says running or timed out.
- A yield or foreground deadline hands back a still-managed command. Wait using the existing handle; do not duplicate execution. A user interruption or process exit closes managed work. Resume restores recorded context, not operating-system processes. After a failed turn, /retry uses retained context/results and does not replay completed tools.
- Configuration reload failed.
- /config leaves invalid edited text on disk while keeping the previous valid runtime configuration. Fix the file before restarting. Only changed IRC fields replace current runtime network state; unrelated edits preserve it.
- Session resume reports a lock or incompatible format.
- A live session cannot be resumed concurrently. Do not delete its lock to start a second owner. Pre-1.0 internal formats can change; preserve older logs and use a compatible binary to inspect them rather than rewriting or deleting unknown data. Session deletion does not erase shared prompt history.
Provider message phases
A streaming assistant message can begin with a provisional commentary or final_answer phase. A message may already be completed when first announced. Empty text placeholders accompanying usable messages or tool calls are omitted; an empty final does not turn commentary into a final answer, and empty refusals remain invalid. A genuinely empty response still receives the normal correction. Its first completed snapshot determines the phase retained for tool follow-ups and session resume. Live text keeps its original streaming presentation; completion reconciles any remaining text once. A conflicting phase in a later completed snapshot is a protocol error, as are inconsistent message identity or content. This supports providers such as DeepSeek Flash that finalize pre-tool text as commentary after initially announcing a final answer.
Provider reasoning streams
Completed reasoning items are retained in the private session journal and returned to the same provider, endpoint, upstream model and credential identity in their original order alongside tool calls and results. Plaintext reasoning, summaries and opaque encrypted state remain separate from public messages and executable tools; history display and redacted protocol traces omit their payloads. The provider's token counts still include reasoning usage.
DeepSeek V4 Pro and V4.1 Flash can use their native Responses endpoint with thinking enabled; select a configured provider with, for example, -m deepseek/deepseek-v4-pro/high. Public text, refusals and tool arguments still require consistent item identities, nonnegative stream indexes and a valid terminal response. Sparse indexes are resolved by item identity; a nonempty terminal snapshot supplies canonical item order and must retain observed identities. Protocol errors in these fields remain failures; reasoning alone cannot complete a turn or execute a tool.
Resume preserves retained continuation. Changing provider, endpoint, upstream model or API key omits incompatible private state from subsequent requests; OAuth token refresh retains continuity when the account ID is unchanged. Ordinary messages, summary text and paired tool results remain available across model switches. The covered compaction boundary remains durable while opaque private compact items from another binding are omitted. The selected binding performs any required tail compaction or summary reduction against its own capacity; the previous binding is not restored for request preparation. A smaller target context may require several bounded chunks, each advancing the saved boundary. Failed and cancelled turns retain that progress and the selected model across resume, and completed tools are never re-executed. Older sessions cannot recover reasoning discarded by an earlier executable. Plaintext provider reasoning is sensitive journal data: protect the session directory and its backups as you would the conversation.
Provider failures
Provider HTTP requests use HTTP/1.1, including HTTPS Responses streams, to avoid HTTP/2 framing failures from intermediaries during long responses. TLS certificate and hostname verification remain enabled. Each exchange has its own connection; provider requests do not share a multiplexed HTTP/2 connection.
If a provider stream closes before response.completed, the incomplete response enters ordinary recovery. Public text remains retained; unadmitted tool proposals from that response do not execute. Completed tool results from earlier responses remain available. A warning identifies automatic recovery while the session continues working.
Valid structured keepalive events may arrive before, during or after a response. They leave completed responses successful and do not trigger recovery or repeat their output. Malformed records, conflicting event types, duplicate completions and additional response data after completion remain protocol errors.
Ordinary failures that otherwise offer /retry receive up to five automatic retries per consecutive failure sequence by default. A successful actionable response renews the budget for later failures. Configure max_turn_retries in the agent section. Retries continue the same logical turn with retained context and tool results; they do not replay completed commands or duplicate the original prompt. Steering and explicit interruption remain responsive during backoff. Fresh queued or background chat input at a failed request keeps its normal next-turn handoff. Successful refusals and ordinary failed tool results retain their existing handling. The retry hint appears once when the ordinary budget is exhausted or disabled.
An oversized SSE line reports its recognized event type when present, without echoing provider content. The existing 1 MiB line bound still applies.
An active goal retries failed work indefinitely with paced, interruptible waits. This includes compaction failures and exhausted preparation-attempt budgets. Provider capacity exhaustion, protocol errors, context recovery failures and local prerequisites do not pause or block the goal. Recovery preserves completed tool effects, partial output and unsettled command handles. Unsafe local state must be reconciled before new tools are admitted. Credentials are read again on each attempt; next-turn model settings keep their normal boundary. Explicit pause, cancellation and exit remain available during backoff. An exited process resumes work when its saved session is reopened.
Consecutive recovery failures appear as one bounded host-generated summary in model context. Detailed errors remain in the session log, and repeated terminal notices are rate-limited. A provider refusal does not authorize bypassing its policy or changing the request's purpose.
Transient transport, HTTP rate-limit/server, and structured Responses stream failures receive at most two retries of the same request, with bounded backoff and cancellation available during the wait. Earlier turn/tool work is not replayed. Additional live chat, steering or queued input prevents further retries, including during backoff. Steering keeps its immediate handoff; background chat and queued turns retain their normal admission rules, without interrupting a healthy response. When new input stops retrying a failed response, armed queued turns proceed automatically, without an extra /next. View/status controls alone do not veto retries. Stream retries stop once output or tool activity has been observed; malformed or ambiguous partial records are not automatically replayed.
Policy, denied access, quota and unknown structured failures skip the short transport retry loop. An HTTP 5xx upstream_error with server_error can include a temporary gateway access-verification failure; it receives the same bounded retry of the unchanged request as other transient server errors. It does not trigger model-facing policy clarification. Access, quota and unknown failures retain paced turn recovery. For an eligible cyber_policy rejection, the model receives a durable clarification instruction immediately, with up to five attempts per consecutive clarification sequence. The count resets when the response continues normally or clarification stops, including when its limit is exhausted. Reasoning events and ordinary assistant text, including incomplete messages, permit clarification. Local function-call proposals also permit it: functions are admitted only after a successful response. Calls from a failed response are discarded, including complete arguments, and neither execute nor enter the next request. Delivered text is retained in the session event log and the next model request. The goal remains active during these attempts. The original task remains intact: the instruction asks for accurate restatement of ambiguous phrasing with purpose, actions, targets and authorization unchanged, and operator clarification where needed. New input or observed refusal, provider-executed tool or unknown activity prevents another automatic clarification. After five clarification attempts, a further rejection stops automatic retries and pauses an active goal. Other policy rejections also stop automatic retries. A policy pause is recorded as a provider rejection; an explicit model refusal has a separate pause reason and notice. Existing running commands are retained while the policy-stopped turn waits. The input prompt stays hidden while the command is retained. Press Ctrl-C to leave that turn before submitting a clarification or another command; retained commands are not replayed. There is no provider switch or concealed task scope.
Rollout shows one concise "provider clarification N/5" notice at every verbosity level. Existing levels 4, 5 and 6 add event, redacted protocol and wire details. Structured error codes or types, not message wording, determine eligibility. Policy failures record the structured code/type and the reason clarification stopped, including the first unsafe stream event and its output-item type, or the exhausted clarification limit. A failed stream never becomes a successful response merely because the connection closed.
Building and installing↑
Binary releases
Release availability and platform compatibility are listed at https://agent.snajpa.net/downloads.html. Every new version, including patch releases and prereleases, ships all implemented production targets listed by the Makefile's PROD_TARGETS. Additional implemented targets join subsequent releases; unfinished ports are not claimed as available. Experimental builds are labeled with their actual testing scope, not silently omitted. See RELEASE.md in the source tree for the publication policy.
Each release provides platform-specific executable downloads, SHA256SUMS, source/build and license material, the manual, and separate matching symbols. Verify the downloaded executable against the release checksum before use. On Unix, rename it to snajpagent and make it executable with chmod +x; on Windows retain the .exe extension. Debug symbols are not required to run. Version 0.99.1 is the first binary release. Source builds remain available below.
The downloads page also provides separate runnable debug builds. These use unstripped application code with debug information and frame pointers; dependency libraries remain optimized. macOS packages include the executable and its dSYM. Production symbol archives remain available for diagnosing their matching production executables.
Multimodal source-build dependencies
Build from a Git checkout using GNU make, a C11/POSIX compiler with pthreads, libcurl, Jansson, and FFmpeg library development files (libavformat, libavcodec, libavutil, libswresample and libswscale), plus a C++20 compiler and Poppler/libpng development files for PDF rendering. These instructions describe the normal POSIX build. An experimental native Windows x86-64 package is also available; see the standalone target below and DEPENDENCIES.md for its current qualification scope. Plain make builds only for the host, with size optimization and stripping. Run ./configure first for a best-effort build that probes the optional modalities and reports what it found. ELF production packaging needs strip and objcopy; macOS needs strip and dsymutil. The build version uses the latest reachable Git tag; untagged commits add their short revision, and modified checkouts add a -dirty suffix. After a development tag, the current revision replaces the prior Git suffix; the approved version base stays unchanged. META defines the product name only. A tagged Git history is required; portable build targets pass the same derived identity into their isolated source build.
- ./configure
- Probes the compiler and the four optional modalities, tunes the tracked config.mk with the values it found, and prints a note for each enabled or disabled component. A missing component disables its modality without failing the build; --require-X makes that absence an error, --with-X forces the state on, and --without-X skips its probe. Office takes one of three mutually exclusive states: the linked LibreOfficeKit import, the commanded soffice backend, or none. The linked import also needs the PDF dependency, because Office page validation uses Poppler, so a host with the kit but no Poppler is reported as commands with that reason rather than as Office not found. By default the kit is chosen when a runtime and that dependency are present, otherwise the commands backend when a soffice binary is found. Name a state directly with --with-office, --with-office-commands or --without-office; the two on-states are mutually exclusive. --office-root selects the installed runtime root, --host sets the cross target and maps the compilers, resolving the cross C++ best-effort: a linked-PDF build without one asks for --cxx or --without-pdf. --prefix and --manprefix set the install and manual prefixes, and --cc, --cxx and --debug are also accepted. GNU packaging flags are accepted and noted: the standard directory and Autoconf-style options are ignored, --mandir is a synonym for --manprefix, and a --host naming this machine is treated as native. The install layout stays --prefix and --manprefix. An option the script does not know exits 2 with the usage text and leaves the tracked config.mk untouched. Export PKG_CONFIG_PATH for the build as well as for the probe; without it the probe finds no dependency and turns the modality off. The script needs a POSIX shell providing command -v; the pinned legacy targets build through their own toolchains rather than running it; the Nix app derivations set dontConfigure and take their modality values from the tracked config.mk, so no sandbox probe runs. The make WITH_*=0/1 options below remain explicit overrides.
- make WITH_AV=0
- Build a custom lean executable without linked audio/video file decoding. Audio/video file inspection returns an explicit unsupported-build error. Image normalization is excluded; native PNG/JPEG forwarding remains available. Speech generation and text tools remain available. Official desktop releases use WITH_AV=1. AV_CFLAGS and AV_LIBS override pkg-config flags for cross-builds. The macOS cross-build supplies static file-codec libraries and miniaudio headers for the selected SDK/deployment target. PDF builds use static Poppler/font libraries with the SDK C++ runtime and host font lookup. Office runtime packaging is never bundled: it runs from a separately installed runtime. Dependency cross-links do not establish target rendering or playback/capture. The OpenBSD 7.9 PDF recipe likewise supplies static rendering/font libraries, uses the SDK libc++ runtime and reads the host /etc/fonts configuration. The OpenBSD 5.9 and 3.5 recipes use the shared static GCC 14 C++ runtime with the same font configuration paths. The NetBSD 10.1 PDF recipe uses native libstdc++ and /etc/fonts with static rendering/font libraries. NetBSD 2.0 uses static GCC 14 libstdc++ and the native exception unwinder with the same font configuration paths. The FreeBSD 5.1 and 8.4 recipes supply static rendering/font libraries and GCC 14 libstdc++, with native threading, libc and the exception unwinder. Fontconfig reads /usr/local/etc/fonts and the host font directories.
- make WITH_PDF=0 WITH_OFFICE=0
- Build a custom lean executable without PDF/Office rendering or their C++ runtimes. WITH_PDF=1 is the default. CXX/CXXFLAGS and PDF_CFLAGS/PDF_LIBS select the compiler and matching Poppler/libpng headers and libraries. Windows PDF source builds use native Windows font lookup. The portable multimodal Windows build remains experimental; cross-build qualification is described in DEPENDENCIES.md. The Windows target takes the commanded Office state, like every release artifact: no Office library is linked and an engine installed on the target is resolved when it runs, while host builds whose installed runtime provides LibreOfficeKit keep the linked import enabled.
- make WITH_OFFICE=0
- Build a custom executable without linked Office import; WITH_OFFICE_COMMANDS=1 selects the commanded engine instead of the unsupported-operation stubs. The default WITH_OFFICE=1 requires WITH_PDF=1. OFFICE_ROOT selects the LibreOffice runtime directory; an absolute path selects a native installation, while a relative path is resolved from the actual executable directory, independent of the working directory. Drive and UNC roots are retained on Windows; document/profile file URLs encode spaces, non-ASCII names and URI punctuation. Supply explicit OFFICE_CFLAGS and OFFICE_LIBS for the build-time headers/libraries; there is no runtime PATH search or download. OFFICE_CFLAGS and OFFICE_LIBS supply LibreOfficeKit, libarchive and libxml2 headers/libraries. The runtime is separately installed and never bundled with the build; matching fonts come from the host installation. The Nix-built Linux release artifacts take this variant, because the Office dependency in that closure is a static kit that would be linked into the artifact; host builds keep the default and link a separately installed runtime. The runtime is verified before it is loaded: its root must hold the LibreOfficeKit library or the engine executable under a program directory, and a root that already names that directory is accepted. A missing or incomplete installation fails with a message naming the root and the expected component instead of a load failure. The linked import is loaded from the verified root without a runtime PATH search, and detection never relies on a conversion command's exit status. The commanded backend links no Office library: it drives the engine installed on the target, resolved from OFFICE_ROOT first, then PATH, then the usual library directories and versioned LibreOffice roots; when none is found it names the roots it searched. It converts documents to PDF, including page ranges, and refuses sheet-area selection and out-of-range pages with a clear message because the command line has no equivalent; lossy CSV export and unbounded workbook substitution are not offered. Success is judged from the produced file rather than the engine's exit status. Linux applies syscall and available filesystem confinement. Other POSIX targets report unavailable OS confinement and retain package checks and worker limits. Windows applies job CPU/memory limits and an elapsed timer; OS filesystem/network/exec confinement is reported unavailable. Windows, macOS, FreeBSD, OpenBSD and NetBSD recipes include static ZIP/XML package-reader dependencies. The LibreOffice runtime and its components are still required; portable Office packaging is never bundled and is resolved from a separately installed runtime.
- make WITH_AUDIO_DEVICE=0
- Build a custom executable without capture, playback or audio-device enumeration. The tracked config.mk defaults to WITH_AUDIO_DEVICE=0; configure can enable available device support. Linux, macOS, Windows and BSD production recipes explicitly enable it. MINIAUDIO_CFLAGS supplies the path to miniaudio.h; AUDIO_DEVICE_LIBS overrides platform backend link flags. Device audio also links SpeexDSP for duplex echo reduction. AEC_CFLAGS and AEC_LIBS select a nonstandard SpeexDSP installation; otherwise pkg-config is used. The device adapter uses miniaudio's low-level API, without its engine or decoders. FreeBSD uses OSS. With older OSS headers, device enumeration reports only the accessible default /dev/dsp node; native format/version metadata is unavailable. Capture/playback initialization still negotiates format, channel count and rate. NetBSD uses miniaudio's audio(4) backend and the native loader API without libdl. On NetBSD without POSIX priority scheduling, audio threads use normal priority. Its source recipe supplies static file codecs; target audio qualification and PDF/Office runtime closure remain pending. OpenBSD uses miniaudio's sndio/audio(4) backends and the system dynamic-loader API. sndio capture forwards complete received frames, including split-frame reads; a short blocking playback write fails the device operation. The source recipe supplies static media codecs. The intermediate OpenBSD 5.9 direct-audio API remains paused through initialization, starts explicitly and stops by pausing and clearing queued device data without a blocking drain. OpenBSD 3.5 uses normal-priority audio threads because its native libraries lack the declared scheduler priority-range functions. Stack-size handling remains available. Physical audio and target-runtime qualification remain pending. Portable Linux builds link PulseAudio and ALSA clients. A PulseAudio-compatible server must already be running; the agent does not start one. ALSA uses the host's /usr/share/alsa configuration; ALSA_CONFIG_DIR can select another configuration directory. External shared ALSA plugins cannot be loaded by the static executable.
- make DEBUG=1
- Build unstripped debug code with frame pointers. Both profiles keep the same features and runtime checks. Switching profiles does not require cleaning; do not run different profiles concurrently in one worktree.
- make install
- Build and install production under /usr/local, with executable mode 0755 and manual mode 0644. Use make DEBUG=1 install for a deliberate debug installation. PREFIX changes the prefix; MANPREFIX defaults to $(PREFIX)/share/man. DESTDIR stages both paths without installing on the host.
Source builds
Use GNU make for this Makefile. On the BSDs, install GNU make and use gmake for every make invocation in this manual; the base BSD make does not parse this Makefile. Install a compiler, pkg-config, libcurl and Jansson development files through your platform's package tools. For non-default library prefixes, set the Makefile's JANSSON_CFLAGS/JANSSON_LIBS and CURL_CFLAGS/CURL_LIBS as needed. The pinned standalone recipes below provide the separate native Windows build route.
Build from a Git checkout with a C11/POSIX compiler and pthreads. These instructions describe the native POSIX build; DEPENDENCIES.md records each platform's requirements and qualification scope. Plain make builds only for the host, with size optimization and stripping. ELF production packaging needs strip and objcopy; macOS needs strip and dsymutil. For a release, create its approved Git tag on the clean source commit first, then run make or make prod-matrix without a version override. The build version uses the latest reachable Git tag; untagged commits add their short revision, and modified checkouts add a -dirty suffix. After a development tag, the current revision replaces the prior Git suffix; the approved version base stays unchanged. META defines the product name only. A tagged Git history is required; portable build targets pass the same derived identity into their isolated source build.
- make DEBUG=1
- Build unstripped debug code with frame pointers. Both profiles keep the same features and runtime checks. Switching profiles does not require cleaning; do not run different profiles concurrently in one worktree.
- make install
- Build and install production under /usr/local, with executable mode 0755 and manual mode 0644. Use make DEBUG=1 install for a deliberate debug installation. PREFIX changes the prefix; MANPREFIX defaults to $(PREFIX)/share/man. DESTDIR stages both paths without installing on the host.
- make help
- List targets and overrides without building or contacting a provider.
- make check
- Run existing local tests and source checks. Tests need Python 3 and Perl; tmux enables the terminal integration tests.
Explicit CC, CPPFLAGS, CFLAGS and LDFLAGS replace their profile defaults; required identity and thread flags remain. Production defaults to -Os and debug to -Og. Neither default adds host-specific instruction sets or mandatory LTO. STRIP, OBJCOPY and DSYMUTIL override packaging tools.
Production retains matching symbols beside the build executable: debug-snajpagent on ELF or snajpagent.dSYM on macOS. They are not installed or needed to run the program. Keep them for diagnosing that exact binary; a later debug rebuild is not a substitute.
A user-local source installation, without sudo:
make make PREFIX="$HOME/.local" install export PATH="$HOME/.local/bin:$PATH" man "$HOME/.local/share/man/man1/snajpagent.1"
Use gmake on the BSDs. The downloaded release also includes snajpagent.1; read it with man ./snajpagent.1 on a system with a man reader, or use the web manual. Windows users can read the web manual without installing Unix tools.
Standalone builds
Native POSIX defaults select 64-bit file offsets, including on 32-bit libc builds. Preserve _FILE_OFFSET_BITS=64 when replacing CPPFLAGS; it does not enlarge the process address space or establish old-kernel compatibility.
These explicit targets use the pinned Nix recipes and can download/build dependencies. They do not replace the normal host executable or start VMs. Independent targets can run together with make -jN.
- make prod-linux-x86_64, make prod-linux-aarch64, make prod-linux-i686
- Build a static-PIE Linux executable with embedded CA data. Copy build/matrix/linux-ARCH/bin/snajpagent to the destination; Nix and third-party runtime libraries are not required there. Optional matching symbols are in the adjacent .debug/ directory. These target x86-64, ARMv8-A or i686 CPUs. The download page uses conservative libc/architecture kernel baselines: 2.6.39 for x86-64, i686 and ARMv6; 3.7 for ARM64; 3.13 for PowerPC64 little-endian; and 4.15 for RISC-V64. These are not experimentally established oldest-working kernels. Older kernels can work, as below. The x86-64 executable has also been exercised on Debian Sarge's Linux 2.6.8-12-amd64-generic, including read-only tools, parallel commands, PTY, session resume and TLS trust/hostname checks. The same target supports CentOS 3.9's backported Linux 2.4.21-50.EL x86_64 kernel, exercised with base/IRC tests, read-only enforcement, parallel commands, PTY, resume, TLS verification and hostname-based provider connections in QEMU. This coverage is specific to the tested kernel releases. The i686 executable is 32-bit with 64-bit file offsets and musl time_t; Pentium II/III CPU probes do not establish Linux 2.4 kernel support.
On Linux, filesystem operations first use native descriptor-relative calls. If the kernel reports ENOSYS, the platform layer uses the held directory through /proc/self/fd and older open/lstat/link/rename/mkdir/unlink calls. This fallback requires working procfs descriptor traversal; it does not mount procfs, change the process working directory or invoke external commands. No-follow, file-type, private-permission and exclusive-creation checks remain. The extra pathname prefix can reduce the usable path length, and procfs replacement or concurrent descriptor closure is not made race-free. Legacy close-on-exec setup is non-atomic. Descriptor duplication also falls back to dup-plus-fcntl when the atomic close-on-exec command is unavailable; failure to set close-on-exec closes the new descriptor and returns an error. These fallbacks alone do not qualify a complete old-kernel runtime: threading, clocks and dependency syscall requirements still apply. The standalone Linux libcurl uses its nonblocking pipe-and-fcntl wakeup backend, avoiding a runtime requirement for eventfd or pipe2 while retaining wakeup and asynchronous DNS support. If Linux lacks waitid, the process adapter checks its own child's parent and zombie state through procfs without reaping. Normal collection still uses waitpid, retaining the process-group leader until family cleanup is finished. This fallback requires readable /proc/PID/stat files. The OS must supply secure entropy. Mbed TLS retains /dev/random; on Linux before 5.6, it may block when entropy is depleted even after initial seeding. Maintain the system's entropy source rather than weakening TLS randomness. Official Linux recipes select Mbed TLS for provider HTTPS, native voice DTLS and SRTP. They build libarchive with Nettle cryptography.
The x86-64 and legacy i686 targets share the old-kernel clock fallback described below. Musl's translated clock error is checked with a raw syscall; emulation requires the kernel to report ENOSYS.
- make prod-linux-armv6
- Build the shared ARMv6KZ/VFPv2 hard-float static PIE using the pinned muslpi recipe. It covers Raspberry Pi 1/Zero-class ARMv6 and ARMv7 systems with a hard-float environment; other ABIs need a separate build and qualification. Libraries, TLS and trust roots are embedded. Output is build/matrix/linux-armv6/bin/snajpagent. Existing QEMU checks cover Alpine 3.22.5 ARM1176 and Cortex-A7 guests; physical boards and other kernels need separate qualification. Emulator device-tree and clock adjustments in those checks are not physical-board performance evidence. The configured shell and external commands still belong to the target system.
- make prod-linux-riscv64
- Build an RV64GC/LP64D static PIE with the pinned musl recipe, application libraries, TLS and trust roots included. Output is build/matrix/linux-riscv64/bin/snajpagent. Existing QEMU RV64 checks cover Alpine 3.22.5/Linux 6.12.94, including native inspection, commands, PTY, resume and TLS. Earlier kernels and physical-board performance remain unqualified.
- make prod-linux-ppc64le
- Build the little-endian POWER8 ELFv2 static PIE with the pinned musl toolchain. Application libraries, TLS and trust roots are included. Output is build/matrix/linux-ppc64le/bin/snajpagent. Existing QEMU POWER8 checks cover Alpine 3.22.5/Linux 6.12.94. Big-endian, pre-POWER8 and earlier-kernel systems require separate qualification.
- make prod-linux-ppc32
- Opt-in unsupported build outside the production matrix. It is not shipped as a 0.99.8 executable or update channel. Build a 32-bit big-endian PowerPC hard-float static PIE, with static compiler atomics and embedded application libraries, TLS and trust roots. Output is build/matrix/linux-ppc32/bin/snajpagent. Existing QEMU PowerPC 750 checks cover Debian 8.11/Linux 3.16.0-6-powerpc. Soft-float systems and earlier kernels remain unqualified. The target must provide secure OS entropy; old Linux /dev/random can block if depleted.
These three production targets and the optional PPC32 build need no ELF interpreter or shared application libraries. Matching symbols are kept beside each matrix executable. See DEPENDENCIES.md and QUALIFICATION.md for the scope of earlier tests; a documentation refresh or a new compilation does not establish additional runtime coverage.
- make prod-linux-i686-legacy
- Build a separate static non-PIE i686 executable using uClibc-ng/LinuxThreads and GCC's pthread-key TLS emulation, with embedded CA and locale data. Output is build/matrix/linux-i686-legacy/bin/snajpagent, with optional symbols in the adjacent .debug directory. Actual Linux 2.4.27-3-386 on QEMU Pentium III has exercised base/IRC checks, read-only enforcement, parallel commands, PTY, resume, TLS verification and a hostname-based local provider connection. This is not a claim for every 2.4 kernel or a paid provider test. Prefer the modern i686 static build where it runs; the legacy variant requires working procfs, secure OS entropy, and devpts for PTY commands. LANG=en_US.UTF-8 selects an included UTF-8 locale.
Native kernel interfaces stay first. Rejected atomic socket flags fall back to checked fcntl nonblocking/close-on-exec setup. If kernel clocks are absent, realtime uses the old gettimeofday syscall and monotonic time uses /proc/uptime at 10 ms resolution. The standard i386 jiffies wrap is extended while late samples are clamped; samples must be less than about 248 days apart to disambiguate wrap. Wall-clock time is never used as a monotonic substitute. Legacy flag setup remains non-atomic; no external helper command is invoked.
- make prod-macos-arm64, make prod-macos-x86_64
- Cross-build for the selected macOS deployment target with static application libraries and CA data. Capture, playback and realtime voice are enabled; the executable uses the system libraries and audio frameworks. Output is build/matrix/macos-ARCH/bin/snajpagent, with an adjacent matching dSYM.
- make prod-macos-universal
- Combine both Mac slices into a native Mach-O universal executable and dSYM under build/matrix/macos-universal/bin/.
The macOS cross-builds are experimental: cross-built and inspected, not yet runtime-qualified on macOS. No Developer ID signing or notarization is implied. ARM64 has a linker-generated ad-hoc signature; the Intel slice is unsigned. Cross-compilation alone does not establish runtime support.
- make prod-matrix
- Build all currently implemented standalone production targets: Linux x86-64/AArch64/ARMv6/RISC-V/PPC64LE/i686, macOS ARM64/Intel/universal, Windows x86-64/ARM64, and FreeBSD/NetBSD/OpenBSD with separate legacy ABI targets. The deferred Linux i686 legacy and unsupported opt-in PPC32 recipes remain available separately. Targets use separate build/matrix outputs and shared immutable Nix inputs. Concurrency is selected from online CPUs, current host load and available memory, reserving 4 GiB for the host and 4 GiB per target; GNU make's load limit delays new target starts if host load increases. Missing resource data defaults to one target. Each recipe permits one Nix job and core. SDK availability does not silently remove targets. Use make -k prod-matrix to continue independent targets after a failure; the command still fails and preserves successful outputs. Reruns reuse completed Nix builds. DEBUG=1 is rejected. This does not install, replace the native executable, boot VMs, or contact a model. Cross-build success does not establish runtime qualification.
- make prod-freebsd-amd64
- Build a FreeBSD amd64 executable with static application libraries and embedded CA data. The system must provide libc.so.7, libthr.so.3 and /libexec/ld-elf.so.1. Copy build/matrix/freebsd-amd64/bin/snajpagent to the target; optional matching symbols are in the adjacent .debug directory. Runtime qualification covers FreeBSD 8.4 and 14.4 base/IRC tests, internal read-only inspection, parallel commands, PTY execution, interactive resume and TLS trust/hostname verification with local fixtures. Use a UTF-8 locale and mounted devfs. The executable is non-PIE at this old ABI baseline. Other releases remain unverified.
Source builds targeting pre-8 FreeBSD use validated pathname fallbacks for missing descriptor-relative filesystem calls. Avoid concurrent directory renames: external renames can race validation and access, and moves to a different parent require reopening the directory. Close-on-exec flag setup is non-atomic on these old kernels.
- make prod-freebsd-amd64-legacy
- Build the FreeBSD 5.1/5.5 amd64 agent with static application libraries and embedded CA data. Copy build/matrix/freebsd-amd64-legacy/bin/snajpagent to a system providing libc.so.5, libc_r.so.5 and /usr/libexec/ld-elf.so.1. Matching symbols are in the adjacent .debug directory. Mount devfs before use. This target includes UTF-8 regex and display-width support and uses the C character locale; installed UTF-8 locale files are optional. This legacy executable is non-PIE and its old system runtime has no stack-protector support; its stack is non-executable.
Actual FreeBSD 5.1 and 5.5 qualification covers base/IRC/SSE tests, internal read-only inspection and denied writes, parallel commands, PTY execution, interactive resume and TLS trust/hostname verification with local fixtures. Other releases remain unverified for this artifact. The pathname race limits above apply.
- make prod-openbsd-amd64
- Build the experimental OpenBSD 7.9 amd64 agent with static application libraries, UTF-8 regex/display-width support and embedded CA data. Copy build/matrix/openbsd-amd64/bin/snajpagent into the target system; matching symbols are in the adjacent .debug directory. The PIE executable requires libc.so.103.0, libpthread.so.28.1 and /usr/libexec/ld.so. It uses the C character locale with its static Unicode engine. Other OpenBSD releases require separate ABI qualification.
OpenBSD 7.9 checks cover base/IRC/SSE tests, read-only inspection and denied writes, parallel commands, PTY execution, durable resume and TLS trust/hostname verification with local fixtures.
- make prod-openbsd-amd64-legacy
- Build the experimental OpenBSD 5.9 amd64 agent at build/matrix/openbsd-amd64-legacy/bin/snajpagent with matching symbols in .debug. It requires libc.so.84.2, libpthread.so.20.1 and /usr/libexec/ld.so. Application libraries, Unicode support, compiler thread-local storage support and CA data are static. This PIE executable uses a separate ABI from the 7.9 output. Child exit observation preserves the owner's final wait and exit status. Base/IRC/SSE tests, read-only enforcement, parallel commands, PTY, interactive resume and TLS trust/hostname checks have been exercised on 5.9.
- make prod-openbsd-amd64-early
- Build the experimental OpenBSD 3.5 amd64 agent at build/matrix/openbsd-amd64-early/bin/snajpagent with matching symbols in .debug. It requires libc.so.30.3, libpthread.so.2.4 and /usr/libexec/ld.so, a separate ABI from the 5.9 and 7.9 outputs. Application libraries, Unicode, C99 formatting, compiler support and CA data are static. The native startup ABI uses a non-PIE executable with a non-executable stack and no compiler stack protection.
The 3.5 filesystem adapter validates directory identity and rejects symlinks, but pathname-based operations retain an external rename race; moving a held directory across parents requires reopening it. Close-on-exec setup is not atomic. Native /dev/urandom supplies entropy, and fsync supplies file durability. Child exit observation preserves the final wait and status. Base/configuration/SSE/IRC, read-only enforcement, parallel commands, PTY, CLI/interactive resume and TLS trust/hostname checks were exercised on 3.5.
- make prod-netbsd-amd64
- Build the experimental NetBSD 10.1 amd64 agent at build/matrix/netbsd-amd64/bin/snajpagent with matching symbols in .debug. It requires libc.so.12, libpthread.so.1 and /libexec/ld.elf_so. Application libraries, Unicode, TLS and CA data are static. The executable uses PIE, full RELRO, stack protection and a non-executable stack. Native directory-relative filesystem operations, non-reaping child exit observation and PTY commands are supported. Base/configuration/SSE/IRC, read-only enforcement, parallel commands, CLI/interactive resume and TLS trust/hostname checks were exercised on 10.1.
- make prod-netbsd-amd64-legacy
- Build the experimental NetBSD 2.0/5.2.3 amd64 agent at build/matrix/netbsd-amd64-legacy/bin/snajpagent with matching symbols in .debug. It requires libc.so.12, libpthread.so.0 and /libexec/ld.elf_so. NetBSD 10.1 uses a different pthread ABI. Application libraries, Unicode, compiler thread-local support and CA data are static. The native loader requires a non-PIE executable with two load segments; the build retains a non-executable stack without RELRO or stack protection (the NetBSD 2.0 runtime lacks the stack-protector symbols).
The filesystem adapter validates directory identity and rejects symlinks, but pathname-based operations retain an external rename race; moving a held directory across parents requires reopening it. Close-on-exec setup is not atomic. Native entropy and fdatasync supply randomness and file durability. Child exit observation preserves the final wait and status. Base/configuration/ SSE/IRC, read-only enforcement, parallel commands, PTY, CLI/interactive resume and TLS trust/hostname checks were exercised on 2.0 and 5.2.3.
- make prod-windows-x86_64, make prod-windows-arm64
- Build the experimental native Windows x64 or ARM64 executable using pinned Nix dependencies. Copy build/matrix/windows-ARCH/bin/snajpagent.exe to Windows. Application libraries and CA roots are included; Nix and third-party runtime DLLs are not needed there. Optional matching symbols are in the adjacent .debug directory. Runtime qualification is limited to tested modern Windows PE guests (x64 build 26100, ARM64 build 28000); full desktop and older Windows remain in progress. Plain make still builds only the host platform.
Windows terminal output selects native VT processing when available and native cursor, color, and erase operations for classic consoles. The classic path preserves the initial console palette and supports prompt editing and wrapping. Glyphs and text decorations depend on the console font; italics use underline where supported.
On systems without synchronous I/O cancellation, output uses an authenticated same-executable helper. Inheritable standard console buffers are retained at helper creation, including inactive buffers; other descriptors use exact handle transfer. An unsupported transfer reports an error.
Windows x64 and the internal i686 build use a hidden-console collector for PTY commands when ConPTY is unavailable. The same executable hosts collection; input and output use authenticated private pipes, and command descendants belong to the agent's cleanup job. The collector translates native console cells and keyboard events; font and code-page limitations of the underlying console still apply. ConPTY is selected whenever available.
See the repository's DEPENDENCIES.md for static dependency licenses, embedded trust, corresponding-source obligations and platform qualification details.
Examples↑
Start an interactive session in the current directory:
snajpagent
Execute a task from a script:
printf '%s\n' 'run the tests and summarize failures' | snajpagent -e
Resume the newest saved session:
snajpagent --resume --last
Host a room and connect another agent:
snajpagent -s localhost:6667 -n builder -o alice -r work snajpagent -c localhost:6667 -n reviewer -o bob
Security↑
The model and its tools run with the launching user's OS permissions across the filesystem. Calls require no per-call approval and are hidden at default verbosity. New sessions start with HOME as the working directory; OS process and filesystem isolation applies normally. snajpagent requires its state directories to be mode 0700 and creates state files mode 0600.
Prompt history intentionally retains submitted operator text across sessions and chat/rollout modes. It cannot identify a secret deliberately typed by the operator; remove or protect the history file with the same care as shell history. Session deletion does not erase it.
Provider credential variables and configured secret sources are removed from tool environments when environment-backed, and resolved values are redacted before tool output is stored and displayed. This does not decide which file contents are sent to a provider or published.
Provider redirects are not followed. The IRC server is unauthenticated and unencrypted; keep its default local listener unless the surrounding network is trusted or independently secured.
Verbosity 5 exposes prompts, source, tool output, and model content. Review terminal capture before sharing it.
See also↑
sh(1), tmux(1)
Project documentation:
https://github.com/snajpa/snajpagent
License↑
GPL-2.0-only. See COPYING and LICENSE_SCOPE.