Expand description
fno-agents substrate crate (Phase 6, ab-a09e1eaf).
This crate is the Rust substrate for PTY-managed agents (codex / gemini / future OpenCode). It is split per the design’s Locked Decisions:
- shared types (this module):
ShortId,AgentStatus,ParsedEvent(LD9, sealed enum),MonotonicTimestamp(count-during-sleep clock). - [
pty]: PTY spawn + bounded-ring output drainer (LD31). write_queue: bounded-backpressure stdin queue +write_queue::WriteMsg.supervisor:supervisor::RestartPolicystate machine + hard ceiling (LD36).readiness:readiness::ReadinessDetectortrait +UnknownReadinessSignal(Open Question #9: no generic byte-count fallback; per-CLI signal mandatory).
§Scope of Wave 1 (this PR)
Wave 0’s smoke prototype (cli/scripts/smoke/pty-survival/) refuted the
“direct daemon-owned PTY survives daemon restart” assertion: a child on a
PTY whose master the supervisor owns is SIGHUP’d and dies the instant the
master closes. The locked outcome (Outcome B) was a per-agent worker process
that owned the master and outlived the daemon. That daemon-owned PTY hosting
was retired at G4: the mux is now the agent-PTY substrate, and this crate
keeps the registry, inside-leg reports, and the claude stream-json adopt lane.
Deliberately deferred (documented seams, not gaps):
alacritty_terminalgrid wiring + per-CLIreadiness::ReadinessDetectorimpls -> Wave 2, alongside the smoke captures that define the grid patterns (the trait operates overreadiness::ScreenViewso Wave 2 only adds impls).tokioruntime integration -> Wave 3 (the daemon is its only consumer; the substrate stays runtime-agnostic and is driven fromspawn_blocking).
§Scope of Wave 2 (this PR)
Wave 2 fills the seams Wave 1 left:
provider:provider::Provider+provider::ProviderWithPtytraits (LD8) and the three impls (provider::ClaudeProvidershellout,provider::CodexProvider/provider::GeminiProviderPTY-managed).envelope:envelope::Envelopestructural anti-injection wrapper (LD15).screen: the terminal-grid construction behindreadiness::ScreenView(the per-CLIreadiness::ReadinessDetectorimpls now live inreadiness).
Modules§
- active_
backlog - Active backlog dispatcher: the mission drain-tick core + circuit breaker.
- agents_
config - Read
agents.<provider>.headless_yolo(and sibling knobs) from config.toml. - agy_ask
- Client-side
agy -pask path (Phase C, agy harness). - claims
- Native work-claim substrate: a second implementation of the lockfile
protocol owned by
cli/src/fno/claims/(Python stays the reference implementation and the only CLI surface). - claude_
adopt - Adopt an externally-spawned
claude --bgworker into the fno registry. - claude_
ask - Client-side
claude --bgask path (ab-cc926b4e). - claude_
attach - Speak Claude’s daemon
control.sock: theop:'attach'handshake + the newline-delimited JSON transport. - claude_
drive - The proven drive primitive: inject a turn into an adopted
claude --bgsession and confirm delivery by reading the session transcript. - claude_
roster - Read Claude Code’s own daemon roster and resolve its
control.sock/control.key. - client
- Client side of the daemon protocol (Wave 3): lazy-start the daemon, connect, and forward one request. Kept in the library so it is exercised by the integration tests without shelling out to the compiled binary.
- client_
verbs - Client-side
fno-agentsverbs ported from the Pythonfno agentsapp (the “Python-only verbs” bucket:drive-authority,trace,ping,attach,resume, plus the non-streaminglogspaths). - codex_
ask - Client-side
codex execask path (ab-0429c6e1). - codex_
inject mail-inject --harness codex: LIVE delivery into a running codex session over the codex app-server daemon socket (US8, node x-d899). The codex sibling ofcrate::mail_inject’s claudecontrol.sockpath. Python’s send path (_mail_inject_codex) runs this as a subprocess and falls back to the durable bus ONLY when it reports not-delivered (live-inject-first).- daemon
- The supervisor daemon (Wave 3, tasks 3.0 + 3.4).
- delivery_
completion - Strict process boundary for explicitly activated generic delivery.
- digest
fno-agents digest— the “while you were gone” fold (x-4e2d).- drift
- Daemon binary-version drift detection (ab-1891cdff).
- envelope
- Structural anti-injection envelope (design module
envelope.rs, LD15). - events
- Operator-facing
events.jsonlemitter (Wave 3, task 3.2). - finalize
fno-agents finalize(control-plane step 6, ab-f8e5f214): the terminal-only WRITER the stop-hook shim invokes on a terminal-allowloop-checkdecision.- gc
- Dead-row garbage collection decision (x-b1aa).
- gemini_
ask - Client-side
gemini -pask path (ab-73da4ac2). - kill_
criteria fno-agents kill-checkverb — Rust port ofscripts/lib/kill-criteria.sh(packaging EPIC ab-8bdb4642, eliminate-don’t-vendor leg).- logs
- Daemon-side
agent.logsstreaming handler (Category C / ab-d82655d7). - logs_
client - Client side of
fno agents logs <name> --followfor codex/gemini (Category C / ab-d82655d7). - loop_
dispatch - Shellout dispatcher that wraps the bash driver-lib contract.
- loop_
runtime - Unified loop runtime primitive for target, megawalk, and megatron drivers.
- loop_
target - Target driver: TargetQueue + the
loop runCLI verb. - loopcheck
fno-agents loop-checkverb (Task 1.1, ab-d0337fbc).- mail_
inject mail-inject: the one-shot LIVE-DELIVERY verbfno mail sendcalls to inject an a2a turn into a LIVE adoptedclaude --bgsession over the daemoncontrol.sock. Python’s_deliver_liveruns it as a binary subprocess and falls back to the durable bus queue ONLY when this reports not-delivered (live-inject-first, durable fallback – node x-1f23, epic x-07c1).- manifest
- Detection manifest engine (E6.2).
- needs
fno-agents needs- the needs-me-queue events-fold leg (x-feec).- nudge
- P2 loop-boundary inbox nudge (ab-098967b4).
- opencode_
ask - Client interceptor for the
askverb on an opencode target (x-51f6), plus the headless one-shot dispatch (dispatch_opencode_once). - osc
- OSC (Operating System Command) capture for the readiness read loop (E6.1).
- paths
~/.fno/agents/filesystem layout (Wave 3).- protocol
- Unix-socket wire format (Wave 3, task 3.1).
- provider
- The
Providerabstraction (design moduleprovider.rs, LD8). - readiness
- Readiness detection (design module
readiness.rs). - scrape
- Screen-manifest scrape sweep: the fallback rung of the badge lattice (pane-exit > inside-leg hook > screen-manifest > liveness).
- screen
- Terminal-grid construction behind the
ScreenViewseam (Wave 2). - spawn_
gate - Spawn gate (x-c5cc): global concurrency cap + free-RAM floor + queue loop.
- state
- Shared state files (Wave 3):
registry.json(schema v4) and per-agentstate.json(schema v1), plus the flock-protected, atomic read/modify/write helpers the daemon and worker share. - stream_
worker - Per-session stream-json worker (Group 1, Outcome B) — the claude analog of the retired PTY worker lane.
- subprocess_
ask - Shared one-shot-subprocess primitives for the client-side
askports (codex + gemini). Extracted fromcodex_ask.rs(ab-73da4ac2) so the SIGINT forwarding, process-group kill, grace reap, watchdog, and output tee live in ONE place and the PR #371/#372 hardening carveouts apply to every provider: - subscribe
fno-agents subscribe– stream registry state transitions + pane exits as newline-delimited JSON.- supervisor
- Restart policy state machine (design module
supervisor.rs). - terminal_
stop - Terminal-stop markers for fire-and-forget
claude --bgworkers (x-fcbf). - verify_
evidence fno-agents verify-evidenceverb — Rust port ofscripts/lib/verify-event-evidence.sh(packaging EPIC ab-8bdb4642, eliminate-don’t-vendor leg).- version
- Shared version reporter for the fno-agents bins (client, daemon, worker).
- wait
fno-agents wait– block until a named agent reaches a target state.- write_
queue - Bounded-backpressure stdin queue (design module
write_queue.rs).
Structs§
- Monotonic
Timestamp - A monotonic timestamp that counts during system sleep, used for drive-window heartbeat math (LD17 + Domain Pitfall: macOS/Linux suspend divergence).
- ShortId
Enums§
- Agent
Status - Agent lifecycle status.
state.statusis canonical;registry.statusis a denormalized projection of it (LD10). Serialized snake_case for the JSON state files and the cross-language schemas. - Parsed
Event - Sealed event vocabulary every provider parses INTO (LD9). Variant additions
are a one-line crate-wide change; no per-provider enums.
#[serde(tag="kind")]matches the wire shape in the design’s Architecture section. - Short
IdError - A short, opaque agent identifier (e.g.
wkA). Stored in the registry and used to name per-agent state directories. Validation is intentionally light at this layer; dispatch-layer validation (US1 invariant) owns argv rules.
Constants§
- KNOWN_
EVENT_ KINDS - All real operator-facing event kinds emitted by the Rust supervisor. Excludes test-only kinds (tick, heartbeat).
Functions§
- emit_
schema_ json - Build the unified (x-2901) events.jsonl envelope JSON Schema and the
status-v1AgentState schema as static JSON objects.