prime-agent Documentation

A self-improving RLM agent for coding workflows and long-running autonomous tasks.

22,264 stars Rust Rust 20 files Β· ~39,548 tokens
RAW Doc

Technical Documentation: PrimeIntellect-ai/prime-agent

ℹ️ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference

Overview (`README.md`)

Prime Agent: A Self-Improving RLM Harness

Prime Agent is an open-source coding and research agent for general and long-running work. It is designed around two core abstractions:

  • The Recursive Language Model (RLM) treats context as variables (prompt-as-a-variable) and tools like recursive subagents as function calls (programmatic tool /sub-agent calling) inside a persistent REPL.
  • The Continual Harness stores supplemental prompts, memories, skill descriptions, and reusable subagent specifications as durable state that Prime Agent can refine through small, evidence-backed updates, local to the session by default.

Install

Install the latest stable version with the one-command installer:

bash
curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh

On Windows, install from PowerShell:

powershell
irm https://app.primeintellect.ai/prime-agent/install.ps1 | iex

The Windows installer downloads the latest stable Windows x64 build and verifies its checksum before installation.

Why Prime Agent

Prime Agent combines a persistent Python control environment with durable harness state, so useful working context and reusable operating patterns can outlive a single chat window.

  • Everything is programmatic: a persistent Python REPL is the built-in model tool; file operations, shell commands, tool use, subagents, and context management happen through code.
  • Subagents are built in: rlm.spawn(...) spawns real child agents for parallel or background work and returns their results programmatically.
  • The harness can improve: /refine reviews the current trajectory and can apply small, evidence-backed updates to supplemental harness state. It never rewrites the immutable base system prompt, and recorded snapshots support rollback.
  • Skills are executable: skills are importable Python packages, and the built-in skill creator can turn recurring workflows into project or personal skills.
  • Sessions run in the background: daemon-backed agents keep running when the terminal disconnects and can be reattached later.
  • Agents communicate directly: running agents can exchange messages and orchestrate one another without routing everything through the user.
  • Long tasks keep moving: automatic compaction, persistent goals, heartbeats, schedules, autonomous mode, and retained subagents preserve progress across turns and terminal sessions.

Slack

Connect Slack from /mcp or run /mcp login slack inside Prime Agent to search
your workspace and use Slack tools. See the Slack connector guide
for OAuth setup and troubleshooting.

Clipboard over SSH and tmux

Prime Agent uses the local clipboard tool (such as pbcopy) when available. In tmux it asks the attached tmux client to forward a clipboard buffer if that client advertises the Ms capability. This works with tmux's default set-clipboard external, unlike application-origin OSC 52. Without tmux, remote copies request delivery using OSC 52. Terminal requests cannot confirm that your local clipboard changed, so Prime Agent reports them as unconfirmed.

Nested tmux servers and terminals may still block delivery. An outer tmux with set-clipboard external can reject the inner server's OSC 52. In that case, run tmux set -s set-clipboard on on the outer server, or use native terminal selection (Shift-drag in most terminals; Option-drag in iTerm2) followed by Cmd-C. macOS Terminal.app does not support OSC 52; iTerm2 requires its β€œApplications in terminal may access clipboard” setting.

Custom certificate authorities

HTTPS requests trust your operating system's trust store together with the bundled Mozilla roots. For servers behind a custom CA, point NODE_EXTRA_CA_CERTS at a PEM bundle of your CA certificates β€” the same variable the Node/TypeScript build honors:

bash
export NODE_EXTRA_CA_CERTS=/path/to/your-ca.pem

Each transport reads the variable once when its first client is initialized. The background daemon keeps the environment it started with, so run prime-agent shutdown after changing it.

Getting Started

Start Prime Agent from the repository or directory you want it to work in:

bash
cd /path/to/project
prime-agent

On first launch, run /login to choose a subscription or API-key provider. Prime Agent works in the current directory and can run commands and modify files there. Use a disposable clone, clean worktree, or another checkpoint you can inspect and restore.

[!WARNING]
Prime Agent executes model-generated Python and project commands with your user permissions. Its worker and kernel processes improve lifecycle isolation and recovery; they are not a security sandbox. Review changes and use trusted repositories, instructions, and skills only. Run untrusted code or instructions in an external sandbox or restricted environment.

Useful commands:

bash
prime-agent agents                   # Browse running, idle, and saved sessions
prime-agent attach <agent>           # Reattach to a running session
prime-agent --resume [path|id]       # Browse sessions or resume one directly
prime-agent status                   # Inspect background service state
prime-agent doctor [--fix]           # Inspect or repair background services
prime-agent update [--force]         # Update Prime Agent
prime-agent shutdown [--force]       # Stop every agent, worker, and background service

Built for Long-Running Work

Prime Agent is built for long-running work, especially for evaluations in research. These features are available in the TUI, and when run autonomously.

  • Continual Harness: /refine can persist focused, reviewable lessons as supplemental prompts, memories, reusable skill descriptions, or subagent specifications, with recorded refinement history. It does not replace packaging and reviewing new executable skills.
  • Direct agent-to-agent communication: running agents and retained subagents can discover one another, exchange messages, and steer active work.
  • Daemon-backed continuity: active sessions, Python REPL state, schedules, and subagents keep running when the terminal detaches and can be reattached later.
  • Heartbeats and schedules: /heartbeat, rlm_heartbeat, and prime-agent schedule can re-enter a session periodically or at a specific time.
  • Persistent goals: /goal keeps an objective and its progress active across turns until it is completed, paused, or cleared.
  • Bounded autonomous mode: /autonomous continues within configured turn, token, and time budgets and can run user-defined quality gates. A passed gate checks only what that gate verifies; reaching a limit does not imply task success.

Contributing

Start with a GitHub Discussion for general questions, bug reports, and feature requests. Maintainers promote accepted work into Issues, and pull requests are reviewed from maintainers and vouched contributors.

License

Prime Agent is fully open source and released under the MIT License.

Citation

If you use this codebase in your research, please cite Prime Agent:

bibtex
@article{karten2026prime,
  title={Prime Agent: A Self-Improving RLM Harness},
  author={Karten, Seth and Zhang, Alex L. and Thomas, Kevin and MΓΌller, Sebastian and Bakouch, Elie and Auras, Daniel and Senghaas, Mika and Obeid, Fares and Dunas, Konstantin and Hagemann, Johannes and Jaghouar, Sami},
  journal={arXiv preprint arXiv:2608.23552},
  year={2026}
}

Available at https://arxiv.org/abs/2608.23552.

Automatic compaction settings

/settings exposes the global automatic compaction percentage in five-point steps,
with a 95% default. A saved off-grid percentage stays selectable. The effective
trigger also reserves room for retained history and summaries; the model's input
and requested-output ceiling always takes priority. Manual compaction budgets are
unchanged. The underlying compaction.thresholdPercent setting accepts 1–100,
including fractional values; project settings can override the global setting.

For per-model early compaction, use the existing provider-scoped
modelOverrides..contextWindow option in models.json, for example:

json
{
  "providers": {
    "prime-inference": {
      "modelOverrides": {
        "your-model-id": { "contextWindow": 524288 }
      }
    }
  }
}

Use the actual model ID for your provider. This sets that model's effective context
window; it does not change every model's global compaction percentage.


Overview (`crates/pa-agent/README.md`)

pa-agent

The agent loop.

Scope

Turn loop: drives a provider stream, dispatches tool calls, enforces max-turn and sequencing semantics, handles partial streams, retries on stream failure, propagates aborts.

Non-goals

No tool implementations (pa-core), no providers (pa-ai), no session persistence, no UI. The loop is tool-agnostic: it receives a ToolDispatcher boundary.

Public API

run_loop (session inputs -> stream of loop events), ToolDispatcher trait. Loop internals pub(crate).

Depends on

No workspace dependencies.


Overview (`crates/pa-ai/README.md`)

pa-ai

Provider APIs and model registry.

Scope

Provider trait + per-provider streaming clients (anthropic, openai-completions/responses, google, bedrock, mistral, azure, prime-inference), model registry/resolution, usage accounting, stream-failure retry, provider-error shapes (per-SDK user-facing texts, diagnostic error names, connection-error profiles), bedrock transport selection (h2c prior-knowledge HTTP/2 cleartext, h2-preferred TLS ALPN, the http1 AWS_BEDROCK_FORCE_HTTP1/proxy mode), overflow handling, JSON repair parsing, faux provider for tests.

Non-goals

No agent loop, no tool execution, no session state, no UI. Receives/returns pa-types messages.

Public API

Provider trait, ProviderRegistry, model lookup/resolution, faux provider. utils::tls::http_client_builder lets caller-owned HTTP transports apply the shared extra-CA trust configuration. Per-provider internals are pub(crate).

Depends on

pa-types (one-way).


Overview (`crates/pa-cli/README.md`)

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), trace-sharing state (client_traces.rs β€” the settings flag writes and the credential resolution /traces shows; the upload subsystem stays unported), 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).

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/refinement consumption, and the threshold arm,
the TS _checkCompaction flow), 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, the #253 residue): print
prompts that are /compact, /refine, /goal, or /autonomous execute
through the pa-core session-command executor (the daemon worker's seam β€” no
parallel implementation), streaming the TS shapes: the
session_action_update phase frames around the durable echo row, the
per-command events (compaction_start/compaction_end for /compact, the
refinement rows plus refine_complete/refine_failed for /refine, the
unconditional goal_update publish for /goal), the 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, ...) stay model prompts β€” TS
_normalizeSubmission classifies only the four session commands before
admission, so they never cross the turn boundary.
The json stream emits the TS session-event surface byte-for-shape: the
session header row (version 3), the message_update streaming deltas with
the slim assistantMessageEvent (the daemon wire drops the nested
partial), tool_execution_update, the harness digest's message pair at
the first-turn boundary, the compaction compaction_start/compaction_end
pairs with their durable outcome rows, the goal_update/session_action_update
frames of the goal loop, and the refinement rows with
refine_complete/refine_failed. Verifier: the print_runtime_e2e rows.

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.


Overview (`crates/pa-core/README.md`)

pa-core

The session engine.

Scope

MCP host side (mcp/): auth gating for built-in integrations (the
disabled-skill overrides and /mcp list status), the mcpServers settings
seam, and the mcp.* host-request handlers the kernel's generic MCP
client reaches through (mcp.config, mcp.refresh, optional
mcp.begin_login). The OAuth flow lives behind it (oauth* submodules:
RFC 9728 discovery, dynamic registration, PKCE, the local callback
server, token refresh; McpOAuthConfig::callback_port selects an exact registered
desktop callback port, while dynamic clients retain the candidate range.
Slack's endpoint selects its documented public desktop client; login.rs: login execution that persists the
endpoint-bound credential into the shared auth store and the
wire_begin_login/McpOAuth refresh integration hosts wire). The MCP
protocol itself runs kernel-side (Python
stdio/HTTP); the engine never spawns MCP servers. Tools (bash, edit, python_repl + internal rename/stdout), file mutation queue, truncation and rendering rules, RLM kernel lifecycle (IPython spawn/execute/revive), skills loading, system prompt assembly, compaction, harness refinement, settings/config, package manager (npm/git/local source install/remove/list/update against settings), session manager (persist/resume). Autonomous mode
(autonomous): runtime state with limit normalization, continuation and
gate-failure texts, shell quality gates with retry windows and git
worktree snapshotting, and the AutonomousDriver policy trait a turn
loop consults after every settled turn (the engine holds no autonomous
logic of its own). The RLM recursion host seam
(session_engine::rlm_host): the trait the kernel's rlm.spawn/
rlm.create_session/rlm.list_subagents/rlm.collect/
rlm.delete_subagent host requests call into, with the roster/collect/
selector-error vocabulary the daemon implements over the supervisor link.
Platform wall (platform): process control (signals/process groups, kernel exit waits), file locking, file permissions, and shell selection - every OS-specific behavior in the engine lives there behind cfg-gated implementations (the durable-write rename primitive is pa-telemetry's rename_onto, re-exported as platform::rename_onto). Scheduled jobs (cron, the AgentCronJobStore port of core/cron-jobs.ts): file-backed job state under session artifacts (scheduled-jobs.json partitions) with cross-process locking, plus the public read-only scan (cron::store::read_scheduled_jobs_artifact) the update flow's roster projection and boot re-arm read. Tools (bash, edit, python_repl + internal rename/stdout), file mutation queue, truncation and rendering rules, RLM kernel lifecycle (IPython spawn/execute/revive), skills loading, system prompt assembly, compaction, harness refinement, settings/config, package manager (npm/git/local source install/remove/list/update against settings, plus resolve(): precedence-ranked session resource resolution over configured packages, settings arrays, auto-discovery, and bundled skills), session manager (persist/resume). origin/main

Provider resilience policies in session_engine: the shared quick-retry
policy (TS provider-retry.ts: permanent-kind classification,
Retry-After-aware capped delays), the interactive auto-retry loop, and the
provider-failover driver (when a provider exhausts its quick retries and
another configured provider serves the same model, the failed turn
re-routes to the next provider in catalog order; the TS
auto_retry_start/reason: "backup" event vocabulary surfaces the
progression). The drivers are pure decision logic: the host (pa-daemon)
owns the attempt, the wait, and the model switch.

Session HTML export (export_html): the standalone viewer file for
/export and session export - the embedded product template
(assets/export-html, vendored marked/highlight.js attributed in its
NOTICE.md) plus the theme-to-CSS resolution over the bundled theme data
(pa-types) and custom themes under <agent-dir>/themes/, the tools
section mapping (tools_section: the session's tool registry to the
template's name/description/parameters list), and the custom-tool
pre-render pipeline (ToolHtmlRenderer/pre_render_custom_tools +
ansi_to_html: the exporter walks the entries, the session layer's
renderer produces line-oriented output, ANSI converts to inline-styled
HTML at the export step), and the two entry points
(export_session_to_html for the daemon worker's export_html
command, export_from_file for the CLI). Terminal theme rendering stays
pa-tui's; tool renderers live with the session's tool surface;
the share-viewer flow (gist creation) is the caller's.
Cloud workspace snapshots (workspace_snapshot): the portable, verifiable
capture of a git worktree's working state - tracked modifications, staged
paths, deletions, and nonignored untracked files - staged into an
owner-private directory as content-addressed blobs plus a deterministic
manifest that hashes them, with credential-shaped, symlink-ancestor,
escaping-symlink, nested-repo, and submodule exclusions recorded in the
manifest, hard entry/size bounds, race-free openat(O_NOFOLLOW) leaf reads
(Unix), and offline verification of a staged snapshot before upload. Capture
refuses on non-Unix targets: owner-only 0700 staging and 0600 files cannot be
enforced there. The baseline is an explicit caller choice (BaselineMode):
HEAD's tree content staged through the same filters as the delta (each entry
verified against HEAD's recorded object id and mode, no git history ships),
or an explicit external reference the consumer must reach itself. The secret
skip is a filename-shaped denylist only; ordinary-named files containing
secrets stage verbatim. Snapshot directories are secret-bearing: every
consumer must treat them as credentials. Capture does synchronous file I/O
inside its async entry point; callers must offload the entire operation with
tokio::task::spawn_blocking and Handle::block_on. Foundation plumbing
only: nothing wires it to a user-facing cloud toggle yet.

Non-goals

No provider HTTP (pa-ai), no loop policy (pa-agent), no daemon supervision (pa-daemon), no TUI (pa-tui). No update coordination (the pa-cli coordinator owns the FSM; the update-flow seam here is the daemon-free support layer: update::version (semver/channel policy), update::install (the managed install-root layout), update::release (the channel manifest fetch), update::download (sha256-verified archive download + staging)). The package manager installs sources and resolves resource paths only. No workspace snapshot upload, transport, or remote materialization (staging and verification live here; the cloud attach surface owns the transport when it ships).

Public API

SessionEngine (message in -> events out), export_html (the HTML exporter plus its renderer seam: tools_section, pre_render_custom_tools + ToolHtmlRenderer/RenderedToolHtml/RenderedToolResult, and ansi_to_html::ansi_lines_to_html/tool_render::trim_rendered_result_lines for renderer implementers), ToolRegistry, kernel manager, settings (SettingsManager), packages (packages::PackageManager + source types), platform (process control, file locking, permissions, shell selection - usable by higher crates, e.g. pa-cli's detached spawn), session_engine::rlm_host::RlmSubagentHost (implemented by pa-daemon for supervisor-backed children and by pa-cli's print mode for in-process children; the default is no children; the trait crate also owns the shared spawn/depth facts β€” DEFAULT_RLM_MAX_DEPTH, the depth-limit and name errors, the daemonless create_session refusal, rlm_child_session_dir, and the persisted_rlm_max_depth/resolve_rlm_max_depth precedence chain), session_engine::semantic_edges (the TS semantic-edges.ts WRITER: SemanticEdgeRecorder/SemanticEdgeIdentity, semantic_edge_ledger_path + last_committed_request_id, and the X-ACP-Model-Request-ID/Idempotency-Key constants β€” one durable per-session ledger of model-request events, byte-compatible with the TS lines; the fold and any publishing stay downstream), mcp::McpManager (the session's host-side MCP manager, exposed as SessionEngine.mcp_manager: auth gating for built-in integrations, the mcpServers setting seam, and the mcp.* host-request handlers - mcp.config/mcp.refresh/optional mcp.begin_login - the kernel's generic MCP client resolves through), mcp::McpManager::connection_roster + mcp::McpConnectionEntry (the /mcp view's roster: every resolved integration with its connected state, display kind, transport, and generic-listable flag), mcp::McpManager::service_catalog_views + service_catalog_diagnostics (the /mcp view's resolved service-catalog cards and their diagnostics, answered from local state - the TS buildServiceCatalogViews parity; the picker never round-trips the kernel), mcp::McpManager::api_key_credential_views + mcp::API_KEY_CREDENTIALS/McpCredentialView (the /mcp view's api-key credential rows - the stored keys the surface manages alongside the connections, e.g. the web-search key - configured from the shared auth store), models (the registry and resolver, plus models::order_for_picker: the picker-catalog order the composition root applies to its snapshot), session_engine::provider_adapter (switchable_stream_fn/ProviderTarget: the live provider-target slot a host swaps on set_model; the adapter bridges the loop's abort into pa-ai's StreamOptions::signal cancellation token, so a turn abort cancels the in-flight HTTP fetch at the transport β€” closed by ModelStream::close and the stream's drop), session_engine::ipython_state (the post-compaction ipython_state kernel-persistence notice: the CompactionKernelProbe view of the kernel provisioner the engine wiring binds via AgentSession::set_kernel_state_probe, and the notice row that rides CompactRun for the surfaces to broadcast). session_engine::session_commands (the /compact//refine//goal//autonomous executor: the durable echo/result rows with their persistence order - the echo ahead of the command so its own work sees it - the compaction/refinement/goal/autonomous arms, and the post-execution live-context rebuild that keeps the rows in the agent state; hosts own the wire surface), session_engine::auto_refine_trigger (the compact-trigger auto-refine machine on AgentSession: the arm/discard/increment accessors and consume_compact_auto_refine, the shared gate/review/stamp sequence every scheduling surface applies - TS _compactAutoRefinePending/_lastAutoRefineReviewAt/_assistantTurnsSinceAutoRefine plus _maybeAutoRefine's compact arm, the serialized checkpoint's compact step, and dispose's serialized drain, with the surface parameter carrying the one behavioral difference between a boundary and a disposal). session_engine::goal_boundary (the goal arms of the natural turn end on SessionEngine: the --goal construction seed with its next-turn context row - TS _pendingNextTurnMessages on AgentSession - the usage accounting, the budget-limit steer mint, the natural continuation mint with its slot rollback, the RLM deferral behind unsettled descendant work (mark/drop/take owed, the wake-up ownership probe, and the latest settled turn the post-settle deliveries decide from), and the terminal-error finish; the transports - the print driver's in-loop hook, the ACP settle loop, the daemon worker's queue - drive the one goal driver through these methods). SessionEngine::expand_skill_submission + AgentSession's prompt-path expansion (the /skill:<name> submission seam, TS _expandSkillCommand: skills::expand_skill_command builds the <skill ...> block over the shared pa_types::skill_blocks parse, the accepted-turn row and the admitted user message agree on the expanded text, and the skill_use_count session counter counts at the admission). workspace_snapshot (create_workspace_snapshot: bounded, secret/nested-repo/submodule/symlink-ancestor-excluding capture of a git worktree into an owner-private staging directory, with the caller-chosen BaselineMode (HEAD-tree content verified against HEAD's object ids, or explicit external) and a ConcurrentMutation error when the worktree changes under the capture; verify_workspace_snapshot: offline manifest/blob/baseline integrity check; the SnapshotManifest/WorkspaceSnapshot/SnapshotLimits/BaselineMode/SnapshotError types). All subsystem internals pub(crate); the engine is the only facade - subsystems must not import each other directly (the package manager consumes the public settings API only).

Depends on

pa-types, pa-ai, pa-agent (one-way).

The Unix platform process API exposes current_user_id() for the daemon
endpoint namespace to match the TypeScript process.getuid() contract on
macOS as well as Linux. This reads the OS identity without filesystem or
subprocess work.

Automatic trace delivery (agent_traces::ContinuousTraceUpload) owns consent-gated
persist scheduling and startup outbox recovery alongside the existing manual uploader.
Hosts reuse load_settings to capture consent before their existing settings parse,
attach persisted after successful disk writes, and own the returned installation
for the session lifetime. Replacements use rebind with the effective cwd; forks
use forked. The installation never drains on drop. A detached process-wide runtime
owns all transcript reads, scans, settings reloads, networking and retries; a maximum
of 256 registered controllers and four deliveries bounds resident work. Admission
returns None at capacity and logs rejection; hosts attach no hook in that case.
Retired descriptors free capacity only after background recovery acknowledges them.
Session replacement transfers its existing slot and cancels the predecessor; forks
require a distinct slot. Replacement retains a bounded retirement flag until the
background recovery snapshot acknowledges its exact controller identity. The synchronous
persist seam checks the cached consent snapshot and performs an exists/create
pending-marker write; consent metadata reads and reloads stay in the worker after
the host captures its initial settings generation; it does not fsync the marker (matching TS v0.9.8 process-crash
recovery rather than promising power-loss durability). A short nonblocking mutation lease prevents cursor/prune races; contention
records one stable fallback marker per session, which is retained conservatively
until a future sender has delivered it. Its cost must be measured
separately; startup/paint parity is not established by scheduler unit tests.
Active requests monitor consent changes every second on the worker and cancel on
revocation/errors; bytes already sent cannot be recalled. Recovery belongs to all
live hosts sharing the agent directory and cancels when the last host disappears.
Catch-up uses at most three delivery cycles per pending entry, honors Retry-After,
and releases upload capacity during retry waits; exhaustion retains durable intent.


Overview (`crates/pa-daemon/README.md`)

pa-daemon

Session supervision and wire serving.

Scope

ACP stdio transport (acp): the JSON-RPC serve surface for Agent
Client Protocol clients, served over a daemon session (initialize, session/new, session/prompt, session/close,
session/cancel, and the outgoing session/update notification with
namespaced _meta correlation), owned by this crate because
wire-protocol serving is its area; the worker turn loop owns compaction
arms, auto-refine, and goal continuation. The compact-trigger
auto-refine scheduling owns its surface here (compact_autorefine.rs
for the daemon worker: every compaction arm plus the manual compact
command arm the pa-core trigger, and the quiescent turn boundaries /
the command path consume the gated review - TS
_scheduleAutoRefineAfterCompaction plus the background
_maybeAutoRefine("compact")).
Supervisor process (one worker process per active session), restart/backoff
supervision, session registry/roster + worker self-registration (session
identity survives supervisor restarts: workers re-register with backoff and
the roster rebuilds), parent-death child cleanup (supervisor_parent_death.rs:
an unexpectedly exited worker's resident RLM children close with it - the
same plain-stop semantics as the worker-side #246 close, which SIGKILL
bypasses - via the durable create's parentActiveSessionId join), append-only session store ownership (including the
compaction-entry fold for compacted message reads), client attach/detach
(full-snapshot and chunked session_snapshot_begin/chunk/end
streaming), direct-attach transport (supervisor-issued single-use tickets
with a 10s TTL, worker-side peer grants burned on first use, session-plane
command gating on peer links), kernel agent_message/agent_observe bridge (agent_messaging.rs: the worker-side controllers over the supervisor link; the family view joins the supervisor roster with the session's own RLM children registry - the same registry rlm.list_subagents reads - so registry children are Child members addressable by name, RLM child id, and persisted session id, the spawning session is the Parent member for a subagent worker, and everything else stays a sibling; delivery tries the direct peer transport then the supervisor-routed send_message), worker-to-worker peer messaging (stage 3:
worker-purpose single-use grants minted by get_worker_peer_transport
for a source worker's kernel agent_message.send, direct
worker_deliver_message on the target worker's socket with the supervisor
routed send_message as the never-retried fallback),
saved-session wake
for non-resident send_message targets (session_catalog.rs: catalog
resolve by session-id prefix or exact name, cwd-scoped first; a live miss
lazily restores a matching file from sessions-archive/ without
overwriting an existing live file; no new sessions are physically archived;
a resident worker hosting the file is reused, otherwise one spawns over it; RLM
children fall back to the spawn ledger's live child edges when the catalog
misses - children persist outside the sessions dir, under the parent's
session-artifacts tree - and wake over their own session file), wire protocol serve/negotiation (including the
compact/abort_compaction/set_auto_compaction commands and their
compaction_start/compaction_end events, and the live
set_model/set_thinking_level commands β€” model_switch.rs: registry
resolution, the durable model_change/thinking_level_change rows, the
settings defaults, and the engine's live agent/provider-target/level
switch), session export commands (session_export.rs: the worker-side
export_html/export_jsonl handlers; the HTML data carries the engine's
tools section and custom-tool pre-render through the
SessionEngine::export_tools/export_rendered_tools seams, built
lazily when an export precedes the first turn - the TS state exists from
create), the get_model_catalog command (the TS refreshModelCatalog:
registry refresh β€” live Prime Inference catalog fetch, disk cache at
<agent-dir>/prime-inference-models-cache.json, bundled fallback β€” then
the full catalog minus unauthorized private models plus the configured
providers; the worker create path fires the same refresh at startup), the
agent-roster arms
(roster_subscribe/roster_unsubscribe with the full snapshot, live
roster_update pushes keyed by the TS roster agentId = session id, and
authenticated worker_roster_delta self-reports so live status reaches
subscribers without polling) plus the live-roster ledger seed (TS
seedRosterLedger, supervisor_roster.rs: passivated ledger descendants
of resident workers seed into the live roster - roots are the resident
session files, descent is membership at any parent-walk step, and
present rows by agent id or session file are never overwritten - at
subscribe, spawn-admission, and worker-stop moments, so subscribers see
the full family, not just resident rows), cloud sandbox attach, session
leases (core/session-lease.ts port). Daemon-owned RLM spawn ledger (rlm_ledger.rs):
one append-only JSONL per sessions dir (spawn/rename/delete admissions with
the TS rlm-ledger.ts record grammar, bounds, stat-guarded replay, and
legacy-registry seeding) plus the per-child display files; family topology
is read from the ledger, never re-derived from session files. Passive-RLM
roster walk (rlm_roster.rs): list --all and the saved-session catalog
merge non-resident ledger children (walk roots = saved + resident session
files), so passivated children stay roster-visible (TS
walkPassiveRlmSubagents / withPassiveRlmDescendantInfos). The saved
session scan (session_store.rs) folds each file once into the durable
catalog row.
Supervisor-backed RLM child sessions
(rlm_children.rs, the daemon side of the pa-core RlmSubagentHost seam):
rlm.spawn/rlm.create_session create real daemon sessions through the
worker's supervisor link - one supervised worker process per child - and the
parent-side registry serves rlm.list_subagents/rlm.collect/
rlm.delete_subagent with TS-parity selector errors; child model resolution
and thinking-level validation live in rlm_child_model.rs; the create
command carries the RLM recursion identity (rlmDepth/rlmMaxDepth/
parentSessionPath/thinking) so respawned children keep their depth. Per-session model binding: the
create-config provider/model/apiKey are authoritative for worker model
resolution (explicit CLI flags reach the worker; env remains the no-flag
fallback). Worker session files carry the TS creation prefix
(model_change/thinking_level_change/service_tier_change), and queue
snapshots persist to the worker recovery journal, not the session file.
Queue-lane command surface (queue_commands.rs): the full TS
DAEMON_COMMAND_TYPES accept list with exhaustive router tables in
protocol.rs, plus the worker's mutate_queued_message/resume_queue arms
(AgentSession.mutateQueuedMessage/resumeQueuedWork: preview-addressed
delete/move/replace over the two lanes with the TS status vocabulary, and
the empty-queue resume refusal). The sessionActions queue projection
(SessionActionSnapshot) carries one Rust-native typed rider the TS wire
has no counterpart for: rlmChildStatus, the parked RLM child status
notices' lane indices, derived per item from the injected custom row (the
rlm_child_terminal_notice/rlm_child_failure kinds) at projection time β€”
so journal recovery re-derives it, the lane strings stay the TS
queuedAgentMessagePreview projection verbatim, and the condensed queue
strip folds exactly these rows (a user-typed lookalike never flags); the
rider serializes only when a notice is parked. The reserved kinds are
daemon provenance, never client data (child_status_notices.rs): the
prompt/steer/follow-up parse and the restore_actions validation answer a
caller-supplied custom row claiming one loudly β€” it never parks, never
folds β€” and the daemon's own notice injection rides the follow-up route
with a one-shot capability minted in the worker process (the wire's
optional rlmNoticeNonce), so the queue's classification only ever sees
daemon-authentic rows.
Prompt attachments: the prompt/steer/follow_up wire images array
(base64 payload + mime type) rides the queue item into the session engine
as multimodal user content (images on a queued prompt do not survive a
worker respawn - the recovery journal keeps the text lanes only, the TS
command-recovery shape).
Eager turn abort (TS requestAbort's closing agent.abort()): every
worker abort surface β€” abort/abort_and_clear_queue, the compaction and
branch-navigation interrupt-and-settle waits, shutdown, kill, and the
cancel_prompt_admission cancel-owned arm β€” funnels through
SessionEngine::abort_in_flight_turn (the engine's build-time agent
mirror), so an abort landing mid-provider-wait cancels the in-flight fetch
immediately and the aborted turn settles on its zero-usage aborted message
(the usage-accounting parity of the #238 adjacent gap 3 fix; the transport
half is pa-core's provider-adapter cancellation token).
Queued-input suspension (TS _sessionInputPumpSuspended, the #227/#233
ruling): abort/abort_and_clear_queue/manual
compact suspend queued-input admission indefinitely; while suspended a
plain prompt/prompt_and_wait is rejected with the TS admission error
and the lanes park. Resume sites: a prompt carrying streamingBehavior,
steer/follow_up, resume_queue (even on the empty queue), an applied
mutate_queued_message, a cron/heartbeat fire, and a successful compact
with an active goal. Agent-message delivery is rejected while suspended
and idle (TS acceptAgentMessagePrompt runs with resumeIfIdle: false).
The compact-with-active-goal resume is also the TS post-compaction
continue (agent-session.ts compact()'s didCompact branch): with no
queued work parked, the worker mints the owed goal continuation through
the engine (SessionEngine::mint_post_compaction_goal_continuation, the
_maybeResumeGoalContinuationAfterRlmWork mint β€” the follow-up lane item
with the durable goal-context row), emits the mint's goal_update, then
resumes: the parked continuation crosses the suspension gate through the
resume site and the turn runner drives it (the _schedulePostCompactionContinue
schedule). The compact-trigger auto-refine defers behind that continuation
(TS _compactAutoRefinePending), servicing at its turn boundary.
Thread-goal durability (TS one-store parity): every goal_update
announcement mirrors the new state as a thread_goal_state custom row in
the worker session file (the turn emit closure; the compact mint persists
its row at the mint site, outside a turn), and the engine's session-build
adoption rehydrates the fresh goal driver from the moved branch's or the
session file's latest valid thread_goal_state entry
(goal_state_persist.rs) β€” status, objective, usage counters, and
continuation counts continue across a worker-recovery rebuild, and the
seeded published baseline keeps the rehydrated state from announcing
itself (TS loads at construction without emitting). Recovery branch
rebuild (TS one-store parity): the owned-session worker respawns with
--resume <sessionFile> (createRpcRecoveryArgs), so the rebuilt
session's branch is the durable history β€” while the Rust worker owns the
file writes and the engine keeps an in-memory manager, the engine's
session-build adoption rebuilds the branch from the worker session file
(the pending_branch seam's recovery twin), so a post-recovery compaction
walk (and the live loop context) reads the full durable history instead
of a fresh engine's empty branch skipping "Session is too short to
compact". Platform wall
(platform): per-OS endpoint naming and socket identity; the transport itself
is the shared trait in pa_types::platform::transport, and the private-frame
codec plus command planes are the shared wire contract in
pa_types::daemon (clients in pa-tui/pa-cli speak them).

Live token-stream rendering
(streaming): turn events forward from the hosted engine to the worker's
emit path as they arrive (one message_update per provider delta, the
full partial message per frame - never buffered until turn settle); the
worker coalesces them for broadcast in a single-slot coalescer with a
50ms flusher, while message_start/message_end/tool frames flush the
parked update first and go out directly, so wire order and event-sequence
order match uncoalesced streaming. The supervisor stays payload-free:
deltas ride the worker -> client session-event stream (direct-attach or
supervisor-routed).

Autonomous
continuation driving in the worker's engine: per-message usage accounting
runs in the agent-loop subscription, and the in-run continuation hook
(autonomous_continuation.rs) consults the pa-core AutonomousDriver
policy at the agent loop's natural turn end (product default: shell
quality gates in the session cwd; deterministic drivers injectable for
eval harnesses) β€” continuations mint as durable user rows churned
inside the one prompt wait (the TS getContinuationMessages shape), a
threshold compaction holds its minted continuation for the worker's
queued followUp admission, and gate pass/fail or limit stops surface no
row (the headless status request and the print exit contract carry
them).

Live MCP product-path verifier
(tests/mcp_product_path_e2e.rs): a settings-declared stdio MCP server
round-trips through a real worker session and kernel - the kernel cell's
mcp.list_tools/mcp.call_tool resolve mcp.config through the
session's host handlers, spawn the fixture
(tests/fixtures/mcp_echo_server.py), and echo back.

Update-prepare transaction (update_prepare.rs): the supervisor-side FSM
Draining -> Fenced -> Snapshotted -> Prepared -> Stopping with the
mutation-drain latch + admission gate (mutating commands refused with
"Daemon is preparing an update restart" while active; reads/attach and the
TS abort-family drain commands stay served), the hard prepare deadline
(90 s default) and the durable 45 s marker self-expiry as watchdogs β€” both
re-checked on a timer and on any later command β€” idempotent
prepare_update_restart on updateId (a different id is a typed refusal
the coordinator maps to Join), and Aborted -> Serving with prepared-dir
cleanup as the failure default. The prepare RPC drives accept
through the mutation drain to Fenced, snapshots every resident worker
(update_snapshot, worker.rs), assembles the roster (update_roster.rs:
sessions/workers/subagents from the ledger + displays, heartbeats as a
read-only scheduled-jobs.json projection), writes
prepared//{roster,marker}.json durably, and acks Prepared β€” all
inside the prepare budget. commit_update_restart consumes the
transaction (update_stop.rs): the acked worker shutdown (its handler is
the flush barrier) with the 30s + 30s budgets, abandon-on-refusal (the
supervisor resumes Serving, refused sessions untouched, stopped workers
relaunched β€” never a kill), or the update exit when all workers stopped
(descriptors survive for the new supervisor's create-or-adopt restore).
The new supervisor's boot owns the update's restore side
(update_restore.rs, spec Β§6): it consumes PRIME_AGENT_UPDATE_ROSTER
from its spawn env before the unconditional scratch-dir sweep (this
socket's update-restarts/ subtree plus the TS-era legacy names β€” no
liveness checks, no exceptions), then restores the roster rows bottom-up
(create-or-adopt; per-session failure records, restore never fails the
boot), re-arms scheduled work (a boot scan of scheduled-jobs.json;
due active jobs of sessionless files are woken once, never archived), and
reports the pass over update_restore_status while hello.update_resume
carries the settle state; client attaches to a not-yet-restored roster row
queue behind the pass instead of failing.
For historical TypeScript update manifests, the restore pass recreates parents
before their children, remaps active parent IDs, and restores pending context
and action queues explicitly because those manifests predate Rust's journals.
Attaches remain gated until that replay completes.
The heartbeat-catalog change
broadcast owns its surface here too (the worker's cron-store
on_heartbeat_change listener emits a heartbeats_changed outbound frame
β€” TS daemon-mode's broadcastGlobal β€” and the supervisor re-broadcasts it
to every client, so any heartbeat mutation anywhere in the daemon reaches
attached UIs daemon-wide).

Non-goals

No agent behavior inside workers beyond hosting a pa-core engine; no UI.

Public API

Supervisor entrypoint, worker entrypoint, mcp_login::{WorkerMcpLoginUi, wire_worker_mcp_login} (the worker's browser+callback login behind mcp.begin_login; wired by the agent engine before sessions register host handlers), client connection API for pa-tui/pa-cli, acp::daemon::{run_daemon_attached_acp_mode, DaemonAcpOptions} (pa-cli dispatches --mode acp through it), agent_messaging::LinkAgentMessageController + rlm_children::{SupervisorChildSessions, ParentIdentity, RlmChildIdentity} (e2e verifiers construct the worker-side family controller and the children registry; the engine wires the same types), agent_engine::AgentSessionEngine::dispose_kernel (the session-end kernel teardown the worker invokes at kill/shutdown/orphan exit β€” the engine outlives the session, so the pa-core engine-drop teardown cannot run there), the worker's get_mcp_connections command (mcp_connections.rs: the /mcp view's roster from the session's MCP manager overlaid with the session kernel's per-server tool listing, plus the api-key credential rows the same response serves β€” Rust-native session-plane extension; the TS daemon has no counterpart). Supervision internals pub(crate).

Depends on

pa-types, pa-core (one-way).

The composition root initializes the packaged product version with
protocol::configure_app_version before entering daemon or worker mode.
protocol::app_version supplies that identity to both hello frames and update
checkpoints; library embedders fall back to the compiled version. Release
manifest restamping therefore remains consistent with CLI --version.


Overview (`crates/pa-models/README.md`)

pa-models

The live model catalog subsystem: fetch layer, no-cold-start chain, strict
schemas, transport pinning, and the credentialed Prime Inference catalog.
Port of the TS catalog client (branch feat/catalog-client:
model-catalog-cache.ts, model-catalog.ts, model-compat-schema.ts,
provider-model-catalog.ts, bundled-model-catalog.ts,
prime-inference-model-catalog.ts).

Scope

  • Fetch layer: unauthenticated GET on the two catalog URLs; 5 s timeout,
    8 MiB cap (content-length AND streaming), redirects refused, ETag
    If-None-Match with 304 snapshot reuse; PI_OFFLINE disables the network.
  • No-cold-start chain: validated last-good disk cache -> build-time bundled
    snapshot -> compiled fallback (42 transport tuples + 110 offline Prime
    Inference entries).
  • Strict schemas: serde deny_unknown_fields, version gates (models
    schemaVersion == 1), skip-invalid for remote model refresh, compat
    validation per api. Unsupported future versions keep last-good
    silently, forever.
  • Transport pinning: every remote model must match a compiled
    (provider, api, baseUrl) tuple; catalog data never carries request
    headers.
  • Prime Inference: live credentialed fetch, scope-keyed disk cache
    (HMAC-SHA256 of key over team), 401/403 clears only that scope.
  • Refresh: hourly + startup + picker open + auth change; fire-and-forget;
    coalesced per source; a mid-session refresh never retargets the active
    model.

Non-goals

  • The plugins/MCP catalog serde + resolution (the plugins lane; this crate
    hands it the fetcher, the generic CatalogCache, and the bundled asset
    bytes).
  • The build-time asset generator + release packer (the build lane).
  • Session auth storage, models.json parsing, and registry merges
    (pa-core): the user's local models.json takes precedence over this
    catalog and is merged by its owner.
  • The catalog repo's sync/export tooling (lives with the data).

Public API

  • ModelCatalog (chain resolution + refresh cadence),
    RefreshTrigger, PrimeCredentials
  • cache::CatalogCache<T> (generic last-good cache; the plugins lane
    instantiates it with its own fail-closed parser),
    cache::{RefreshOptions, PUBLIC_SCOPE}
  • fetch::{CatalogFetcher, FetchOutcome, FetchError, MODEL_CATALOG_URL, MCP_SERVICE_CATALOG_URL}
  • schema::{parse_model_catalog, InvalidEntries, ModelCatalogV1}
  • compat::is_model_compat
  • pinning::{parse_provider_model_catalog, pin_catalog_models, PinnedTemplates}
  • transports::{compiled_models, compiled_transport_tuples, prime_inference_offline_entries}
  • bundled::{BundledAssets, load_bundled_models, PACKAGED_MODEL_CATALOG_FILE, PACKAGED_MCP_CATALOG_FILE}
  • prime_inference::{PrimeInferenceCatalog, PrimeInferenceCredentials, build_prime_inference_models, merge_prime_inference_models, parse_prime_inference_model_catalog, is_private_prime_inference_model_id, scope_key, PRIME_INFERENCE_BASE_URL}
  • offline::is_catalog_offline

Dependencies (direction compliance)

Depends on pa-ai (compiled transport templates: models_generated) and
pa-types (shared Model). Nothing else in the workspace; no crate below
pa-ai is touched. Consumers live at or above pa-agent (registry wiring
in pa-core). The plugins lane must not place its catalog code in pa-ai
(pa-models already depends on it).


Overview (`crates/pa-perf/README.md`)

pa-perf

The prime-agent performance tooling: a deterministic corpus generator over
the product's real on-disk layout, and a pty bench harness that drives the
REAL release binary (target/release/prime-agent) headlessly. A dev/CI
crate at the top of the workspace dependency graph β€” nothing depends on it,
and it never ships (the release lanes build -p pa-cli --bin prime-agent
only).

Scope and non-goals

  • Scope: faking realistic agent-dir state at scale (sessions, subagent
    trees, artifacts, the spawn ledger, the cwd project tree), and measuring
    the interactive surfaces (start, agents view, open/switch session,
    @ completion) through the real binary.
  • Non-goals: benchmarks of provider/LLM paths (the harness stays offline),
    and any product-code changes. The new vt100 screen-model dependency
    is described below; clap and nix were already in the tree.

The corpus generator

text
pa-perf corpus --profile medium --out DIR [--seed N] [knob overrides]

DIR must be absent or empty. A populated output is rejected before any writes,
so stale sessions cannot enter the new corpus or its restore baseline. Choose a
fresh directory for another standalone generation. The suite command can be run
again in the same output: it replaces only its own corpus directories after
verifying their manifests, and refuses unrelated populated directories.

Writes the product's real layout under DIR/:

text
DIR/
  corpus.json                      # manifest: counts, needle markers, the file-set baseline
  project/                         # the sessions' cwd + @-completion tree
  agent/                           # point PRIME_AGENT_CODING_AGENT_DIR here
    settings.json                  # onboarding pre-answered, traces off
    sessions/<uuid>.jsonl          # current-format session files (flat)
    sessions-archive/<uuid>.jsonl
    rlm-ledger/<hash16>.jsonl      # written through pa-daemon's RlmSpawnLedger
    session-artifacts/...          # kernel-state stubs, harness/, sub-<id>/ trees

Row payloads are serialized from the product's own types (pa-types message
shapes inside pa-daemon's SessionEntry envelope and session_header_line
header) β€” no hand-copied JSON. Entry ids are counter-based unique 8-hex: the
product's own ids are uuid4 prefixes with a 100-try collision check, which
collide around ~22K rows and silently break branch reconstruction.

Knobs

--profile small|medium|large|xl picks the defaults; every knob overrides:

flag meaning small medium large xl
--sessions top-level sessions 10 100 1,000 2,500
--rows rows per session 100 2,000 10,000 400
--huge-rows rows of the huge marker session 2,000 4,000 20,000 2,000
--child-rows rows per subagent child 40 100 150 80
--fanout children per parent per level 2 3 3 2
--depth subagent tree depth 2 2 3 2
--tree-parents sessions growing a tree 2 5 8 10
--wide-children one parent's direct children (the operator's ~110-wide real parent) 0 0 110 0
--archive sessions in sessions-archive/ 3 20 200 500
--cwd-files files in the project tree 200 2,000 4,000 2,000
--artifacts per-session artifact partitions (on/off) on on on on
--cache cold/warm (see below) cold cold cold cold
--seed the deterministic seed 7 7 7 7

Total rows: small ~3.5K, medium ~250K, large ~10.1M, xl ~1.2M (medium is
~210 MB on disk; large ~3.5 GB; xl ~1 GB, generated in ~5 s).

Determinism

Same seed + same canonical path => a byte-identical tree, file mtimes
included (roster order is mtime-desc). Two stamps are the product writer's
own wall clock and are excluded from that contract: the ledger records'
at fields, and the corpus-root path embedded in child headers/ledger
records (parentSession/child are canonical paths).

Caches (cold vs warm)

  • --cache warm runs the product's own pa_core::session::window::WindowedSessionStore::open
    over every openable session file, writing real .window-cache.json
    sidecars. On a real store warm window-cache sidecars are the common state
    (the operator's live store: 71 of 111 sessions carry one).
  • The .info-cache.json sidecar is written only at worker lease
    release (pa-daemon, no public writer), so generated corpora carry none β€”
    matching real stores (9 of 111). The agents-view-warm scenario measures
    the warm scan via the daemon's in-process cache instead.
  • pa-perf run generates BOTH corpora (cold for the cold scenarios, warm
    for warm-start/warm-ready) β€” every scenario runs on the state it needs.

The bench harness

text
# the whole suite (CI + make perf): generates the corpora, routes every
# scenario to the corpus state it needs, writes one results/<scenario>.json
pa-perf run --binary target/release/prime-agent --profile medium --out DIR [--runs N]

# one scenario over one corpus (investigation)
pa-perf bench --binary target/release/prime-agent --corpus DIR --scenario S --runs 10 --json OUT

Scenarios (timed from a triggering action to an observable needle β€” literal
paints as byte needles, cell-diffed content through a vt100 screen model;
the liveness deadline only fails broken runs):

scenario corpus trigger -> needle
cold-start cold exec -> the chat first paint (the splash paints before any daemon work β€” the product's own paint contract)
cold-ready cold the same launch, exec -> the tray's session-usage pill, the first attach-only content (daemon spawn + session create + attach included)
warm-start warm exec -> first paint, daemon pre-started
warm-ready warm the same launch, exec -> the attach pill
agents-view cold left-arrow -> the settled first catalog page (Inactive (N) plus the M older sessions not loaded footer account for every session) in the screen model
agents-view-warm cold the same daemon's second populate (its in-process scan cache warm)
open-session cold --resume <huge session> -> the huge session's tail marker (the final user row; its reply is one line)
session-switch cold enter on the uniquely-filtered switch-b row -> B's transcript marker (the final user row; its reply is one line)
at-completion cold the @ keystroke -> the first completion menu row

Mechanics (the repo's pty-e2e discipline): the TUI child runs in its own
session with NO controlling tty (setsid pre-exec; vendored crossterm
falls back to the stdio pty), 120x36, kitty probe answered identically every
run, PRIME_AGENT_CODING_AGENT_DIR + --daemon-socket isolate it, and the
env is rebuilt from scratch (no ambient credentials leak in). Between runs
the harness restores the corpus to the manifest's file set (the product's
new session files, sidecars, logs, and leases are deleted) so every run
measures identical state. A discarded warm-up run precedes the samples.

Teardown signals only the harness's own child pids; the daemon stops by the
shutdown protocol and the run FAILS if it keeps accepting β€” never a
pid/name-based kill.

report + baseline

text
pa-perf report --baseline crates/pa-perf/baseline.json --results results/*.json [--markdown OUT]

Prints the aggregate table (scenario/profile, median, p95, baseline, delta)
and exits nonzero ONLY when a non-report-only median exceeds twice the
baseline. Noisy scenarios (>20% spread across 3 CI runs) are marked
report_only in the baseline: their deltas always print, the gate never
fails. baseline.json is committed from CI numbers, not laptop/sandbox
ones.

Local run

make perf builds the release binaries and runs the whole suite
(pa-perf run --profile small) with the report table printed at the end.
Run it on a quiet machine; the numbers are wall-clock latencies.

Dependencies

pa-perf adds ONE new third-party dependency: vt100 (MIT), the screen
model behind the content needles (the agents view's heading counts repaint
as terminal cell diffs, which the raw byte stream cannot see). The crate
never ships β€” the release lanes build -p pa-cli --bin prime-agent only β€”
and make deny stays clean.

CI

The bench matrix runs all four profiles at 10 runs each; the xl
(2500-session) agents-view populate measures ~2.7 s on an 8-core sandbox.

.github/workflows/perf.yml: nightly schedule + workflow_dispatch +
label-gated pull requests (perf-test label, [labeled, synchronize] β€”
labeling is the explicit opt-in). One release build, a bench matrix over
the four profiles, and a report job writing the $GITHUB_STEP_SUMMARY
table with the >2x gate.


Overview (`crates/pa-telemetry/README.md`)

pa-telemetry

Modular telemetry: the event schema, queueing/batching, sinks, and the
pseudonymous installation identity for Prime Agent product analytics.

Scope

  • Properties: primitive-only property maps (the privacy boundary β€” strings,
    numbers, booleans, null; structured values are rejected at insertion).
  • TelemetryEvent: {id, name, timestamp, properties} records.
  • TelemetryClient: non-blocking track(event, properties), flush,
    shutdown. One background worker, capped queue (drop-oldest), batched
    flush (size + interval + explicit), fan-out to every sink. Best-effort by
    contract: never blocks, never panics, never fails the agent.
  • TelemetrySink trait + shipped sinks: AnalyticsSink (the one product
    destination: the TS endpoint ANALYTICS_ENDPOINT and wire format
    {installation_id, events: [{id, name, timestamp, properties}]}, no
    credentials, 1.5s timeout; the platform backend forwards to PostHog),
    FileSink (local JSONL transparency mirror at <agentDir>/telemetry.jsonl),
    NoopSink (opt-out fast path), MockSink (tests, also re-exported for
    downstream crate tests).
  • install_id: <agentDir>/telemetry.json {version, installationId},
    exclusive 0600 create, validated on load (TS parity).
  • rename_onto: the shared rename-onto-destination primitive for durable
    persist writes (TS renameOntoSync: bounded win32 destination-busy
    retry, 10ms * attempt backoff, every failure immediate off Windows).
    Lives in this crate because every persist owner (pa-core's settings and
    session writes, pa-daemon's descriptors/journals/session store, this
    crate's install id) already depends on it while it depends on no other
    workspace crate; pa-core re-exports it as platform::rename_onto so its
    platform wall stays the engine's single platform entry.
  • The TS boolean env parsing (parse_bool_override); the opt-out
    precedence (PI_OFFLINE, DO_NOT_TRACK, PRIME_AGENT_TELEMETRY,
    settings) lives in pa-core's telemetry_switch.

Non-goals

  • No settings reading: the enabled/disclosure flow is owned by pa-core
    settings + pa-cli wiring (this crate only answers "does the environment
    override?").
  • No event emission from this crate: seams live in pa-core/pa-daemon/pa-cli,
    which call track with schema-documented events.
  • No prompt/session content, tool arguments, file paths, or user identity β€”
    by construction (primitives-only properties).
  • No retries or delivery guarantees: batches are best-effort and offline-safe
    (dropped on failure), matching the TS product.

Public API

  • TelemetryClient / TelemetryClientConfig (track, flush, shutdown,
    dropped_count, install_id, spawn)
  • TelemetrySink trait, SinkOutcome
  • Properties, TelemetryEvent
  • install_id(agent_dir)
  • rename_onto(from, to)
  • parse_bool_override(value), existing_install_id(agent_dir)
  • Sinks: AnalyticsSink (+ ANALYTICS_ENDPOINT), FileSink, NoopSink, MockSink
    (+ RecordedBatch)
  • AnalyticsSink::new(endpoint) and with_timeout(endpoint, duration) retain
    their default HTTP clients. with_http_builder(endpoint, builder) lets the
    caller supply its trust configuration while retaining the product timeout;
    pa-core composition supplies the shared extra-CA builder without adding a
    workspace dependency to this leaf crate.

Placement

Depends on no workspace crate (sits beside pa-types at the bottom of the
dependency graph). Consumers: pa-core, pa-daemon, pa-cli.


Overview (`crates/pa-tui/README.md`)

pa-tui

The terminal UI.

Scope

Rendering (markdown, themes, layout, tool panels, custom-message decorated rows: the agent-message summary rows render the viewer-relative notice (the shared Agent message label, the ↑ sent/queued and ↓ received arrows from the row's actual direction, the counterpart agent's name only, no collapsed body preview β€” the operator's 2026-09-25 directive), and the ipython cell renders the sent/queued receipt rows below the code, and the RLM child rows render the outcome diamond rows β€” accent diamond, semantic label colors, the failure error and the cancellation reason as the expandable body; the skill-invocation card β€” TS SkillInvocationMessageComponent: a user message whose text is one <skill ...> block renders the [skill] <name> card collapsed, the **<name>** + content markdown expanded over the shared pa_types::skill_blocks parse, and the trailing invocation args as their own user block), the working loader row (activity label, TS formatWorkingElapsed, and the tool-owned working note during python-kernel boot, sourced from starting tool partials), image support (the terminal-image library: capability detection, Kitty/iTerm2 protocol encoders, dimension parsing; the image component with the fullscreen compose fallback; terminal.showImages gating of tool-result image rows; clipboard image paste behind app.clipboard.pasteImage with [image #N] editor markers, byte-budgeted registry, and wire-image attach), editor (cursor/kill-ring/undo/history/word ops, with the live prompt highlight: argument tokens in their TS colors on every line and the first line's slash-command token in accent for any recognized builtin command (sanctioned divergence, operator ruling 2026-09-29: TS accents argument-taking commands only) β€” suppressed while the cursor sits inside it, TS CustomEditor.styleDisplayText), the transcript prompt-highlight surfaces (TS PromptTokenMask: the user-message block masks its accent command segment and argument tokens to same-width private-use placeholders before the markdown layout and restores them after, TS UserMessageComponent; the session-command echo row styles the typed command the TS SlashCommandMessageComponent way β€” accent command segment, colored tokens, styled wrap), and the suspend-to-background cycle (app.suspend, default ctrl+z, TS handleCtrlZ: SIGINT ignored for the stopped window, the TUI stopped with the terminal handed to the shell, the process group SIGTSTP'd; the SIGCONT resume re-applies raw mode, the alternate screen, and SGR mouse tracking β€” the terminal modes a suspend cycle can lose), keybindings (user-editable via ~/.prime/agent/keybindings.json: TS parse/migration/merge semantics β€” legacy id migration on load and at startup, malformed values dropped, empty-array disables, same-scope default eviction, conflicts, case/order-insensitive matching β€” the effective set wired into every hint, handler, and picker), autocomplete (slash commands, path completion, and the @ fuzzy file search: a parallel ignore walk with fd's semantics, off the UI thread), fullscreen/scrollback (the wheel-scroll surface: SGR mouse-report decoding, ?1002+?1006 tracking enabled blind at the fullscreen surface's entry and released on exit/suspend β€” tmux never answers DECRQM, unsupporting terminals ignore the mode-sets β€” wheel turns scroll the transcript three lines, consumed without scrolling while a picker owns the frame or when the terminal.fullscreenMouse setting disabled tracking; in-frame click/drag selection stays unported), input handling, and the double-Ctrl+C force-quit guard (the exit contract: two in-window Ctrl+C presses end the process within 1.5s of the second press regardless of daemon or loop state; cleanup inside that window is best-effort). Interactive sessions attached through the daemon: the prompt stash (TS prompt-stash-state.ts: the client-owned per-session draft store shared across a TUI process's chat views β€” a draft left behind on a session switch returns to the editor when the session's chat reopens, its pasted images rehydrated behind their markers; the capture on the agents-view handoffs (/resume, the rebound app.session.resume key, the scoped view, /resume <selector>) and the in-place /switch, the restore on reopen/switch-back, and the queued-stash semantics for multi-switch chains; the manual app.prompt.stash capture (default ctrl+s, TS handlePromptStash): the key stashes the live draft and clears the editor, restores it on the next press, and a manually stashed draft returns on that key or after the next sent prompt is admitted β€” never on a reopen or a switch landing), the JSONL client socket (hello handshake, command envelopes, session-event loop), slim-attach snapshot reconstruction, the direct worker link loss path (a killed/crashed worker tears the link; the UI re-attaches through the supervisor with the TS reconnect surface - the Daemon connection lost; reconnecting… warning, the resync, Daemon reconnected, the 60s TS window with TS backoff, and the terminal ⚠ Error: Daemon reconnection failed: <last> arm with dispatch blocked so the UI never rides a silent spinner), prompt submission with streamed assistant output, live session list and switch, the inline pickers (/model: the TS inline menu-panel - the bordered Search models field, >-marker rows with the effort-square cluster and current Β· require sign in Β· provider trailing, the price detail block, and the model/effort key hint - over the catalog the daemon's get_model_catalog refresh supplies (bundled + models.json fallback), with the TS sort chain, the scored fuzzy filter, left/right effort adjustment, and apply through the daemon set_model command (plus set_thinking_level for a user-edited effort) with the Model: status row; /effort: the session's thinking levels from get_state, the unsupported-model note, and apply through set_thinking_level; /mcp: the inline connections view over the daemon's get_mcp_connections roster - the TS MCP-Connections-tab panel (bordered Search MCP connections field, β€Ί rows with the status flush right on connected entries, the selected connection's detail block with its tool listing, the navigate/select/close hint), Enter resolving to the selected connection's login command through the interactive loop's client-command seam, plus the api-key credential rows the same response serves (the stored keys, e.g. the web-search one; Enter runs the paste-the-key prompt, a rust-only surface - the operator's /login-is-providers-only ruling moved the key here, TS still serves it on its /login MCP tab); /heartbeats: the inline management view (TS HeartbeatManagerComponent, inline-picker style per the lane directive): the session-scoped user and agent heartbeat catalog from the selector-less supervisor heartbeats_list β€” scoped like TS scopeHeartbeatsToSession to this session and its roster descendants β€” with the TS count label, β€Ί rows with status-colored trailing, the selection's detail line, the actions pane (pause/resume/stop through heartbeat_manage), the fetch/action error rows, the heartbeats_changed broadcast refresh through the update channel, and the tray heartbeat label N heartbeats Β· M paused (Ctrl+R); ctrl+r (app.heartbeats.open) opens it from anywhere), the TS showStatus back-to-back rewrite (a status emitted with nothing after the previous one rewrites it in place), the session export and share commands (/export [path]: the path-argument parse, the .jsonl branch switch, the daemon-side export_html/export_jsonl requests, and the Session exported to: row; /share: the gh auth gate, the temp export, the background gh gist create run behind the bordered Creating gist... loader with Esc cancel, and the Share URL:/Gist: rows - the gist provider is the GitHub CLI exactly like TS), the visible follow-up queue (TS queuedMessagesContainer): prompts parked behind a running turn render as dim preview rows above the prompt dock with the TS prompt-highlight styling (accent on a leading slash command's /name segment, success/mdLink on @path/--flag argument tokens β€” TS styleQueuedMessagePreview) β€” Steering:/Follow-up: labels with the ╰─ <key> to browse and edit queued messages hint, re-synced from state.sessionActions on attach and kept live by session_action_update; the queued internal prompts (heartbeat fires, agent messages, goal contexts, background-command notices) condense into one counted row below the human previews β€” "1 agent message, 1 heartbeat, and 1 other internal prompt queued": each origin's own count, singular/plural-correct, zero-count origins omitted (sanctioned divergence, operator requests 2026-09-24 + 2026-09-25: TS renders every internal prompt as its own preview row; the browse/edit affordances still walk every queued item, so only the strip rows condense β€” the four TS-internal labels classify by preview string, TS isLabeledQueuedPreview, the queue wire carries no provenance for them, so a human-typed prompt that begins with one of the internal labels classifies internal for the strip too: it still delivers, and the browse/edit affordance walks and shows it; the parked RLM child status notices β€” the [child-exited: ...]/[child-failed ...] lifecycle rows the queue-fold operator bug 2026-09-25 flooded the strip with β€” classify by WIRE-TYPED provenance instead, never by their text: the worker's sessionActions.rlmChildStatus rider carries the parked injected rows' lane indices, they count as their own origin ("N child status notices"), and a user-typed lookalike stays a human row); the collapsed conversation view (operator directive 2026-09-28, the undo of the 2026-09-25 condensed-run summary block): the collapse mode renders every activity item exactly as details does - every tool card, every notice, every agent-message row - with ONLY the thinking blocks hidden (the TS fork's shape), a chat starts at that collapsed level, and the Ctrl+O cycle from there is the thinking-visibility toggle (overview -> details -> all -> overview unchanged; a card click toggles only the clicked card's own expansion - TS's per-component expanded - and the Ctrl+O cycle resets every card to the new level) - purely render-time over the unchanged transcript model (no session jsonl schema change, no stored-event change); the follow-up key (app.message.followUp, default alt+enter) parks a submission on the follow-up lane (streamingBehavior: "followUp" + queueIfBusy), Enter on the steering lane; the browse affordances TS gives the rows (TS QueueSelection port): alt+up/alt+down walk the parked messages newest-first with the editor draft stashed and restored, the dim browse header (lane, index, key hints) renders as the editor box header row, Enter steers the edit (replace/move to steering, empty deletes), the follow-up key re-parks it, ctrl+alt+arrows reorder through mutate_queued_message (mirrored locally like TS β€” the event can land after the response), the clear key restores the draft, and queue-change statuses surface the TS status rows verbatim; and the utility slash commands (TS dispatch_client_command arms): /btw+/side (the side-question pane over the daemon start_side_question/abort_side_question flow β€” the popup-surface turns with the /btw header, follow-up bubbles, the slash/image rejection notices, the running/return hint rows, Esc cancel; replies while the pane is open seed follow-ups from the answered turns), /fast (the priority-tier toggle with the TS eligibility check and unavailable status), /rlm-max-depth (view/set with the --global arm and the TS status rows), /name+/rename (the daemon set_session_name rename whose persisted session_info entry and session_info_changed broadcast the daemon owns), /settings (the TS SettingsList grouped into tabs β€” the spaced tab strip (white-bold active tab) over label/value rows with descriptions, ←/β†’/Enter/Space value cycling (the arrows step a row's options in place; Tab/Shift+Tab and the number keys move the tabs), the detail block's separator rule and description-matched hint padding, the thinking/theme/warnings/service-tier submenus with the theme's live preview and restore, type-to-search; daemon switches for autocompact/steering/follow-up/transport/thinking, the client-settings seam for the persisted preferences), and /reload (the TS guards, the bordered reload box, the daemon reload request, keybinding/theme/catalog re-reads, and the outcome row); /thinking rides the registry alias into /effort. The client-settings seam (client_settings::ClientSettings, implemented by the composition root) is how pa-tui persists user preferences without a pa-core dependency; the autocomplete listing drops /fast when the current model is not fast-mode-eligible (the pa-types supports_fast_mode helper), the subagent summary line under the tray (TS SubagentSummaryLine: live descendant counts from the roster subscription, the dock renders in every session, all-zero included (the operator's 2026-09-28 directive; TS renders nothing at zero, a sanctioned divergence), and its Subagents group opens the scoped agents view even with no children, which shows the view's No sessions yet. empty state, the focus hand-off from the editor β€” Down at the end of the prompt or app.subagents.focus β€” the focused key contract β€” confirm/open into the focused group's own view, left/right stepping every rendered group (an empty one included, wrapping at the row's ends β€” the operator's 2026-09-26 muscle-memory directive), up/cancel/back to the editor, expand keeping the focus, other keys releasing it into the editor path β€” and the scoped-view hand-off; every dock panel's exit (ESC/left from the heartbeats view, the bash view, the goal panel, or the scoped agents view's back keys) restores the dock's focus on the panel's own group instead of the prompt bar β€” the operator's 2026-09-26 panel-exit ruling; TS has no multi-group dock, its panels close to the editor, a sanctioned divergence); and the agents view (the unified live-roster +saved-catalog session list: Running/Idle/Inactive sections, inline search(TS query language: case-insensitive substring tokens, "quoted phrases",
re: regex, capped fuzzy fallback; the corpus is deliberately restricted to
the session name, the durable session id, and the cwd β€” the TS corpus fields
(first/last messages, the capped saved transcript, and
file paths) never match; Escape clears the query and the view restores the roster;
the query survives opening a chat and returning), open-to-attach/resume,
n-via-ctrl+n new session, live roster_subscribe pushes; the
selection is session-keyed, not index-keyed (TS
resolveAgentsViewSelectionState: row identity, then active-session id,
then session id re-find the same session across roster rebuilds β€” spawn
churn, activity re-sorts, and identical no-op re-pushes never move it;
a session that left the roster keeps the bounded position, TS parity),
and the list window follows the selection (TS renderSessionRows: the
slice centers on the selected row and clips the overflow behind
leading/trailing ellipses, so arrowing below the fold keeps the
selection on-screen instead of the view snapping back to the top of the
list; rows carry their session's β—· N heartbeat count from the
heartbeats_list catalog, refreshed on heartbeats_changed), ctrl+o's spawn-program surface (app.agents.program: the selected row's target β€” the row itself for a top-level agent, its parent otherwise β€” expands its list and shows the program rows, one code block per spawn cell above the children it launched, capped at 10 lines with the remainder marker, on the tool-panel background, skipped by the selection and the click surface, a second press hides it, and collapsing the list clears it), ctrl+r's rename mode (app.agents.rename: the prompt becomes the "Rename agent session" header over the name buffer β€” the search grammar, Enter submits the trimmed name through the daemon rename/rename_saved_session, Esc cancels back to the untouched query β€” a saved rename patches the catalog row in place), and the tui agents action adoption events (program_shown, renamed) that ride the view outcome out of the run.

Non-goals

No session logic, no providers, no loop policy. The interactive UI renders daemon events and sends user intents (prompts, abort, switch) as daemon commands; the session loop itself lives in the pa-daemon worker. It never computes agent behavior and never spawns the supervisor (launch semantics live in pa-cli).

The client commands of the clipboard/auth/update group: /copy (the last assistant text through the platform clipboard tool chain with the OSC 52 fallback β€” clipboard.rs + osc52.rs, the same emitter the selection-copy work can reuse; the daemon get_last_assistant_text supplies the text), /import (the path argument parse, the TS confirm, the typed error surfaces β€” the missing file, the missing session cwd with its fallback-cwd retry β€” and the post-import transcript rebuild through the daemon's import_jsonl), /login + /logout (the TS providers selector β€” provider_auth.rs, the panel showConfigurationMenu("providers") mounts: the tab bar, the search, the status indicators, the in-panel API-key prompt β€” over the composition root's auth flows), /traces (the TS status block and settings writes; the upload subsystem stays unported, its arms keep the TS state shapes and report the unavailability), and /update (the TS busy guard, the split-CLI child mapping β€” packages then the binary β€” and the self-update relaunch through the composition root's hook).

Public API

custom_message::{custom_message_entries, AGENT_MESSAGE_CUSTOM_TYPE, skill_invocation::{SkillInvocationRow, skill_invocation_entries}, ...} (the custom-type render dispatch shared by the live and replay paths; the user-message skill-invocation card decode; component row renderers are pub(crate)), app::run_app (replay), interactive::{run_interactive, InteractiveOptions, ModelSelection, SessionSelection, UiMode, HeadlessPlan, InteractiveOutcome} (daemon-attached), daemon_client::{DaemonClient, DaemonClientEvent} (wire client for the UI and the composition root), session::SessionStream, agents_view::{run_agents_view, AgentsViewOptions, AgentsViewUiMode, AgentsViewOutcome} (the view and its headless verifier seam), interactive::{OnboardingTask, OnboardingSink} (first-run flow surface; the composition root implements the sink against the settings manager), keybindings::{KeybindingsManager, migrate_keybindings_file, KEYBINDING_NAME_MIGRATIONS, format_key_text} (the effective-binding surface: the composition root loads the user config and passes the manager in; migrate_keybindings_file is the startup rewrite), queued::{QueuedMessages, QueueSelection, QueueSelectionItem, QueueLane, QueueBrowseKeys, render_queue, browse_header_text, mirror_lane_move} (the strip and browse state; the session UI owns the selection), prompt_stash::PromptStashStore (the per-process draft store the composition root owns; entry semantics are crate-internal), client_settings::ClientSettings (the client-process settings seam the interactive commands persist through β€” the composition root implements it over the real store), client_auth::{ClientAuthCommands, ClientAuthCommandsHandle, run_mcp_auth_command} (the /mcp login//mcp logout client surface: the TUI owns the command UX and mounts the inline auth panel β€” TS usage wording β€” while the composition root runs the login against it). mcp_view::{McpView, McpViewAction, McpConnection, McpToolInfo} (the /mcp inline connections view: the daemon get_mcp_connections roster rendered in the TS MCP-Connections-tab panel shape, with the per-connection status and tool detail; Enter resolves to the selected connection's login command, dispatched through the interactive loop's client-command seam). traces::{TracesCommands, TracesCommandsHandle, traces_base_url} (the /traces settings + credential seam), provider_auth::{ProviderAuthCommands, ProviderAuthCommandsHandle, ProviderRow, AuthType, AuthCategory, AuthFlow, AuthStatusIndicator, AuthStatusStyle, AuthSelectorKind, PRIME_INFERENCE_PROVIDER_ID} (the /login+/logout provider-auth seam: rows, status indicators, and the flow hook), auth_panel::{AuthPanel, AuthPanelHandle, AuthPanelRequest, PrimeTeamOption, PrimeTeamPick, PasteStyle} (the inline auth panel the login flows render through β€” TS LoginDialogComponent + PrimeTeamSelectorComponent: progress lines, the browser URL block, the paste prompts, and the Prime team picker, all over the run loop's request/reply channel), update_command::{UpdateCommands, UpdateCommandsHandle, UpdateOutcome, UpdateRunFuture} (the /update out-of-band runner seam: the TUI confirms, then spawns the composition root's funnel task and folds the landed outcome row), confirm::{ConfirmPanel, ConfirmAction} (the Yes/No confirm panel), and InteractiveOutcome::clipboard_emissions (the headless OSC 52 capture the verifiers assert with). Component internals pub(crate). altscreen::{enter, leave} is the process-wide alternate-screen ownership shared by the terminal surfaces: the first surface enters the screen, a real exit leaves it, and in-process view switches adopt the same buffer (no primary-screen flash between the agents view and a chat).

Depends on

pa-types only (the daemon wire protocol types live there). The headless UiMode is the verifier seam: it drives the identical attach/submit/stream/render path without a TTY and captures rendered frames.

Transcript caching

Fullscreen transcript frames render the visible entry window through a persistent entry cursor. Per-entry, per-detail line caches retain visited rows; unseen entries stay unrendered. Selection highlighting restyles only the rows the selection change touched (cached rows, never a rebuild from raw entries), and appends, entry growth, and tail re-anchors fold into the sparse window's bookkeeping instead of a geometry pass. Absolute-row operations, including paused detail changes, use separately cached count-only geometry and paint only the requested window. Cold geometry still scans uncached content; the exceptional copy fallbacks walk only the selected range from the viewport cursor. The frame's end sections β€” the splash and the streaming tail (the shortcut guide, pending bash output, the loaders) β€” render only when the visible window reaches them, and frames without a selection skip the restyle pass (its styled rows are the cached window verbatim). These caches and viewport coordinates are private to this crate; wire types and dependency direction are unchanged.


Overview (`crates/pa-types/README.md`)

pa-types

The single shared vocabulary crate. Nothing else is shared between crates.

Scope

compaction::is_valid_threshold_percent is the shared percentage-domain validator
used by settings deserialization, persistence, runtime policy, and the TUI.
Wire and domain types + serde only: AI messages/content blocks/tool calls/usage/stream events, session JSONL entry schema, daemon wire protocol messages, worker frames.

Daemon wire mechanics shared by the serving side (pa-daemon) and clients (pa-tui/pa-cli) live here because pa-tui depends on pa-types alone:

  • daemon::framing: the private-frame codec of the worker socket (direct-attach clients speak it too).

  • daemon::plane: the session/control command-plane table (worker-side peer gating and client-side socket routing both read it).

  • daemon::{DaemonPeerTransportTicket, DaemonWorkerPeerGrant, DaemonPeerCommand}: the direct-transport ticket and grant wire shapes.

  • goal: the thread-goal wire state (GoalState/GoalStatus) shared by the
    goal engine (pa-core), the daemon wire (goal_update session events and
    the attach snapshot's state.goal), and the TUI (announcement rows, tray
    label). The goal engine (validation, accounting, continuation prompts)
    is pa-core's.

  • slash_commands: the builtin slash-command table every surface shares
    (the TUI dispatch + autocomplete, the session engine's command admission,
    CLI suggestion help) plus its pure parse/suggestion helpers β€” the TS
    product keeps the same single table in core and imports it from its TUI.

  • incident: the daemon incident classifier shared by the incident CLI
    (pa-cli's prime-agent incident, TS src/cli/incident.ts) and the
    agents-view incident notice (pa-tui's incident_notices, TS
    src/modes/agents-view/incident-notices.ts) β€” pa-tui depends on
    pa-types alone, and the TS product keeps the same single classifier in
    cli/incident.ts with both halves importing it. Pure log-line parsing
    (agent.jsonl records, per-daemon plain-text lines), worker pid
    attribution, event classification, and the stall/burst/gap anomaly
    computation β€” the message shapes are the TS regexes kept 1:1. The CLI's
    log-file discovery, --since/--until window parsing, and report
    rendering belong to pa-cli; the notice polling, rotation-safe
    incremental reads, and dismissal horizons belong to pa-tui.

  • themes: the bundled theme definition files (prime/dark/light) as
    pure data, shared by the TUI's theme loader (pa-tui renders terminal
    colors from them) and the session HTML exporter (pa-core resolves them
    into CSS variables). Everything built on top of the files β€” terminal
    color rendering, export CSS generation, custom-theme discovery β€” belongs
    to the consuming crates.

  • daemon::update_flow: the update-flow state machine's shared vocabulary:
    the coordinator FSM states + legal
    transition table, UpdateId, the on-disk artifact schemas (intent.json,
    status.json β€” TS status-file shape, prepared//{roster,marker}.json),
    the roster-snapshot projection (sessions/workers/subagents/heartbeats,
    durable session ids, heartbeat re-arm fields only), the timeout-budget
    table with PRIME_AGENT_UPDATE_*_MS overrides, and the artifact path
    layout under <agent-dir>/update-restarts/. Pure serde + pure data
    helpers; the FSM drivers and watchdogs are owned by pa-daemon/pa-cli. The
    prepare/commit wire commands (prepare_update_restart/ commit_update_restart) and the TS mutation classification
    (is_daemon_mutating_command, is_update_drain_command β€” the
    update-flow admission gate's read tables) live in daemon::{command,plane}.
    Platform contracts (platform): the cross-crate transport, process-identity, socket-identity, and home-dir helpers, plus the suspend-to-background signal control (process::{stop_own_process_group, ignore_sigint_for_suspend, restore_default_sigint}: the TUI's TS-handleCtrlZ cycle β€” SIGTSTP to the own process group, SIGINT ignored for the stopped window and restored on the SIGCONT resume; unsafe lives behind this wall because pa-tui opts into the workspace unsafe_code forbid) (platform::dirs::home_dir: HOME, then on Windows USERPROFILE / HOMEDRIVE+HOMEPATH - Node os.homedir() parity, returning None so each caller owns its fallback). pa-types is the only crate every transport consumer can depend on (pa-tui depends on pa-types alone), so the shared trait vocabulary and its cfg-gated platform implementations live here: AF_UNIX on Unix, named pipes (\.\pipe\, platform/windows_pipe.rs) on Windows. Remaining platform areas (process control, perms, ...) are implemented per platform behind the same traits, not re-plumbed in callers.

Non-goals

No provider logic, no session logic, no UI. Beyond pure data helpers and the platform contracts, no behavior: a domain type that wants a method belongs in the owning crate.

Public API

Everything in this crate is deliberately pub - it is the cross-crate contract. Unknown fields survive round-trips via catch-all maps so schema revisions stay compatible.

Depends on

serde, serde_json, thiserror, anyhow, tokio, regex (the incident classifier's TS log-message patterns; see the incident scope entry). No workspace crates.


Overview (`scripts/release/README.md`)

Release publication

Eligible main commits still produce unique beta versions and tags through
nightly.yml. release.yml retains the five-platform build/reuse routes and
publishes versioned installer payloads to R2. Only stable tags create individual
GitHub releases; beta tags refresh the single prerelease named nightly.

For future beta versions, the public R2 prefix
releases/v<version>/ retains the entire published payload: platform tarballs,
Linux debug decoders, the npm migration bridge, SHA256SUMS, merged manifest,
beta.json, and per-target SPDX SBOMs. Audit files upload before channel pointers
advance, even when a newer main commit has superseded the build. The existing
provenance attestation step continues to cover tarballs and decoders. The rolling
nightly release also carries the full current payload and checksums.
After a successful nightly asset publication, the same serialized refresh moves
refs/tags/nightly to manifest.json's verified build commit and updates the
release title/notes with the version, source commit, and refresh time. It never
uses a newly read main HEAD. Failed uploads leave the existing tag unchanged;
a failed tag/metadata update fails the job and can be repaired by rerunning the
same version. GitHub's original release publication date is not rewritten.

Installers and native/npm updaters keep using the R2 channel manifests and
versioned payload paths anonymously; they do not require per-beta GitHub release
objects. Consumers of /releases/download/v/... must use the public R2
versioned prefix for future beta builds, or nightly for its current payload.
Stable GitHub asset URLs retain their existing behavior.

No historical releases or assets are removed. Versioned nightly asset names
continue to accumulate; retention and historical cleanup require a separate
decision. The R2 channel comparator repair remains in PR #3452; this change only
hardens the independent rolling GitHub newest-wins guard.

python3 scripts/release/test_release_publication.py executes the workflow shell
against local GitHub/R2 fakes, including stale reruns, failed reads, complete
payloads, interrupted bootstrap, and superseded beta archival. It performs no
network requests or real publication.


Overview (`vendor/crossterm/README.md`)

Travis Latest Version MIT docs Lines of Code Join us on Discord

Cross-platform Terminal Manipulation Library

Crossterm is a pure-rust, terminal manipulation library that makes it possible to write cross-platform text-based interfaces (see features). It supports all UNIX and Windows terminals down to Windows 7 (not all terminals are tested,
see Tested Terminals for more info).

Table of Contents

Features

  • Cross-platform
  • Multi-threaded (send, sync)
  • Detailed documentation
  • Few dependencies
  • Full control over writing and flushing output buffer
  • Is tty
  • Cursor
    • Move the cursor N times (up, down, left, right)
    • Move to previous / next line
    • Move to column
    • Set/get the cursor position
    • Store the cursor position and restore to it later
    • Hide/show the cursor
    • Enable/disable cursor blinking (not all terminals do support this feature)
  • Styled output
    • Foreground color (16 base colors)
    • Background color (16 base colors)
    • 256 (ANSI) color support (Windows 10 and UNIX only)
    • RGB color support (Windows 10 and UNIX only)
    • Text attributes like bold, italic, underscore, crossed, etc
  • Terminal
    • Clear (all lines, current line, from cursor down and up, until new line)
    • Scroll up, down
    • Set/get the terminal size
    • Exit current process
    • Alternate screen
    • Raw screen
    • Set terminal title
    • Enable/disable line wrapping
  • Event
    • Input Events
    • Mouse Events (press, release, position, button, drag)
    • Terminal Resize Events
    • Advanced modifier (SHIFT | ALT | CTRL) support for both mouse and key events and
    • futures Stream (feature 'event-stream')
    • Poll/read API

Tested Terminals

  • Console Host
    • Windows 10 (Pro)
    • Windows 8.1 (N)
  • Windows Terminal
    • Windows 10 x86_64 (Enterprise)
    • Windows 11 arm64 (Enterprise)
  • Ubuntu Desktop Terminal
    • Ubuntu 23.04 64-bit
    • Ubuntu 17.10
    • Pop!_OS ( Ubuntu ) 20.04
  • (Arch, Manjaro) KDE Konsole
  • (Arch, NixOS) Kitty
  • Linux Mint
  • (OpenSuse) Alacritty
  • (Chrome OS) Crostini
  • Apple
    • macOS Monterey 12.7.1 (Intel-Chip)
    • macOS Sonama 14.4 (M1 Max, Apple Silicon-Chip)

This crate supports all UNIX terminals and Windows terminals down to Windows 7; however, not all of the
terminals have been tested. If you have used this library for a terminal other than the above list without
issues, then feel free to add it to the above list - I really would appreciate it!

Getting Started

see the examples directory and documentation for more advanced examples.

Click to show Cargo.toml.

toml
[dependencies]
crossterm = "0.27"
rust
use std::io::{stdout, Write};

use crossterm::{
    execute,
    style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor},
    ExecutableCommand,
    event,
};

fn main() -> std::io::Result<()> {
    // using the macro
    execute!(
        stdout(),
        SetForegroundColor(Color::Blue),
        SetBackgroundColor(Color::Red),
        Print("Styled text here."),
        ResetColor
    )?;

    // or using functions
    stdout()
        .execute(SetForegroundColor(Color::Blue))?
        .execute(SetBackgroundColor(Color::Red))?
        .execute(Print("Styled text here."))?
        .execute(ResetColor)?;
    
    Ok(())
}

Checkout this list with all possible commands.

Feature Flags

toml
[dependencies.crossterm]
version = "0.27"
features = ["event-stream"]
Feature Description
event-stream futures::Stream producing Result<Event>.
serde (De)serializing of events.
events Reading input/system events (enabled by default)
filedescriptor Use raw filedescriptor for all events rather then mio dependency

To use crossterm as a very thin layer you can disable the events feature or use filedescriptor feature.
This can disable mio / signal-hook / signal-hook-mio dependencies.

Dependency Justification

Dependency Used for Included
bitflags KeyModifiers, those are differ based on input. always
parking_lot locking RwLocks with a timeout, const mutexes. always
libc UNIX terminal_size/raw modes/set_title and several other low level functionality. optional (events feature), UNIX only
Mio event readiness polling, waking up poller optional (events feature), UNIX only
signal-hook signal-hook is used to handle terminal resize SIGNAL with Mio. optional (events feature),UNIX only
winapi Used for low-level windows system calls which ANSI codes can't replace windows only
futures-core For async stream of events only with event-stream feature flag
serde serializing and deserializing of events only with serde feature flag

Other Resources

Used By

Contributing

We highly appreciate when anyone contributes to this crate. Before you do, please,
read the Contributing guidelines.

Authors

  • Timon Post - Project Owner & creator

License

This project, crossterm and all its sub-crates: crossterm_screen, crossterm_cursor, crossterm_style,
crossterm_input, crossterm_terminal, crossterm_winapi, crossterm_utils are licensed under the MIT
License - see the LICENSE file for details.


Overview (`scripts/release/native-compat/README.md`)

Native installer compatibility assets

Shipped TypeScript native installers validate these paths before activating a
release and whenever validating an installed release for rollback. Rust ships
the real historical assets to preserve that contract. They are not loaded by
Rust's renderer or image implementation.

  • theme/prime.json and export-html/template.html are unmodified from
    Prime Agent v0.9.8, commit 7d442aafa985f9342134fac16c2ef41f03fb45c1, paths
    packages/coding-agent/src/modes/interactive/theme/prime.json and
    packages/coding-agent/src/core/export-html/template.html.
  • photon_rs_bg.wasm and PHOTON-LICENSE.md are unmodified from
    @silvia-odwyer/photon-node 0.3.4, the dependency used by that release.
    Source: https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz
    Tarball SHA-256: 5a23015c1cd2c38e3c492dd96929985247b92f52d2ff0fb948d29edca52bc50a.
    Wasm SHA-256: 10468181565c56004c867f3a4af96f89a0ef5a63a72f2b5fb12c1f1992a3615c.

The assembler also supplies package.json for every release and copies the
maintained install-rust.sh to install.sh. The old updater keeps executing
its existing installer; the downloaded installer is not a migration hook.
The bundled installer is available for manual Rust installation/repair and
uses the Rust installer's arguments and environment contract.

Keep these wire-level file names unchanged while supporting those installers.


Overview (`crates/pa-daemon/testdata/acp/README.md`)

ACP differential corpus (slice 1)

Captures of the installed TS binary (prime-agent --mode acp --no-session --provider prime-inference --model z-ai/glm-5.3-flash) speaking ACP over
stdio, one file per scenario. Every file is line-delimited JSON:

text
{"scenario": ..., "direction": "in"|"out", "frame": {jsonrpc frame}}

out lines are what the scenario client sent; in lines are what the
binary answered, in order. The last line of each file is a meta row with
the process exit code and stderr.

Scenarios

  • happy_path: initialize, session/new (matching cwd), one text prompt,
    session/close. Shows the full completion envelope: chunks, then the
    responseBoundary info update (terminalQuiescenceExpected: true,
    outcome), the quiescence event, the terminalQuiescence envelope, then
    the {stopReason} response.
  • cwd_mismatch: session/new with cwd /tmp while the process runs in
    another directory. The result carries _meta.cwd {requested, actual}.
  • errors: unknown-session prompt/close, a second session/new on a live
    connection, an unknown method (-32601 with the observed message
    shape), close. Also demonstrates the one-initialize-per-connection
    behavior of the served surface.
  • second_initialize: a second initialize on the same connection is
    served normally.
  • cancel: session/cancel mid-turn. The prompt resolves
    {stopReason: "cancelled"} with no boundary frames after the streamed
    chunks.
  • tool_call: a turn that calls the ipython tool (6*7). Shows the
    tool_call / tool_call_update shapes including the content wrapper
    and the ipython rich-output _meta on results that carry attachments.
  • compact_command: two turns, then /compact. The session is too short
    to compact, so the observable parity is the namespaced compaction: {}
    update (the TS compaction_end with an undefined result) followed by the
    normal completion envelope and end_turn.
  • goal_command: /goal <objective> then /goal status. Shows the
    goal-start _meta.goal frame, mid-turn usage updates after each settled
    message, the completion observation, and the status-prompt frame.
  • autonomous_gate: --autonomous --autonomous-gate true. The gate passes
    after the first turn: the completion and terminal envelopes carry the
    _meta.autonomous accounting and remainingAutonomousContinuations
    reflects the configured budget; the stop reason is end_turn.
  • autonomous_limit: --autonomous --autonomous-max-tokens 1. The token
    limit stops the run: the envelopes carry the final accounting and the
    stop reason is max_tokens.
  • mcp_stdio: session/new with two valid servers (one stdio with a
    literal environment entry, one http with a header) plus one
    schema-invalid entry (missing the required env array): the ACP SDK
    zod filter (vecSkipError(zMcpServer)) silently drops it, admission
    succeeds with the survivors, and close releases them.
  • mcp_replace: admission, close, then a replacement admission with a
    different server list β€” the owner-scoped clear/re-release cycle.
  • mcp_errors: the full resolve-stage rejection matrix, every entry
    schema-valid: bad name pattern, duplicate name, NUL in the stdio
    command, duplicate environment entry (case-sensitive), duplicate HTTP
    header (case-insensitive, the reason reports the second casing),
    unsupported sse transport, embedded URL credentials (all -32602 with
    the exact TS reason payloads), then a name that passes the 64-char
    admission pattern but fails the 48-char tool-name pattern
    (-32603 with Invalid ACP MCP server name: ... details), then a
    valid admission and close. Failed admissions keep the connection
    serving.

The Rust captures in this directory

The rust-*.jsonl files are the evidence run for this slice: the same
scenario client against the Rust build with the same provider/model
(z-ai/glm-5.3-flash) and the kernel sidecar resolved through
PI_PACKAGE_DIR (a cargo-built binary has no sidecar next to the exe).

Since slice 4 the Rust ACP mode serves over the daemon-attached transport
(the TS shouldUseDaemonClient behavior): the binary spawns a supervisor
on the sandboxed --daemon-socket, hosts a client-owned daemon session,
and streams its session events as ACP updates. The daemon-attached
captures are: happy_path, cwd_mismatch, errors, second_initialize,
tool_call, compact_command, mcp_stdio, mcp_replace, mcp_errors β€” all
MATCH in full mode. cancel, goal_command, autonomous_limit, and
autonomous_gate were captured on the since-removed in-process
transport; recapture is pending (the files stay):

  • cancel: the capture matched in full mode; the daemon-attached run
    resolves the same {stopReason: "cancelled"} shape with no boundary
    frames, but a fresh worker's first-token latency exceeds the
    scenario's 2s cancel window, so the pre-cancel chunk count differs
    per run. The daemon-attached cancel semantics are locked by the
    deterministic e2e (acp_daemon_attached_cancels_mid_turn).
  • goal_command / autonomous_limit / autonomous_gate: the captures
    predate the daemon's goal_update / autonomous-accounting producers;
    recapture is pending.

Comparing a Rust run

compare_differential.py normalizes both captures (volatile ids, version,
model-generated text, sequence numbers) and compares the frame shape
sequences field by field. mcpCapabilities is compared exactly: both
binaries advertise { "http": true }:

text
python3 compare_differential.py ts-happy_path.jsonl rust-happy_path.jsonl

Both captures must come from the same scenario client with the same
request order. crates/pa-cli/tests/acp_mode_e2e.rs locks the deterministic
scenarios offline over the daemon-attached transport (the scripted faux
worker); the network-dependent scenarios (tool_call, cancel mid-turn) are
verified by running the scenario client against both binaries on a
networked box.

--landmarks drops model-behavior-dependent work frames (tool calls,
chunk streams, and mid-turn goal-usage updates) and compares the protocol
envelopes only. Use it for scenarios where the two model runs legally
diverge in their tool usage (goal_command: one run answered directly,
the other ran several ipython cells; autonomous_limit: one run wrote
the essay with tool calls, the other without β€” both sequences are
correct protocol behavior). The goal and autonomous frames themselves are
locked field-by-field by the offline e2e tests. All other scenarios
(happy_path, cwd_mismatch, errors, second_initialize, tool_call,
compact_command, cancel, autonomous_gate, mcp_stdio, mcp_replace,
mcp_errors) match in full mode.


Daily Stats (`docs/daily-stats.md`)

Daily Prime Agent stats

The GitHub Actions workflow Prime Agent daily stats prepares a report at 9 AM
America/Los_Angeles, including daylight saving changes. GitHub may start scheduled
runs late. The report covers the previous completed UTC day, matching the
Prime Agent Overview dashboard.

It reports DAU, WAU, MAU, token usage, completed runs, and run success rate. Daily
values compare with yesterday and the same weekday last week. WAU compares with
the preceding seven-day window, and MAU with the preceding 30-day window. Success
rate changes use percentage points. A zero baseline shows new (baseline 0);
no completed runs gives an unavailable success rate.

Active users count anonymous installations that emitted agent run completed,
including successful, failed, and aborted runs. They do not count verified people.
Tokens sum total_tokens, including recorded cache usage; they do not measure
spend. Success rate divides runs with outcome=success by all completed runs.
The query excludes PostHog test accounts and version 0.0.0-benchmark.

Setup and activation

  1. Add repository secret POSTHOG_PERSONAL_API_KEY: a
    PostHog personal API key with
    read access to queries and insights, limited to project 22174. A project capture
    token cannot read analytics. The script uses the EU
    query endpoint.
  2. Set repository secret SLACK_STATS_WEBHOOK_URL to the engineering bot's incoming
    webhook for #research-19-prime-agent. The webhook chooses the channel and sender.
  3. Merge the workflow to main. Run it manually with publish unchecked to
    validate access without sending a message. Logs show validation status only.
  4. After reviewing a local report, set repository variable
    PRIME_AGENT_STATS_ENABLED=true to enable daily delivery. Keep this variable
    unset or false to stop delivery. Manual sends also require this variable,
    publish checked, and the main branch.

Neither Codex nor n8n runs the scheduled report. An Actions preview does not receive
the Slack secret. Metric values never enter job logs, summaries, or uploaded artifacts.

For a local preview, supply the PostHog key through the environment and run:

sh
python3 scripts/daily_stats.py prepare --output /tmp/prime-agent-stats.json

Read the JSON file locally. Do not commit or upload it: it contains business metrics.
An existing query response can be replayed without network access:

sh
python3 scripts/daily_stats.py prepare --date YYYY-MM-DD \
  --response-file /tmp/posthog-response.json --output /tmp/prime-agent-stats.json

Delivery and recovery

The workflow serializes delivery attempts and saves an artifact named
prime-agent-stats-YYYY-MM-DD before contacting Slack. It contains only the
report date and workflow run ID, retained for 90 days. Manual report dates are
limited to completed UTC days within the last 60 days. A rerun with an existing
artifact skips delivery, including a timeout whose delivery outcome is unknown.
Failed preparation creates no delivery artifact and can be rerun safely.

An interruption after saving the artifact can leave a report unsent. Check the
channel and run first. If the report was never delivered, delete that date's
artifact in GitHub Actions before rerunning with publish enabled. Do not remove
the marker for a delivered report. The script never automatically retries Slack.
Incoming webhooks cannot delete messages; deletion requires the owning bot or a
Slack administrator.

Run the synthetic tests without credentials or network access:

sh
python3 -m unittest discover -s scripts -p test_daily_stats.py -v

Legacy Release Inventory.Json (`docs/legacy-release-inventory.json`)

{
"retrieved_at": "2026-10-09",
"github_release_pages": 2,
"github_release_count": 107,
"stable_count": 18,
"releases": [
{
"tag": "v0.9.8",
"target_commitish": "7d442aafa985f9342134fac16c2ef41f03fb45c1",
"published_at": "2026-09-29T22:06:42Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.8",
"npm_assets": [
{
"name": "prime-agent-0.9.8.tgz",
"digest": "sha256:d7b72785119efc28bfbca8ec4a7f47a1fcdcf47fcd8cebdb60bffaa79e3e1274",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.8/prime-agent-0.9.8.tgz"
}
]
},
{
"tag": "beta",
"target_commitish": "7d442aafa985f9342134fac16c2ef41f03fb45c1",
"published_at": "2026-07-17T23:04:57Z",
"prerelease": true,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/beta",
"npm_assets": [
{
"name": "prime-agent-0.9.8-beta.2286.1.7d442aa.tgz",
"digest": "sha256:7a311e2d7fd45c0b9f134197bb3101ebc11233c6c2bf649c4d574a0d98d3473b",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/beta/prime-agent-0.9.8-beta.2286.1.7d442aa.tgz"
}
]
},
{
"tag": "v0.9.7",
"target_commitish": "08ff1b2e2794ea9e8f4a08d12bc95408a66e1074",
"published_at": "2026-09-28T23:13:49Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.7",
"npm_assets": [
{
"name": "prime-agent-0.9.7.tgz",
"digest": "sha256:d696f2636cd7780d2d67296142bc1d13d72960c8be4e5cfae0bbba82f9d8707d",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.7/prime-agent-0.9.7.tgz"
}
]
},
{
"tag": "v0.9.6",
"target_commitish": "e260085dd8f742e0def3d871860c9a888b114851",
"published_at": "2026-09-24T01:01:53Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.6",
"npm_assets": [
{
"name": "prime-agent-0.9.6.tgz",
"digest": "sha256:e5bf0e349e55b3f75c79e66006c993b10c51ed1c9bf15863b06658fcaf0232b2",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.6/prime-agent-0.9.6.tgz"
}
]
},
{
"tag": "releases/v0.9.1-fhcache1",
"target_commitish": "da819d32f6a3f57e71f5d5fb784a4958929dbdae",
"published_at": "2026-09-20T03:45:42Z",
"prerelease": true,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/releases/v0.9.1-fhcache1",
"npm_assets": [
{
"name": "prime-agent-0.9.1-fhcache1.tgz",
"digest": "sha256:4a461bf7fc9d8e6e4e61471b66341aff05ba94e02c6c25f678851d26d1b27d2c",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/releases/v0.9.1-fhcache1/prime-agent-0.9.1-fhcache1.tgz"
}
]
},
{
"tag": "fhcache-candidate-1",
"target_commitish": "da819d32f6a3f57e71f5d5fb784a4958929dbdae",
"published_at": "2026-09-20T03:33:40Z",
"prerelease": true,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/fhcache-candidate-1",
"npm_assets": [
{
"name": "prime-agent-0.9.1-fhcache1.tgz",
"digest": "sha256:4a461bf7fc9d8e6e4e61471b66341aff05ba94e02c6c25f678851d26d1b27d2c",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/fhcache-candidate-1/prime-agent-0.9.1-fhcache1.tgz"
}
]
},
{
"tag": "v0.9.5",
"target_commitish": "a7d791bc1be09793ed5f3ec05bf4cccbc60679ea",
"published_at": "2026-09-16T00:13:49Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.5",
"npm_assets": [
{
"name": "prime-agent-0.9.5.tgz",
"digest": "sha256:349f1682c7909550842f1b04a71ba95814341b136474ade736df93f8ec006876",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.5/prime-agent-0.9.5.tgz"
}
]
},
{
"tag": "v0.9.4",
"target_commitish": "f771dfcedd684d1afff84ca2c6fa95c7a21efbc2",
"published_at": "2026-09-08T23:18:05Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.4",
"npm_assets": [
{
"name": "prime-agent-0.9.4.tgz",
"digest": "sha256:b8d752a53d11a8c9a7580e1fb5fc24f7ce74ccad979c7e6e6aa8880fc3ad90b0",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.4/prime-agent-0.9.4.tgz"
}
]
},
{
"tag": "v0.9.3",
"target_commitish": "915c78f42c248b08238dd27fcd4bcab32c60beab",
"published_at": "2026-09-06T10:44:04Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.3",
"npm_assets": [
{
"name": "prime-agent-0.9.3.tgz",
"digest": "sha256:ce71049389877770aa31b9be64c473685a86159adbbd2466bd43e4a9113242f1",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.3/prime-agent-0.9.3.tgz"
}
]
},
{
"tag": "v0.9.2",
"target_commitish": "9c54a35dac3a2ad17910074d66664859ea175666",
"published_at": "2026-09-05T19:14:39Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.2",
"npm_assets": [
{
"name": "prime-agent-0.9.2.tgz",
"digest": "sha256:d649b9f0258c77de7d02aba33d786925ce41a36b601e8e24eef0f00bdc2f1f49",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.2/prime-agent-0.9.2.tgz"
}
]
},
{
"tag": "v0.9.1",
"target_commitish": "81ae3cb34d27d38ee37f9e205a1e73694993b344",
"published_at": "2026-09-01T18:54:36Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.1",
"npm_assets": [
{
"name": "prime-agent-0.9.1.tgz",
"digest": "sha256:573bce0cd004fc62052e9a924089941b7f39266ab71e66a94c85a1f9d35835ba",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.1/prime-agent-0.9.1.tgz"
}
]
},
{
"tag": "v0.9.0",
"target_commitish": "c394506e2f0dd887b3f94908da9f2910b43c846b",
"published_at": "2026-09-01T18:18:51Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.9.0",
"npm_assets": [
{
"name": "prime-agent-0.9.0.tgz",
"digest": "sha256:fc560ec74195297999405d4fbfd0d308598cf502e0fccef7f02eb60032213108",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.9.0/prime-agent-0.9.0.tgz"
}
]
},
{
"tag": "v0.8.1",
"target_commitish": "514633727bf26d74f39f3119c2b0e31a5ceb2a9d",
"published_at": "2026-08-26T05:14:48Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.8.1",
"npm_assets": [
{
"name": "prime-agent-0.8.1.tgz",
"digest": "sha256:46c24db1782dd31adc35d5c6cbcc75564faba6ced3bf2ccf03d836ee77134475",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.8.1/prime-agent-0.8.1.tgz"
}
]
},
{
"tag": "v0.8.0",
"target_commitish": "8d7deeab5861bf9d77bde3d8511046a5c799818d",
"published_at": "2026-08-21T21:18:10Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.8.0",
"npm_assets": [
{
"name": "prime-agent-0.8.0.tgz",
"digest": "sha256:f5b0093c7e0fddb73f94773d74383585456adfa84f12a4082d3098f23bb8fab6",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.8.0/prime-agent-0.8.0.tgz"
}
]
},
{
"tag": "v0.7.4",
"target_commitish": "af0b8e00b9f704e834787fd321065ca78281f2aa",
"published_at": "2026-08-19T23:44:42Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.7.4",
"npm_assets": [
{
"name": "prime-agent-0.7.4.tgz",
"digest": "sha256:dea045d7c170466a102e030e940f459cc13f2c113465f40be1c7505d80405d2e",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.7.4/prime-agent-0.7.4.tgz"
}
]
},
{
"tag": "v0.7.3",
"target_commitish": "61131b2d195ba7a67a4ce8ac60bb10cecae07b67",
"published_at": "2026-08-17T22:38:17Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.7.3",
"npm_assets": [
{
"name": "prime-agent-0.7.3.tgz",
"digest": "sha256:2a188738318b91ff91ea77b8a0b215c41543cf0483fcb18a53475dc4e7032784",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.7.3/prime-agent-0.7.3.tgz"
}
]
},
{
"tag": "v0.7.2",
"target_commitish": "83a0f9f9566219551fcb6ffaf7f519a815749a58",
"published_at": "2026-08-11T19:00:48Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.7.2",
"npm_assets": [
{
"name": "prime-agent-0.7.2.tgz",
"digest": "sha256:bc5471f2a626d727b88a45eb745fff93b10c554a3c4fc5912f25d8c64b987f5e",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.7.2/prime-agent-0.7.2.tgz"
}
]
},
{
"tag": "v0.7.1",
"target_commitish": "95afd319a78ae017a41241d50b013d656a0685ce",
"published_at": "2026-08-07T18:39:08Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.7.1",
"npm_assets": [
{
"name": "prime-agent-0.7.1.tgz",
"digest": "sha256:d68612c83239caafab72cc76c55ac572bfd07a059ea8fbd2a3ddbe1f2b55dcdb",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.7.1/prime-agent-0.7.1.tgz"
}
]
},
{
"tag": "v0.7.0",
"target_commitish": "be9e2fa0714e7cd1c6bd9bdb1b554d2cc6550387",
"published_at": "2026-08-05T18:41:37Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.7.0",
"npm_assets": [
{
"name": "prime-agent-0.7.0.tgz",
"digest": "sha256:88b6578518c72cd51a825bc80f28e0fef9a64c67de4a7d6fd7afd7ca1b34da0b",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.7.0/prime-agent-0.7.0.tgz"
}
]
},
{
"tag": "v0.6.1",
"target_commitish": "8bd7c18f16bfdc356c1cd20fb9fcf01119147cda",
"published_at": "2026-08-05T14:55:26Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.6.1",
"npm_assets": [
{
"name": "prime-agent-0.6.1.tgz",
"digest": "sha256:4c7150dbfe5807c8480676d20c3104e63045b8ca2463abf38f021062e9fcacef",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.6.1/prime-agent-0.6.1.tgz"
}
]
},
{
"tag": "v0.6.0",
"target_commitish": "7db7b69c60be0f7b271faf948864891813b27182",
"published_at": "2026-08-05T04:36:20Z",
"prerelease": false,
"url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/tag/v0.6.0",
"npm_assets": [
{
"name": "prime-agent-0.6.0.tgz",
"digest": "sha256:8ef229a0422398174ba8f0ba4f7101adfc614b023a8f819f6ef3920e5d2350c6",
"browser_download_url": "https://github.com/PrimeIntellect-ai/prime-agent/releases/download/v0.6.0/prime-agent-0.6.0.tgz"
}
]
}
],
"support_scope": "Prime Agent 0.6.0 onward",
"rust_beta_tags": [
"v0.9.9-beta.10",
"v0.9.9-beta.4",
"v0.9.9-beta.6",
"v0.9.9-beta.7",
"v0.9.9-beta.8",
"v0.9.9-beta.9",
"v0.9.9-beta.11",
"v0.9.9-beta.17",
"v0.9.9-beta.18",
"v0.9.9-beta.21",
"v0.9.9-beta.22",
"v0.9.9-beta.23",
"v0.9.9-beta.24",
"v0.9.9-beta.25",
"v0.9.9-beta.26",
"v0.9.9-beta.27",
"v0.9.9-beta.28",
"v0.9.9-beta.29",
"v0.9.9-beta.30",
"v0.9.9-beta.31",
"v0.9.9-beta.32",
"v0.9.9-beta.33",
"v0.9.9-beta.34",
"v0.9.9-beta.35",
"v0.9.9-beta.36",
"v0.9.9-beta.37",
"v0.9.9-beta.38",
"v0.9.9-beta.39",
"v0.9.9-beta.40",
"v0.9.9-beta.41",
"v0.9.9-beta.42",
"v0.9.9-beta.43",
"v0.9.9-beta.44",
"v0.9.9-beta.45",
"v0.9.9-beta.46",
"v0.9.9-beta.47",
"v0.9.9-beta.48",
"v0.9.9-beta.49",
"v0.9.9-beta.50",
"v0.9.9-beta.51",
"v0.9.9-beta.52",
"v0.9.9-beta.53",
"v0.9.9-beta.54",
"v0.9.9-beta.55",
"v0.9.9-beta.56",
"v0.9.9-beta.57",
"v0.9.9-beta.58",
"v0.9.9-beta.59",
"v0.9.9-beta.60"
]
}


Legacy Upgrade Contract (`docs/legacy-upgrade-contract.md`)

Legacy TypeScript upgrade contract

This audit covers published Prime Agent releases from v0.6.0 onward, including
18 stable TypeScript releases through v0.9.8 and relevant candidate and beta
artifacts. Earlier versions are excluded from the requested support scope.
Inventory and source comparison are not evidence that an installed-artifact
upgrade or every supported platform has passed testing.

Published inventory in scope

A full two-page GitHub Releases API query on 2026-10-09 returned 107 releases.
The compact release inventory records the 18
stable releases in scope, independent GitHub npm-asset digests, source targets,
and relevant TS prereleases. The stable versions are 0.6.0–0.6.1,
0.7.0–0.7.4, 0.8.0–0.8.1, and 0.9.0–0.9.8.

All 18 have genuine published prime-agent-VERSION.tgz assets; this inventory
does not count inherited upstream Git tags as Prime releases. The unscoped
public npm registry endpoint for prime-agent returned 404 during this audit;
these npm packages are published as tarballs through GitHub and the release bucket.

Additional published TS artifacts are 0.9.1-fhcache1 (the same digest hosted by
both fhcache-candidate-1 and releases/v0.9.1-fhcache1) and the rolling beta
release, currently 0.9.8-beta.2286.1.7d442aa. The latter has npm and native
archives. The fhcache package digest, independently supplied by the GitHub API,
is 4a461bf7fc9d8e6e4e61471b66341aff05ba94e02c6c25f678851d26d1b27d2c.
It has no published SHA256SUMS file. A locally computed digest alone is not
independent download verification.

The 49 versioned Rust 0.9.9-beta.* releases are distinct from TS migration
fixtures and require Rust-to-Rust update coverage. Their exact published tags
are recorded in the inventory. The nightly release is a rolling Rust asset host.
Historical rolling TS beta assets are overwritten on GitHub; this inventory
cannot establish an exhaustive list of every beta ever distributed through the
bucket. Do not equate an absent GitHub beta tag with an unpublished build.

Authoritative release sources

Use the GitHub release target_commitish below for source comparisons of the
audited versions, then verify behavior against the actual published artifacts.
Several local version tags resolve to upstream history with different package
names and no Prime Agent updater. Those tags are not suitable fixtures for these
releases. A recorded source target alone does not prove the bytes shipped; release
assets remain the authoritative end-to-end test inputs.

Release GitHub release target commit
v0.6.0 7db7b69c60be0f7b271faf948864891813b27182
v0.6.1 8bd7c18f16bfdc356c1cd20fb9fcf01119147cda
v0.7.0 be9e2fa0714e7cd1c6bd9bdb1b554d2cc6550387
v0.7.1 95afd319a78ae017a41241d50b013d656a0685ce
v0.7.2 83a0f9f9566219551fcb6ffaf7f519a815749a58
v0.7.3 61131b2d195ba7a67a4ce8ac60bb10cecae07b67
v0.7.4 af0b8e00b9f704e834787fd321065ca78281f2aa
v0.8.0 8d7deeab5861bf9d77bde3d8511046a5c799818d
v0.8.1 514633727bf26d74f39f3119c2b0e31a5ceb2a9d
v0.9.0 c394506e2f0dd887b3f94908da9f2910b43c846b
v0.9.1 81ae3cb34d27d38ee37f9e205a1e73694993b344
v0.9.2 9c54a35dac3a2ad17910074d66664859ea175666
v0.9.3 915c78f42c248b08238dd27fcd4bcab32c60beab
v0.9.4 f771dfcedd684d1afff84ca2c6fa95c7a21efbc2
v0.9.5 a7d791bc1be09793ed5f3ec05bf4cccbc60679ea
v0.9.6 e260085dd8f742e0def3d871860c9a888b114851
v0.9.7 08ff1b2e2794ea9e8f4a08d12bc95408a66e1074
v0.9.8 7d442aafa985f9342134fac16c2ef41f03fb45c1

Update families

  • v0.6.0–v0.9.4 ship npm tarballs. Their
    packages/coding-agent/src/utils/version-check.ts files are byte-identical:
    SHA-256 5a87851fc4f6473452607cb2816945e82f37e35f3aa2ecc846b3d41f1932aa29.
  • v0.9.5–v0.9.8 additionally ship managed native archives. Their manifest parsers
    are byte-identical: SHA-256
    2a37f5116a6d855da564bb3efe4a6b62eb0d0a35523c369796b5529379dacff0.
    Their cli/native-update.ts files are also byte-identical: SHA-256
    707cdc796f035f01c08647c026a1f1a5aaeafbb3b4158a22e741b367a8d10f03.

All stable clients read latest.json under PRIME_AGENT_DOWNLOAD_BASE_URL, or
the default release bucket. Beta clients read beta.json. The parser accepts
version, package (or packageName), and tarball; relative tarball URLs
resolve against the release bucket. Keep the npm tarball in the manifest even
when native artifacts are present.

npm bridge requirements

The published package builder rewrites the source package to prime-agent
and the command to prime-agent, with entrypoint dist/bundle/cli.js.
Do not infer the published package identity from the source package.json.

The existing updater runs the detected global package manager against the
manifest tarball. Supported command builders include npm, pnpm, yarn, and Bun;
that source support is not a claim that each has been integration-tested.

Keep the package name and dist/bundle/cli.js and
dist/bundle/cli-node.js paths stable. The bridge also retains a thin
dist/cli.js alias; that compatibility alias does not expand the tested version
scope. In published v0.9.5–v0.9.8 npm
artifacts, the packer moves the TS bundle to cli-node.js and puts the old
native-migration shim at cli.js. When that shim falls back to Node, the
updater's original entrypoint is cli-node.js. After package replacement the
old CLI and TUI execute their original Node executable with that original
absolute JavaScript entrypoint path. Both paths must run the new Rust bridge;
a new command shim elsewhere does not repair this relaunch. The bridge must forward arguments, signals, terminal
I/O, and exit codes and remain usable when package-manager install scripts are
disabled. It must not remove its own package while the old process still needs
to relaunch through it.

Failed migration and retry

The bridge includes the checksum-pinned, genuine v0.9.8 TypeScript package as
recovery. A host that cannot execute Rust must retain a usable public command,
daemon, worker, and saved sessions. Explicit updates report failure; ordinary
launches use recovery without repeatedly attempting the same failed install.

For bridge-driven channel installs, the native executable must pass the bounded
staged version probe before the installer replaces the public launcher. A valid
archive checksum alone does not establish that the host can execute its payload.

An explicit retry after failure must consult the current release through the
preserved updater, including downloading its updated bridge and installer.
Retrying only the failed bridge's pinned version would prevent a later release
from adding support for that host. The retry may report success only after a
working native version and its corresponding migration receipt are verified.

Managed native requirements

The v0.9.5–v0.9.8 updater executes the installer bundled in the old release.
Replacing only the new archive's install.sh cannot affect that installation.
The old installer downloads
releases/vVERSION/prime-agent-VERSION-PLATFORM.tar.gz, checks its checksum,
and requires these regular files at the archive root:

  • prime-agent
  • package.json with the target version
  • install.sh
  • prime-agent-runtime/pyproject.toml
  • prime-agent-runtime/src/rlm/repl.py
  • theme/prime.json
  • export-html/template.html
  • photon_rs_bg.wasm

It requires prime-agent --version to print the bare target version and
prime-agent --help to succeed. It then installs to
releases/VERSION-PLATFORM-SHA256[.SUFFIX]/, writes .archive-sha256 and
.install-source, and atomically changes bin/prime-agent. The managed-root
marker is prime-agent-native-v1.

These same legacy files are revalidated by the still-running TS client before
it can find the new launcher for restart. They must remain present after
activation, including the two metadata files written by the old installer.
The next Rust update must recognize this managed installation layout.

The manifest parser prefers binariesV2 over binaries; each accepted entry
must have an exact platform-specific filename and lowercase 64-digit SHA-256.
Retain the legacy platforms in the manifest only when the corresponding
artifacts can actually run on those hosts.

Daemon and TUI handoff

All audited versions launch the newly installed entrypoint with:

text
update --internal-update-restart-coordinator --daemon-socket SOCKET
       --internal-update-restart-status STATUS_PATH
       [--internal-update-restart-origin SESSION_ID]

The TS parent creates the status directory but does not create a staged
status file
. The coordinator must initialize it. TS polls a version-1 JSON
record with requestId, socketPath, phase, coordinator.pid,
counts: {total, restored, resumed, failed}, startedAt, and updatedAt.
Terminal phases are complete, skipped, and failed; other phases include
starting, preparing, stopping, starting_daemon, and restoring.
heartbeatAt, predecessor/successor identities, failures, and message are
optional. Keep progress live during a long restore.

This differs from the newer Rust staged-update FSM and its status schema.
Recognizing the flag alone is insufficient: a legacy adapter must initialize
and maintain TS-compatible status and use the TS daemon's
prepare_update_restart / shutdown / session restoration contract. The parent
otherwise reports a restart warning even if installation succeeded.

For /update, the old TUI runs the update subprocess with
PRIME_AGENT_INTERACTIVE_SELF_UPDATE=1, disposes its old connection, launches
the coordinator itself, and relaunches with its saved session file. Do not
restart the daemon prematurely from the installer or an executable probe.

The TS daemon persists its restart manifest before replying to preparation.
A lost reply therefore does not prove that preparation failed. Record a durable
binding to the predecessor, socket, target release, and attempt before sending
the request; recover a fresh scoped TS manifest only through that binding.
Retain pending source manifests through ordinary startup cleanup until their
state has been adopted. A definitive preparation refusal with no new manifest
must allow another attempt. Queued actions must remain paused while the resumed
continuation is admitted, so follow-up input cannot run ahead of that turn.

Platform limits and release claims

The old native distribution supports Darwin arm64/x64 and Linux arm64/x64,
including linux-arm64-musl, linux-x64-baseline, linux-x64-musl, and
linux-x64-musl-baseline. The Rust release matrix currently ships GNU Linux
builds with a GLIBC 2.35 ceiling and no musl target. A GNU archive must never be
renamed to a musl suffix: that would turn a controlled refusal into a broken
installation. Supporting Alpine/musl requires real musl artifacts and runtime
verification. Older glibc hosts likewise need a compatible build or a clear
unsupported-platform result before activation.

The TS baseline suffix selects x64 CPUs without AVX2. Rust can reuse its
ordinary x64 artifact for that alias only after verifying its target CPU and
all bundled native dependencies work without AVX2; the absence of a separate
Rust baseline target is not by itself proof.

β€œAll versions since 0.6” describes the source-version range, not an exemption
from platform support. Do not claim every prior platform migrates until those
platform gates pass. Test all 18 published npm releases, all four native source
releases, CLI and TUI entrypoints, session preservation, daemon handoff, and a
second Rust update using assembled artifacts.


Legacy Upgrade Evidence (`docs/legacy-upgrade-evidence.md`)

Legacy update compatibility evidence

This is unreleased-candidate evidence, not approval to publish v1. All builds
and runtime tests after the remote-only instruction ran in Prime sandboxes.
Support scope starts at 0.6.0; no earlier versions are required.

Current verification status

The recovery-hardening snapshot passed the full repository gate, but its real
TS /update rerun exposed a request-ID collision after reconnecting: TS replayed
the preparation response instead of executing shutdown. The follow-up uses
distinct preparation and shutdown envelope IDs. A deliberate collision fails
the regression; the fix passes all six coordinator tests. Full gates and actual
migrations for this follow-up remain pending. Earlier results below are not
attributed to the latest source.

The consolidated full run encountered ETXTBSY (text file busy) in the existing
update::download::tests::stages_a_local_payload_directory test. A diagnostic
single-test run and the complete core library suite then passed (1,160 passed,
3 ignored), without product changes or retry logic. The cause is unproven; the
original failure is retained in make-check-hardened-etxtbsy-red.log. One fresh
full run passed; this does not by itself establish that the failure is fixed.

Check Verified result
npm CLI and TUI migration, all 18 stable TS versions 36/36 on optimized candidate before final recovery hardening
Native CLI and TUI migration, TS 0.9.5–0.9.8 8/8 on that optimized candidate
Subsequent Rust 1.0.0 β†’ 1.0.1 update Passed through the original custom public command
Fresh curl installation and reinstallation Passed with final staged-probe installer
Unusable native executable Red reproduced; fixed installer retained usable TS recovery
Failed migration followed by a newer release Original update command reached Rust 1.0.1
Lost prepare response and retained queued work Six focused coordinator tests passed; red-first regression reproduced
Startup cleanup preserves pending recovery manifests Two focused tests passed
Busy-session continuation before follow-up input Real worker regression reproduced red; 19 focused restore tests passed
npm bridge unit suite 25 passed, including IPC, fd3, signals, retry, and prefix behavior
Packaging suites 39 passed: native compatibility, restamping, decoder, catalog
Consolidated full repository gate fmt, clippy, 5,206 tests passed (19 ignored), release build passed

Published-version matrix

The npm runs use unchanged, checksum-verified published packages and their real
public CLI/TUI entrypoints. Every version below passed both commands.

Versions CLI cases TUI cases
0.6.0, 0.6.1 2 2
0.7.0–0.7.4 5 5
0.8.0, 0.8.1 2 2
0.9.0–0.9.8 9 9

Native installs additionally cover every stable native TS release,
0.9.5–0.9.8, using each archive's unchanged old installer.
The TUI runs require completed restoration, the original saved session attached
to a Rust worker, a preserved transcript prefix, responsive input, and no
restart warnings. CLI checks include version/help, checksum refusal, state
preservation, and paths with spaces.

The optimized Linux x64 snapshot tested before final recovery hardening:

  • Rust 1.0.0 archive: 7a5e663b535d1755504f8315d4f13de0857ed746ac5c6fdc6c5e55db84ec11b3.
  • Rust 1.0.1 archive: f46a36e1456dc6d7ddfefaf85bad68b29a0b06d863cb1577ce5e3445d4cad97a.
  • Shipped executable: 0dbfb29af17614870f613017de2c6a11b25aa80572390a61dbe4d42c36335227.

Independent source archive digests and the exact version inventory live in
legacy-release-inventory.json.
The compatibility contract describes the layouts,
wire fields, and required recovery behavior.

Failure and usability evidence

Unusable executable: the actual TS 0.6.0 updater installed a checksum-valid
archive whose native executable named a nonexistent interpreter. With the
old probe condition, the npm launcher was replaced and the command exited 127.
With the fix, the staged probe refused activation, retained the npm symlink,
wrote no success receipt, and left a genuine TS 0.9.8 daemon/worker able to
resume the original session and accept input. The npm/native prefixes overlapped.

Future retry: after an unsupported architecture caused migration to fail,
the feed advanced from 1.0.0 to 1.0.1 and host support was restored. The original
public update command fetched the new bridge and installer, activated Rust
1.0.1, and resumed the saved session. An explicit retry that remains unsupported
must still fail, despite the old TS updater's warning-only success exit.

Lost response: a Unix-socket protocol fixture writes a TS-shaped manifest
containing queued work, then drops the prepare reply. The Rust coordinator
recovers it through its durable predecessor/socket/attempt binding before
shutdown. Disabling recovery makes the regression fail. Tests reject foreign
owners and stale data and permit retry after a definitive preparation refusal.
Startup cleanup preserves pending source manifests until adoption.

Busy queue: a real scheduled Rust worker with a scripted engine proves that
restored follow-up input stays parked until continuation admission. Disabling
the input pause consumes the queue too early; restoring it passes.

Usability: separate real TS 0.6.0 npm and TS 0.9.8 native handoffs execute
and persist a controlled Bash command after restoration, preserving prior
transcript bytes. No provider inference or IPython execution was tested.

The genuine fallback is the complete published TS 0.9.8 package, pinned to
SHA-256 d7b72785119efc28bfbca8ec4a7f47a1fcdcf47fcd8cebdb60bffaa79e3e1274.
The final staged-probe installer tested here is
323328cea0ede4d60aedfef486ba383ea081457e65ccedb1782ca6a0575a7f09.

Reproduction and retained reports

Run these only in an isolated test host, with temporary homes and no real
credentials. Harnesses accept candidate archives and report paths; use their
--help for the published source archive/checksum/prefix arguments.

Harness Purpose
scripts/release/verify_legacy_release.py Download and verify a genuine native TS release, then exercise its updater
scripts/release/test_legacy_upgrade.py Native CLI, checksum refusal, custom paths, second Rust update
scripts/release/test_legacy_npm_upgrade.py Genuine npm CLI migration
scripts/release/test_legacy_tui_upgrade.py Actual npm/native TUI update with saved-session restoration
scripts/release/test_legacy_npm_fallback.py Unsupported host, unusable payload, and later-release recovery
scripts/release/test_legacy_runtime_restart.py Real post-migration worker command execution
scripts/release/test_channel_install.py Current curl installer, reinstall, and user-file preservation

Reports, hashes, TUI captures, and logs were downloaded without agent binaries
to /tmp/prime-legacy-artifacts/ in the maintainer workspace. Key reports:
native-upgrade-evidence-optimized.tar.gz, native-tui-optimized-0.9.{5,6,7,8}.json,
channel-install-probe-final.json, probe-red.json, probe-green.json,
future-release-recovery.json, legacy-recovery-red-green.tar.gz, and
make-check-hardened.log. Matrix bundles contain each case's exact archive and
bridge hashes. Intermediate evidence remains in Git history and
evidence-history-before-condensing.md beside the raw reports.

Remaining release gates and limits

  • Complete full gates and the migration matrix against the corrected optimized artifact.
  • Verify production artifacts on all supported hosts. The Linux candidate was
    built on Debian 12 with fixture catalog data. Its highest required GLIBC
    symbol is 2.34, but that is not an Ubuntu 22.04 runtime test.
  • Stable release CI builds and livechecks Linux x64/arm64 in pinned Ubuntu 22.04,
    and gates fresh installation plus TS migration on Linux and macOS arm64.
    macOS x64 currently has a Rosetta version probe, not a full migration gate.
  • Older macOS arm64 CLI runs, two Rust beta CLI runs, and retained TS prerelease
    runs passed earlier snapshots; they do not establish final platform coverage
    or coverage of every historical beta.
  • GNU binaries do not establish musl, older-glibc, or unsupported-CPU support.
    A working TS fallback is not a successful Rust migration.
  • npm was integration-tested; pnpm, Yarn, and Bun were not. Beta artifact reuse
    does not execute the new fresh-install gate.
  • No final live-catalog, public stable endpoint, release-tag, or publication
    verification is claimed. Full rendered-frame and exhaustive wire-byte parity
    were not measured; the unchanged TS clients exercise the actual handoff.

Slack Mcp (`docs/slack-mcp.md`)

Slack MCP connector

Open /mcp (or /plugins), search for Slack, and connect. You can also
run /mcp login slack inside Prime Agent. Complete Slack's consent screen in
your browser. The connection becomes available to the current session through
the generic mcp Python API:

python
tools = await mcp.list_tools("slack")
# Read the returned inputSchema before calling a tool.

Prime Agent connects to Slack's hosted Streamable HTTP server at
https://mcp.slack.com/mcp. It uses the public desktop OAuth client published
in Slack's agent harness guide:
client ID 1601185624273.8899143856786 and callback
http://localhost:3118/callback. This is the client used by Slack's Claude Code
plugin, so Slack's consent screen may identify the app as Claude. Prime Agent
uses PKCE and state validation, then stores the grant in its own auth store;
no Claude installation or credentials are needed.

Slack does not offer dynamic client registration. Workspace administrators
still control app approval and granted scopes. The desktop client registration
is controlled by Slack and its partner; changes there can require an update.

If port 3118 is occupied, finish or close the other login attempt and retry.
Prime Agent reports the conflict instead of redirecting to an unregistered
port. For SSH sessions, forward local port 3118 to port 3118 on the host
running Prime Agent, or paste the final redirect URL into the auth panel.

Disconnect with /mcp logout slack. Connector usage contributes to the existing
mcp_connector_use_count session telemetry counter; server names and Slack
content are not recorded in that counter.


--- METRICS ---