pa-cli #
The prime-agent binary.
Scope #
Argument parsing matching the TS CLI exactly, mode selection (interactive/headless/json/daemon subcommands), binary wiring of crates into processes, exit codes and user-facing errors. The daemon-forensics CLI (incident.rs, TS src/cli/incident.ts): the prime-agent incident timeline command — log-source discovery (agent.jsonl plus its .old rotation, falling back to the newest per-daemon log), --since/--until window and --session filter parsing, and the report rendering; the shared classifier lives in pa-types (pa_types::incident) and the agents-view notice in pa-tui. Client-side MCP auth flows (mcp_login.rs): the /mcp login//mcp logout hook the TUI calls — the TS interactive client's placement — resolving a server (settings mcpServers + the builtin catalog), running the pa-core OAuth login on the suspended terminal, and persisting through the shared auth store. The same placement serves the TUI's client-command hooks: provider auth (provider_login.rs — the /login//logout catalog, the API-key store, the MCP device flow, the Prime Inference terminal login (prime_inference_login.rs — the API-key flow: prime-cli credential reuse, the paste prompt with the whoami access check, the numbered team selector); the subscription OAuth and the Prime browser challenge stay unported and report it), and the /update runner (client_update.rs — the out-of-band installer funnel with the output captured, the same body prime-agent update runs; installer_update.rs is that CLI command's own driver, --check included), plus the TS-parity staged update flow (update_flow/, self_update.rs — the package update self target's body, kept for the TS-parity package corpus).
Non-goals #
No business logic; it composes pa-daemon, pa-core, pa-tui, pa-ai via their public APIs only.
Public API #
The prime-agent executable surface (flags, subcommands, exit codes). Internal lib types pub(crate), plus the daemon wiring (interactive_mode::{ensure_daemon_running, ensure_daemon_running_with, resolve_socket_path}) exported for the integration verifier.
Depends on #
pa-types, pa-ai, pa-agent, pa-core, pa-daemon, pa-tui (one-way, composition root).
Print runtime #
The headless print/json modes (print_runtime.rs) drive the in-process
session engine directly: prompt admission, the turn-boundary compaction
checks (print_boundary.rs — the overflow compact-and-retry arm, the
model-requested compaction consumption, and the threshold arm), the json event stream, and the headless
terminal selection (stdout/stderr/exit code) mirroring modes/print-mode.ts.
Autonomous continuation (print_autonomous.rs, the #254 follow-up): the
composed in-run continuation hook (the pa-agent loop's
getContinuationMessages seam) runs the natural continuations inside
the one agent run (the TS shape: turn_end -> turn_start with the
continuation user row between them, no run boundary); the goal arm runs
first (exclusive priority), the boundary gates (queued input, a
requested compaction, the threshold arm) are shared, and a threshold
compaction holds its minted continuation for the boundary's queued
followUp admission (TurnBoundary::admit_continuation). The stop
surfaces only through the headless exit contract (TS: no row, no frame).
Goal continuation (print_goal.rs, the #252 residue): the --goal seed rides
the first turn; the in-loop continuation hook runs an active goal's natural
continuations inside the one agent run; the budget-limit steer and the
threshold-held continuation drain as this invocation's follow-up runs
with the TS session-action phase frames.
Inline subagents (print_rlm.rs): the daemonless print session hosts
rlm.spawn children in-process over a tree of session nodes — the
RlmSubagentHost impl (depth gate, name uniqueness, model resolution,
the child session/engine build, the kickoff row), the
AgentMessageController (family roster, one steer-lane delivery path),
the quiescence drive that holds the run open until every admitted
descendant settles and its replies and notices drain (the #2270 fix),
the goal/autonomous deferral behind unsettled work, and the exit
disposal (kernels snapshotted, drivers joined; no child outlives the
process).
Session slash commands (print_session_command.rs): /compact, /goal,
/autonomous, and active Lua commands share the pa-core executor. The stream
includes session_action_update phase frames around the durable echo row,
compaction_start/compaction_end for /compact, the unconditional
goal_update for /goal, result rows, and the settled queue frame.
A /goal start (or resume) drains its scheduled
continuation inside the same prompt wait; a failed command rejects the
prompt wait (the raw error to stderr, exit 1, no later prompts). Client
commands (/model, /tree, ...) and unknown slash input are rejected in headless
mode instead of becoming model prompts. Active native plugin commands use the same
core executor. /reload reconciles the plugin host without command history rows;
prime-agent prompt includes its active contribution inventory. Dynamic
context_contribution instructions are evaluated at the model boundary;
the prompt dump does not preview them.
Print mode joins admitted plugin jobs before the next input or process exit,
without waiting for future timers. New command-job failures reject that command;
automatic-job failures are diagnostics. JSON reports terminal jobs with
plugin_job_update rows. The optional harness supplies model-only context;
there is no canonical harness_digest pair or hardwired event.
The stream retains the session header (version 3), slim message_update
deltas, tool_execution_update, compaction events and outcome rows, and goal
session-action frames. Verifiers: print_runtime_e2e and plugin_headless_e2e.
Daemon client #
The daemon-backed public commands (list, stop, rename, send, schedule) talk to the
pa-daemon supervisor over its JSONL Unix socket through the crate-private client module
(daemon_client.rs). The client never spawns a daemon - the TS CLI only auto-starts one for
the internal daemon start/open commands, which are not reachable from the public surface.
Packaged layout / kernel packaging #
The release artifact is exe-adjacent (TS install.sh native layout): the binary
plus package.json (the version manifest --version reads at runtime, with
the compiled-in version as the fallback), prime-agent-runtime/ (the kernel
sidecar), skills/, and docs/. scripts/package_release.py (make package)
assembles, validates, version-pins, hashes, and tars it; the hidden
--prime-agent-bootstrap flag is the installer handoff that pre-bootstraps the
kernel venv. Verifier: tests/packaged_layout_e2e.rs.
Daemon discovery #
The discovery commands (status, doctor [--fix], shutdown [--force], TS
cli/daemon-ps.ts) live in the crate-private daemon_discovery module: an OS census of
listening unix sockets owned by product processes, a socket-dir sweep, probing/classifying
each discovered daemon, and the reap/shutdown planners and executors. Containment is part of
the contract: every scan, probe, and stop is scoped to an explicit DaemonStateRoot
(the env-resolved current root for the CLI), with a hard never-touch exclusion list for this
sandbox's ambient mission daemons. All e2e daemons live in
test-created fixture directories only.
Update flow (staged activation) #
prime-agent update (the update_flow module): the invoking CLI plans, downloads, and
stages the candidate (Acquire..Staged), then spawns the detached
coordinator - the new binary running from its release dir - which adopts the
status file and owns the FSM to a terminal state (Preparing..Complete,
Rollback first-class). intent.json is the coordinator lock (Join on a
live holder, dead-holder steal), the status file keeps the TS schema plus
updateId/state/epoch, and the activation owns the bin-symlink swap
(bin/previous rollback pointer, .activation-state recovery record).
Mechanism support (version/channel policy, install-root layout, manifest
fetch, archive staging) lives in pa-core's update module; the daemon-side
prepare/commit/stop drivers and the boot restore pass are pa-daemon
(slices 2-3 and 5): the coordinator's Restoring phase polls the
successor's update_restore_status RPC for the real per-session counts.
The legacy restart adapter accepts the fresh status path supplied by shipped
TypeScript updaters after they activate a release. It reports the original
requestId/phase status schema, translates their session checkpoint to a
Rust roster, and restarts the daemon without repeating activation. Recovery
rosters remain on disk when restoration fails. Socket-scoped checkpoints resume
after an interrupted handoff when no daemon is running, and reject a different
installed version. The adapter holds the shipped TypeScript shutdown-admission
lease in both historic supervisor registries until Rust owns the replacement
socket, preventing old clients from launching a competing daemon. Admission
records are refreshed and removed only while their ownership token matches.