Terminal hypervisor for AI agent swarms: real-time pane capture, state-machine pattern detection, and a JSON API for coordinating fleets of coding agents across WezTerm
ft โ FrankenTerm
What's here
| If you're... | Start with | |---|---| | Trying it out | 10-Minute Tour โ Commands โ Robot Mode | | Building a meta-agent | Robot Mode โ Sample Mission/Tx Contracts โ Operating Envelope | | Operating at scale | Capacity Planning โ Operator Playbook โ Troubleshooting | | Understanding how it works | Architecture โ System Components โ Deep Dives | | Auditing the claims | Trust & Attestation โ Threat Model โ Formal Methods | | Reading the algorithms | Algorithm & Data Structure Catalog โ Pattern Engine โ Cx Cancellation Model |
A swarm-native terminal platform that observes, controls, and audits fleets of 200+ concurrent AI coding agents. 77 workspace crates, 19 sub-crates carved out of the core, 531 core-library modules, 1089341+ lines of Rust, 57556+ test annotations across 984 integration test files.
Counts are auto-stamped by scripts/stamp-readme-counts.sh and drift fast. See Maintainers: how counts stay honest at the bottom for the exact recipe. Developer checks use the live worktree by default; release snapshots use --source=head so unrelated dirty files cannot alter the attested counts.
Quick Install
cargo install --git https://github.com/Dicklesworthstone/frankenterm.git --bin ft frankenterm
ft --version works immediately after install. ft doctor / ft doctor --json run immediately. Pane/session operations that talk to the live mux require WezTerm CLI in PATH and a reachable mux/GUI for wezterm cli list.
TL;DR
The Problem. Running large AI coding swarms across ad-hoc terminal panes is chaos. When you're driving 50โ200 Claude Code / Codex / Gemini agents at once, a single undetected rate limit wastes hours of compute. A stuck agent silently burns tokens. An auth failure goes unnoticed for thirty minutes. You have no search across agent output, no audit trail, no way for one AI to safely control another, and no way to know whether your swarm is operating inside or outside its safe envelope.
The Solution. ft is a full terminal platform for agent swarms with deep observability, deterministic eventing, policy-gated automation, machine-native control surfaces (Robot Mode + MCP), and a fail-closed operating-envelope contract. It captures every byte of terminal output across every pane, detects state transitions via multi-pattern matching plus Bayesian change-point detection, triggers transactional workflows in response, and exposes all of it through a JSON API built for AI-to-AI orchestration. The closest analogy is Kubernetes for terminal-based AI agents: observe, detect, react, audit, and refuse to drive the swarm outside its proven safe envelope.
Runtime model. Fully Cx-aware, structured, cancel-correct async on asupersync. Direct tokio usage is banned at the dependency level via cargo-deny and at the type level via the RuntimeProof sealed trait. The runtime_async module is the canonical asupersync wrapper that every first-party crate imports. The dual-runtime era is over.
If you'd rather see commands than prose, jump straight to the 10-Minute Tour below.
10-Minute Tour
A guided walkthrough from "I cloned this" to "I have an AI driving an AI." Each step builds on the previous one.
1 ยท Install + verify (1 minute)
cargo install --git https://github.com/Dicklesworthstone/frankenterm.git --bin ft frankenterm
ft --version # smoke test โ should print version + git commit ft doctor # environment check โ exits non-zero on missing prerequisites
ft talks to a live mux through the WezTerm interop boundary, so wezterm must be in PATH. ft doctor will tell you if it isn't. If you need from-source builds or optional features (mcp, web, distributed, semantic-search, ftui), see Installation below.
2 ยท See the fleet (1 minute, no daemon yet)
ft robot state gives an AI-readable snapshot of every pane the mux can see, without starting a long-running daemon. This is the call a meta-agent makes when it wants a one-shot view. The full RobotResponse envelope wraps every response with ok, data, elapsedms, version, now, and schemaversion (the MCP transport adds mcp_version on top of that); data for the state endpoint is a bare array of pane records (truncated below for clarity):
$ ft robot state
{
"ok": true,
"data": [
{"pane_id": 0, "title": "claude-code", "domain": "local", "cwd": "/project"},
{"pane_id": 1, "title": "codex", "domain": "local", "cwd": "/project"},
{"pane_id": 2, "title": "build", "domain": "local", "cwd": "/project"}
],
"elapsed_ms": 4,
"version": "0.2.0",
"now": 1747371642000,
"schema_version": 1
}
For AI-to-AI use, add --format toon to swap the JSON encoder for the lower-token TOON serialization. Exact byte savings depend on payload shape; the CLI prints JSON-vs-TOON byte counts to stderr if you pass --stats.
3 ยท Start observing (2 minutes)
Now start the watcher so capture, pattern detection, and workflow execution kick in. Run it in the foreground for the tour:
ft watch --foreground
(leave this running; new terminal for the next steps)
In a second shell, peek at what the watcher is seeing. ft robot events returns an EventsData envelope that wraps the event list with filter + count metadata:
$ ft status # human-readable fleet overview
$ ft robot events --limit 5 # recent pattern-triggered detections
{
"ok": true,
"data": {
"events": [
{
"id": 1247,
"pane_id": 1,
"rule_id": "codex.usage.reached",
"pack_id": "builtin:core",
"event_type": "detection",
"severity": "warn",
"confidence": 0.95,
"captured_at": 1747371642000
}
],
"total_count": 1,
"limit": 5,
"unhandled_only": false
},
"elapsed_ms": 3,
"version": "0.2.0",
"now": 1747371643000,
"schema_version": 1
}
The watcher's pattern engine has already noticed pane 1 hit a rate limit; the event is queued for any registered workflow handler to react.
4 ยท React safely (2 minutes โ send + wait-for + policy gate)
Send a /compact to the stuck codex pane, but block until the recovery confirms. The default send path stays fast and only returns the policy-gated injection result plus optional wait-for data. Add --verify-submit or --submit-level <write|composer|submitted|working> when the caller needs a durable submit receipt persisted with the audit row under its idempotency_key:
$ ft robot send 1 "/compact" --verify-submit --wait-for "compaction complete" --timeout-secs 30
{
"ok": true,
"data": {
"pane_id": 1,
"injection": { / wire-level injection record / },
"wait_for": {
"pane_id": 1,
"pattern": "compaction complete",
"matched": true,
"elapsed_ms": 4823,
"polls": 96,
"is_regex": false
},
"submit": {
"state": "submitted",
"guarantee_level": "submitted",
"guarantee_met": true,
"attempts": 1,
"evidenceruleids": ["policy.allow"],
"elapsed_ms": 4829,
"polls": 96,
"idempotency_key": "rk:0123456789abcdef"
}
},
"elapsed_ms": 4829,
"version": "0.2.0",
"now": 1747371646829,
"schema_version": 1
}
Notice three things: (1) ft robot send is policy-gated โ if you try to send something that violates a policy rule, you get a structured RequireApproval envelope with an 8-char approval code, not a silent error. (2) --wait-for is condition-based, not sleep-based โ it polls until the pattern shows up or the timeout fires. (3) The response is structured JSON the calling agent can route on.
When ok=false, the error fields live at the top level of the envelope (not nested under error):
{
"ok": false,
"error": "Action requires approval",
"errorcode": "robot.requireapproval",
"hint": "Run: ft approve AB12CD34",
"elapsed_ms": 1,
"version": "0.2.0",
"now": 1747371646830,
"schema_version": 1
}
5 ยท Search the past (1 minute)
Every byte of pane output the watcher captured is FTS5-indexed. ft robot search returns a SearchData wrapping the result list:
$ ft robot search "error: compilation failed" --limit 3
{
"ok": true,
"data": {
"query": "error: compilation failed",
"results": [
{
"segment_id": 41827,
"pane_id": 1,
"seq": 5821,
"captured_at": 1747370104000,
"score": 1.2845,
"snippet": " --> services/billing/pricing.rs:142:12\n error: compilation failed: type mismatch"
}
],
"total_hits": 1,
"limit": 3,
"mode": "lexical"
},
"elapsed_ms": 7,
"version": "0.2.0",
"now": 1747371700000,
"schema_version": 1
}
Add --mode hybrid for semantic-ranked results (requires --features semantic-search build); hits then carry semanticscore and fusionrank fields alongside score.
6 ยท Orchestrate transactionally (2 minutes โ mission + tx)
For multi-pane operations that need rollback semantics, use the Tx engine:
$ ft tx plan --contract-file deploy-staging.json # validate the contract
$ ft tx run # prepare + commit, with auto-compensation on failure
$ ft tx show --include-contract # full receipt with per-step audit
For a free-text operator goal that respects the safety envelope:
$ ft mission objective-plan --objective "spawn 5 codex panes for the pricing refactor"
This invokes the capacity-aware objective planner, which asks the operating envelope whether 5 new panes are safe given current RCH pressure, fleet memory, etc. โ and returns a plan only if it fits. See Sample Mission and Tx Contracts for the JSON shapes.
7 ยท Verify the release attestation (1 minute)
Every load-bearing claim links to a signed artifact slot. The CLI re-hashes every artifact from disk, recomputes the canonical signing payload, checks the recorded .sigstore file hash + size, and verifies the Sigstore signature. Exits 0 on full pass, non-zero on any failure.
ft attestation verify docs/attestations/0.2.0.json # human-readable verdict
ft attestation verify docs/attestations/0.2.0.json --json # machine-readable verdict
ft attestation show docs/attestations/0.2.0.json # pretty-print without re-verifying
ft attestation is a thin Rust wrapper over scripts/attestation-verify.sh; third parties without the ft binary can run the script directly.
Where to go next
- Robot Mode (JSON API) โ the full call contract AI agents use to drive other AI agents
- Operating Envelope โ the fail-closed safety surface that gates new pane admission
- Commands โ every
ftsubcommand with examples - Bundled Demo Scenarios โ side-effect-free onboarding/regression fixtures with retained artifacts
- Configuration โ
ft.tomlreference for tuning poll intervals, retention, redaction tiers - Operator Playbook โ what to do when things break
ft get-text, ft search, ft robot get-text, ft robot search, and MCP wa.get_text / wa.search) are policy-evaluated and redact secret material in returned text/snippets โ you won't accidentally leak a JWT into a notification or an event payload.
Why use ft?
Now that you've seen it run, here's the full capability surface:
| Capability | What it does | |---|---| | Full Observability | Captures terminal output across panes with low-latency delta extraction; sub-50 ms lag is the benchmark-lane target (attestation)[^ft-attest-perf-headline][^ft-attest-lindley] | | Multi-Agent Detection | Aho-Corasick multi-pattern engine + Bloom prefilter + BOCPD change-point detection. Native rule packs for Codex, Claude Code, and Gemini | | Event-Driven Automation | Workflows trigger on detected patterns with per-workflow trigger-policy allowlists (ft-j0ufc), never on sleep loops | | Robot Mode API | JSON / TOON envelopes optimized for AI-to-AI control[^ft-attest-robot-contracts] | | Lexical + Hybrid Search | FTS5 + Tantivy + optional ML embeddings via FrankenSearch; lexical, semantic, and hybrid modes across captured output | | Operating Envelope Contract | ft.operating_envelope.v1 planner module decides whether new pane work is admitted based on system pressure; fails closed when telemetry is missing | | Policy Engine | 14-subsystem policy framework with per-subsystem health verdicts, capability gates, rate limiting, audit trails, and approval tokens | | Transactional Mission Orchestration | Prepare/commit/compensate lifecycle, idempotency ledger, deterministic replay, kill switches, capacity-aware objective planner | | Tiered Scrollback | Three-tier memory management (hot/warm/cold) with worst-of fleet memory controller for 200+ pane fleets[^ft-attest-perf-headline] | | Incident Bundles | Crash and swarm-incident bundles wire to live collectors (process tree, GPU state, mux state, render state, beads coordination snapshot) | | Replay & Forensics | Capture, replay, and diff decision graphs for post-incident analysis and regression testing | | Distributed Mode | Optional agent-to-aggregator streaming with per-agent dedup, versioned wire protocol, and stale-session pruning[^ft-attest-distributed-threat] | | Reality-check + Attestation | Every headline claim links to a signed artifact slot in docs/attestations/manifest.json. Verify offline with ft attestation verify |
[^ft-attest-perf-headline]: Verified via the populated perf/headline-claims attestation slot in docs/attestations/manifest.json; this covers benchmark-lane capture latency, Bloom prefilter speedup, and pane/memory capacity rows. Target-class caveats remain governed by their linked artifacts and the fail-closed swarm-capacity-envelope adjunct. [^ft-attest-lindley]: Verified via the populated perf/lindley-bounds attestation slot for the capture-path Lindley / min-plus latency model. [^ft-attest-robot-contracts]: Verified via the populated proofs/robot-contracts attestation slot for JSON/TOON control-plane envelope contracts. [^ft-attest-distributed-threat]: Verified via the populated security/distributed-threat-model attestation slot for distributed wire-protocol safety review and diff-fuzz coverage.
What "swarm-native" actually means
The phrase has a concrete meaning here:
- The minimum-useful-unit is the fleet, not the pane. Every primary subsystem (storage, search, policy, workflows, mission, tx, distributed) assumes there are dozens to hundreds of panes and was engineered for that scale from the start, rather than retrofitted from a single-pane assumption.
- Fleet pressure is a first-class signal. The operating envelope, fleet memory controller, and backpressure tiers compose pressure across the entire fleet, not per-pane in isolation. A single hot pane can throttle the rest of the fleet's poll cadence; a healthy fleet runs at full speed.
- One AI can drive another safely. Robot Mode, MCP, and the policy gate exist specifically so AI agents can control other AI agents without writing brittle text-parsing glue. The JSON envelopes are the contract.
- Coordination primitives are built in. Beads issue tracking, Agent Mail file reservations, work claim queues (
ft robot work), and tx idempotency ledgers exist because swarm work needs coordination, not just observation. - Failure isolation is per-pane, but recovery is per-fleet. A stuck pane doesn't take down the watcher. An auth failure on one agent doesn't pause the other 49. But the fleet memory controller throttles uniformly when pressure is real.
Who is this for?
- Anyone running 2+ AI coding agents in parallel and tired of writing bash glue to coordinate them
- Anyone building meta-agents (one AI driving N specialized AIs) who needs a structured control plane
- Operators of large swarms (50โ200+ panes) who need fleet-wide observability, memory bounds, and capacity admission
- Anyone who has to audit what an AI did in a terminal session after the fact
- Anyone who has lost work to a rate limit they didn't notice for 30 minutes
- Anyone who wants their multi-agent infrastructure to fail closed rather than silently degrade
Supported Surface Matrix
Honest status of every shipped surface, without migration-era hand-waving.
| Surface | Status | Notes | |---|---|---| | Watch / status / triage / doctor / reproduce | Supported | Native operator surfaces; ft doctor, ft status --health, and ft triage are the first-run and incident entrypoints | | Search / events / audit / workflows / mission / tx | Supported | Backed by local storage, policy, and workflow subsystems | | Robot mode | Supported | All core families: state, get-text, send, wait-for, search, events, rules, workflows, agents, accounts, reservations, mission, tx, health, proof status, approvals, checkpoint, context, work, fleet, profile. NTM-gap fallback retired. Caveat: the agents family is gated behind the (default-on) agent-detection feature โ a --no-default-features build returns robot.featurenotavailable for it (see the Compile-Time Feature Matrix) | | Operating envelope | Supported | ft.operating_envelope.v1 planner contract + golden fixtures; fails closed on missing or critical-pressure telemetry | | Mission objective planner | Supported | Capacity-aware planner for safe swarm orchestration (ft-auy2g) | | Incident bundles | Supported | Wired to live collectors; publish-side snapshot path; beads coordination snapshot included | | Session persistence | Supported with backend prerequisite | Snapshots, session inspection, and ft session doctor are cross-platform; live restore (ft restart, ft snapshot restore) is currently Unix-only | | Reality-check + attestation | Supported | ft attestation verify / show ship as a thin Rust wrapper over scripts/attestation-verify.sh. Signed bundles live in docs/attestations/ | | Deferred proof queue | Supported with fail-closed proof prerequisite | ft proof queue/status/replay/attach and ft robot proof status expose source-landed proof intents. Replay executes only through remote-required RCH when admission is explicitly admitted; local Cargo is never substituted. Release-slot evidence stays under docs/attestations/proofs/deferred-proof-replay.json; current W8.2 remote proof remains blocked on RCH admission. | | Web API / SSE | Supported behind --features web | /health, /panes, /events, /search, /stream/events, /stream/deltas | | Distributed mode | Supported behind --features distributed | Remote panes persist into the same DB and surface through status/search/state; live get-text for distributed panes is intentionally unavailable | | MCP server | Supported behind --features mcp | stdio + tool surface mirroring Robot Mode | | Semantic search | Supported behind --features semantic-search | fastembed-backed embeddings + FrankenSearch hybrid mode | | Browser auth tooling | Feature-gated | ft auth is real, but only in builds that include the browser feature and a usable browser stack | | GUI (FrankenTerm.app) | Supported on macOS | Native macOS bundle; live render-state plumbing, BSU/ESU sync-output, classified drag handlers, command-palette domain labels |
Design Philosophy
1. Passive-First Architecture
The observation loop (discovery, capture, pattern detection) has no side effects. It only reads and stores. The action loop (sending input, running workflows, mission/tx execution) is strictly separated with explicit policy gates. In practice, ft watch can never accidentally send input or modify agent state; it is a pure observer.
2. Event-Driven, Not Time-Based
No sleep(5) loops hoping the agent is ready. Every wait is condition-based: wait for a pattern match, wait for pane idle, wait for an external signal. Deterministic, not probabilistic. ft robot wait-for blocks until a specific pattern appears in pane output, not until a timer expires.
3. Delta Extraction Over Full Capture
Instead of repeatedly capturing entire scrollback buffers, ft uses 4 KB overlap matching to extract only new content. This produces efficient storage, minimal latency, and explicit gap markers for discontinuities. When the overlap match fails (terminal reset, scrollback clear), the gap is recorded as an explicit event rather than silently dropped.
4. Single-Writer Integrity
A filesystem lock (via fs2) ensures only one watcher can write to the database. No corruption from concurrent mutations. Graceful fallback for read-only introspection. The lock metadata records PID and start time for diagnostics.
5. Agent-First Interface
Robot Mode returns structured JSON with consistent schemas. Every response includes ok, data, error, elapsedms, and version. TOON (Token-Optimized Object Notation) is the lower-token machine format; payload shape controls savings, and the benchmark substrate lives at docs/perf-ledger/toon-encoding.md. The format is built for machines to parse, not humans to read.
6. Transactional Safety
Multi-pane operations use a prepare/commit/compensate lifecycle borrowed from distributed transaction protocols. If a commit step fails, compensation rolls back the committed steps. Kill switches and pause controls provide emergency intervention. Every transition emits an observability event with a reason code and decision path.
7. Defense in Depth for Memory
The fleet memory controller synthesizes pressure signals from three independent subsystems: pipeline backpressure (queue depths), system memory utilization, and per-pane memory budgets. These feed a unified 4-tier pressure model (Normal, Elevated, Critical, Emergency) with asymmetric hysteresis that escalates fast and de-escalates slow. During incidents, operators use the resource-pressure cockpit contract to separate rustheap, mmapfilebacked, sqlitepagecache, graphicsmedia, scrollbackcache, child_processes, and unknown resident memory before calling anything a leak.
8. Fail Closed on Missing Telemetry
The operating envelope, network-pressure selector, process-snapshot pipeline, and RCH critical-pressure gate all refuse to advance when their measurement source is absent or unhealthy. A swarm controller that doesn't know what state the system is in must not pretend to know. This is enforced at the call sites of every component that consumes pressure or health telemetry.
9. Layered via Extraction
Layering is enforced through sub-crate extraction, not just discipline. frankenterm-core has 19 sibling sub-crates carved out. Leaf type crates declare zero first-party dependencies; cluster sub-crates depend on frankenterm-core only; there are zero core โ sub-crate edges. Cycles can't sneak in because the build refuses to compile them.
10. Reality-Check Discipline
Every headline claim links to a signed attestation slot. The quarterly /reality-check-for-project discipline produces a bridge plan, a substrate of proof slots, and a per-release bundle. The current round (ft-tf6g3, opened 2026-05-12) is closing the final-mile gaps: attestation graph completeness, renderer SLO suite, round-3 statistical elevations (Lindley, Fano, SPRT, conformal bands, Mazurkiewicz cancel-traces, TLA+ TX-killswitch, Stateright work-family atomicity). See docs/reality-check-bridge-plan.md.
Glossary of Terms
Terminology used throughout this document and the codebase. Reading these once saves grepping later.
| Term | Meaning | |---|---| | Mux | The terminal multiplexer process. ft talks to the WezTerm/FrankenTerm mux. | | Domain | A backend that hosts panes โ local, SSH:<host>, tmux:<socket>, or a custom domain. Each pane has one. | | Window / Tab / Pane | Mux topology unit, in increasing granularity. A window holds tabs; a tab holds panes. | | Session | An ft concept: a coherent run of the watcher with persistent identity. Sessions outlive individual mux restarts. | | Snapshot | A point-in-time capture of the mux topology + per-pane state, persisted to the DB. | | Checkpoint | A persisted session-progress marker; checkpoints accumulate within a session. | | Backup | A portable archive of the entire ft database, separate from snapshots. | | Capture | The act of reading current scrollback from a pane and persisting any new bytes. | | Delta | The new bytes a capture produced after the 4 KB overlap match. | | Gap | Captured discontinuity (the previous tail didn't match anywhere in the new capture). Recorded as an event. | | Rule | A pattern with stable ID, anchor strings, regex, severity, and agent type. | | Rule pack | A versioned collection of rules; the default is builtin:core. | | Detection | A rule firing on captured text; emitted as an event with the originating paneid and ruleid. | | Event | A typed message on the event bus โ detections, gaps, lifecycle changes, system events. | | Workflow | A registered handler that runs in response to detected events (or on demand). | | Mission | A planned multi-pane operation with a contract, lifecycle state machine, and audit trail. | | Tx (transaction) | The execution engine for missions; prepare โ commit โ compensate with idempotency receipts. | | Operating envelope | The fail-closed admission contract that decides whether new pane/workflow work is safe right now. | | Profile | A reusable pane specification (agent + cwd + env + session settings). | | Persona | Behavioral defaults a profile inherits (skill mix, prompt preludes, tool palette). | | Fleet template | A composition of profiles with counts and dependencies. | | Reservation | An advisory lease on a pane (or file path) preventing concurrent edits. | | Approval token | A one-shot, scope-pinned 8-char code that lets a specific action through the policy gate. | | Robot Mode | The JSON / TOON API surface AI agents use to drive ft. | | TOON | Token-Optimized Object Notation โ a compact tree serialization for AI-to-AI envelopes. | | Cx | asupersync's cancellation context; propagates cancel signals through structured concurrency. | | RuntimeProof | A sealed trait that makes tokio::* types refuse to compile in bounded API surfaces. | | Aggregator | In distributed mode, the host running ft watch with the listener; it receives streams from ft distributed agent peers. | | Cockpit | The resource-pressure cockpit contract; classifies resident memory before any "leak" claim. | | Reality-check | The quarterly discipline that produces a bridge plan + substrate + signed per-release attestation bundle. | | Bead | A unit of tracked work in beads_rust (br); IDs prefix ft-* for this project. | | PDU | Protocol Data Unit โ the codec layer's wire message envelope between mux client and mux server. |
Safety Guarantees
- Observe vs act split:
ft watchis read-only; mutating actions must pass the Policy Engine. - No silent gaps: capture gaps are recorded explicitly and surfaced in events/diagnostics.
- Policy-gated sending:
ft sendand workflows enforce prompt/alt-screen checks, rate limits, and approvals. - Policy-gated reads:
get-text/searchsurfaces enforce policy checks and return redacted text payloads. - Transactional operations:
ft tx runuses prepare/commit/compensate phases with idempotency guards and deterministic replay. - Approval tokens: allow-once approval codes scoped to specific action + pane + fingerprint combinations.
- Secret redaction: captured output is redacted before being returned through any API surface, with configurable sensitivity tiers (T1/T2/T3) and retention policies. Token redactor coverage was expanded in 2026-05 to include JWT, GitLab, Twilio, SendGrid, and Datadog patterns.
- Workflow trigger-policy allowlists (ft-j0ufc): high-trust workflows declare allowlisted source panes to prevent low-trust panes from triggering them via output injection.
- Public-field bypass class eliminated: ~6 audit findings closed where
pubstruct fields let callers bypass clamping or validation. Constructors/builders now own the invariants forErasureShard,CircuitBreaker::Config,QuantileBudgetMs,ArrivalCurve,ServiceCurve,ApprovalScope/AuditContext,ScaleFactor, andAxisValue. - Rubber-stamp
issafe()class eliminated: ~17 audit findings closed whereissafe()returnedtrueon cold start or before measurements were recorded. Every release-gateis_safenow requires evidence before reporting safe. - Release attestation bundles: every reality-check claim is published through a content-addressed, Sigstore-signed JSON bundle in
docs/attestations/. Verify any release offline withft attestation verify docs/attestations/<version>.json.
Trust & Attestation
Latest weekly reality-check drumbeat: docs/reports/reality-check-2026-05-02.md (2026-05-02). Round-2 epic (ft-tf6g3) opened 2026-05-12 โ next drumbeat publishes when the next pass closes._
The canonical manifest is docs/attestations/manifest.json; each slot maps a claim category to its producing-bead artifact, and the per-release bundle signs those slot hashes.
The README claim-to-slot map is intentionally limited to populated manifest slots:
| README claim | Manifest slot | Producing bead | |---|---|---| | Capture latency benchmark lane | perf/headline-claims | ft-syqcz.3 | | Capture-path Lindley / min-plus bound | perf/lindley-bounds | ft-43x69 | | Bloom prefilter search speedup | perf/headline-claims | ft-syqcz.3 | | 200-pane capacity and memory-budget benchmark lane | perf/headline-claims | ft-syqcz.3 | | Robot JSON/TOON envelope contract | proofs/robot-contracts | ft-0elb9 | | Operating-envelope read-only admission contract no-verdict artifact | proofs/robot-contracts | ft-booek.7 | | Redactor coverage matrix | security/redactor-coverage | ft-x0666.2 | | Distributed wire-protocol safety | security/distributed-threat-model | ft-x0666.3 | | runtimeasync Loom model | proofs/loom-runtime-async | ft-e87u6.12 | | RuntimeProof sealed-trait guard | proofs/runtime-proof-trait | ft-i2eni.1 | | Transaction kill-switch proof | proofs/tx-killswitch | ft-tf6g3.12 |
The operating-envelope contract (ft.operating_envelope.v1) is wired under the proofs/robot-contracts manifest category as a retained read-only fail-closed admission artifact. Its current artifact status is the source of truth; a blockedrchno_verdict status is not production proof. The slot also does not prove target-class production capacity while the target-class resource-cockpit artifact remains skippednotproven. The renderer SLO suite is still tracked under ft-tf6g3 for inclusion in a future bundle. The optional proofs/rehearsal-score slot hashes the rehearsal-score golden matrix so blocked, skipped, degraded, missing-evidence, and fixture-only score rows stay visible; it is not a production support claim unless the cited receipt criterion has proven evidence.
Verify a release attestation bundle in one command, offline, without trusting GitHub or any registry:
ft attestation verify docs/attestations/0.2.0.json
The CLI re-derives every artifact's SHA-256 from disk, recomputes the canonical signing payload, checks the recorded .sigstore file hash and size, and verifies the Sigstore signature. Exits 0 on full pass; non-zero on any failure. For machine-readable output:
ft attestation verify docs/attestations/0.2.0.json --json
ft attestation show docs/attestations/0.2.0.json
Use --strict-required on verify to fail when the bundle's required_categories list does not match the canonical manifest. Release CI runs the shell verifier with --strict-deferred so tagged releases cannot ship intentionally deferred slots.
The ft attestation family is a thin Rust wrapper over scripts/attestation-verify.sh; third parties without an ft binary can run the script directly with the same arguments. For Sigstore signing identity, Fulcio/Rekor trust-root details, and manual cosign verify-blob commands, see docs/attestations/SIGNING.md. For the per-release closure procedure (when to run, how to file regressions on hash mismatch), see docs/release/attestation-checklist.md.
How ft Compares
| Feature | ft | WezTerm | Zellij | Ghostty | |---|---|---|---|---| | Swarm-native orchestration | First-class (200+ panes) | External glue required | External glue required | External glue required | | Event-driven automation | Built-in workflows + policy gate | Not native | Not native | Not native | | Machine API for agents | Robot Mode + MCP + TOON | None | None | None | | Operating-envelope safety | Native fail-closed contract | None | None | None | | Cross-session state + recovery | Built-in snapshots + sessions | Partial / manual | Session-centric, not swarm-centric | Minimal | | Agent-safe control plane | 14-subsystem policy diagnostics surface | Not native | Not native | Not native | | Transactional multi-pane ops | Prepare/commit/compensate + idempotency ledger | None | None | None | | Full-text search over output | FTS5 + Tantivy + hybrid modes | None | None | None | | Memory management at scale | Three-tier scrollback + fleet controller | Single tier | Single tier | Single tier | | Replay and forensics | Decision graph + diff + provenance | None | None | None | | Reality-check attestation bundles | Sigstore-signed slot manifest | None | None | None | | Incident bundles | Live collectors + publish-side snapshot | None | None | None | | Async runtime guarantees | asupersync, Cx-first, cancel-correct, tokio banned | tokio (no Cx model) | smol (no Cx model) | none of these guarantees | | Unsafe code | #![forbid(unsafe_code)] workspace-wide | unsafe present | unsafe present | unsafe present |
When to use ft:
- Running 2+ AI coding agents that need coordination
- Building automation that reacts to terminal output
- Debugging multi-agent workflows with full observability
- Operating large agent swarms (50โ200+ panes) with memory and backpressure control
- Anywhere a swarm controller needs attestable safety bounds
- Single-shell / single-agent usage where orchestration is unnecessary
- Environments that only need a lightweight interactive terminal and no swarm control plane
- Use cases that require a non-WezTerm mux backend (by design,
ftis a wezterm-fork)
Real-World Scenarios
These are the workloads ft was built for. Each scenario describes the operator pain without ft, then how the platform addresses it.
Scenario 1 โ Detect and recover from rate limits across a 50-pane swarm
Without ft: You're driving 50 Codex/Claude Code panes in parallel. One agent hits its usage limit and silently stops making progress. You don't notice for 30 minutes. The other 49 agents continue burning tokens until they hit their limits.
With ft: the native rule packs detect every form of "usage reached" / "rate limit" output across all three CLIs without operator config. The matched event includes the originating paneid and the ruleid. Two approaches:
# A) Let the built-in workflow handle it (preferred)
ft watch --foreground --auto-handle # registers handleusagelimits, handle_compaction, โฆ
B) Poll the unhandled-events queue and react in shell
while sleep 5; do
ft robot --format json events --unhandled --limit 50 | \
jq -c '.data[]' | while read -r event; do
pane=$(echo "$event" | jq -r .pane_id)
rule=$(echo "$event" | jq -r .rule_id)
case "$rule" in
codex.usage.reached) ft robot send "$pane" "/compact" ;;
claude_code.usage.reached) ft robot send "$pane" "/clear" ;;
gemini.usage.reached) ft robot send "$pane" "/reset" ;;
esac
done
done
C) Block on a single pane until a specific rule fires (good for tight feedback loops)
ft robot wait-for 7 "codex.usage.reached" --timeout-secs 3600 && \
ft robot send 7 "/compact"
ft robot wait-for takes a single pane_id and a substring (or regex with --regex). For fleet-wide reactions, poll ft robot events --unhandled or use --auto-handle.
Scenario 2 โ Coordinate a multi-pane mission with safe rollback
Without ft: You need to run a 5-step refactor across 4 panes (e.g., "pane 1 rewrites tests, panes 2โ4 update implementations in parallel, then pane 1 verifies"). You wire this with bash, shell sleeps, and prayer. When step 3 fails halfway, you have no way to roll back the partial work.
With ft:
ft mission plan --mission-file refactor.json # validate the contract ft mission status # see the dispatch summary ft tx run --contract-file refactor-tx.json # prepare + commit deterministically if a step fails mid-commit, ft tx automatically runs compensation
ft tx show --include-contract # see the receipt + per-step audit
The tx engine uses prepare/commit/compensate phases with an idempotency ledger. A mid-flight crash can be safely resumed; a mid-flight failure runs compensation automatically.
Scenario 3 โ Reconstruct what an agent did six hours ago
Without ft: A coding agent did something destructive at 02:14 AM and you find out at 08:00. The terminal scrollback rolled. The pane process exited. You have no record.
With ft: every byte of pane output is delta-extracted and stored in SQLite with FTS5 indexing. The audit trail records every action that went through the Policy Engine, including denials, approvals, and rate-limited blocks.
# Search captured output for the timeframe (--since takes epoch ms)
SIXHOURSAGO_MS=$(( $(date +%s) 1000 - 6 3600 * 1000 ))
ft search "the suspicious string" --pane 7 --since $SIXHOURSAGO_MS
ft history accepts human-readable since strings
ft history --pane 7 --since "6 hours ago"
ft history --pane 7 --since "2026-05-16T02:00:00" --until "2026-05-16T03:00:00"
Check the audit trail for actions ft itself took (--since takes epoch ms)
ft audit --pane 7 --since $SIXHOURSAGO_MS
Pull a long tail of the pane's current buffer (audit history covers the rest)
ft robot get-text 7 --tail 5000
Scenario 4 โ Stand up a fresh swarm host with a known-safe envelope
Without ft: You provision a new VPS, install your CLI tools, kick off 100 agents, and the box OOMs at 87. You have no model of what "safe" capacity is for this hardware.
With ft: The operating-envelope planner reads RCH cluster pressure, network pressure, process snapshots, fleet memory tier, and SQLite write-queue depth. When the planner admits N panes, you know the platform has agreed that N is safe. If you ask for more than the envelope allows, the planner returns envelope.shed with capacity.red / capacity.black reason codes citing the responsible input.
ft mission objective-plan --objective "spawn 100 codex panes" --strictness strict
returns a plan that's been validated against the envelope, OR
a deny verdict naming the limiting input
Scenario 5 โ Drive one AI by another, safely
Without ft: You want Claude Code (the meta-agent) to control 10 specialist Codex panes. You build glue with tmux send-keys, regex parsing of scrollback, and brittle sleep loops. Three weeks later you have a 2000-line Python script with seven race conditions.
With ft: The meta-agent runs ft robot --format toon state for fleet snapshots, ft robot wait-for for condition-based blocking, ft robot send for input (with policy gates), and ft robot search for cross-pane retrieval. Every call returns a structured envelope. The meta-agent never sees raw scrollback unless it explicitly asks.
# Snapshot every pane the mux can see (use TOON to save tokens)
ft robot --format toon state
Wait for a specific condition on a single pane
ft robot wait-for 7 "Press Enter to continue" --timeout-secs 600
ft robot send 7 "" # respond
Pull unhandled events across the fleet, react per rule_id
ft robot --format json events --unhandled --limit 50 | \
jq -c '.data[]' | while read -r e; do
rule=$(echo "$e" | jq -r .ruleid); pane=$(echo "$e" | jq -r .paneid)
case "$rule" in
*.usage.reached) ft robot send "$pane" "/compact" ;;
*.approvalneeded) ft approve "$(echo "$e" | jq -r .approvalcode)" ;;
esac
done
Scenario 6 โ Investigate a crash without losing context
Without ft: A pane crashes. You have a core file maybe, scrollback definitely gone, no way to correlate with the other panes' state at the moment of failure.
With ft:
ft reproduce --kind crash --output /tmp/incident Bundle includes: process tree, GPU state, mux topology, render state,
BSU/ESU drain telemetry, SQLite WAL state, recent events, audit tail,
beads coordination snapshot
ft proof-doctor /tmp/incident
Live collectors snapshot the world at the moment of failure. The bundle is portable; you can attach it to a bug report and a reviewer can reconstruct the incident without the original host.
Why a WezTerm Fork
ft is a wezterm-fork mux runtime, not an abstraction layer over arbitrary terminal multiplexers. This is a deliberate architectural decision; understanding the rationale clarifies the project's scope.
The decision
The vendored frankenterm/<crate>/ workspace members are first-class. There are currently 42 top-level vendored crate directories and 47 Cargo workspace members when nested derive/lua crates are included. No "implementation boundary" exists between ft and "the mux"; the in-process mux session API is the MuxInterface trait, and the audit under docs/proposals/ft-zoxxq-mux-boundary-truth.md (7,803 LOC examined, 31 importers, 192 concrete-type refs, 0 trait-object consumers) confirmed that nobody actually treats the boundary as polymorphic.
Why not abstract over both WezTerm and (say) Zellij?
- Mux semantics aren't interchangeable. WezTerm's pane lifecycle, scrollback model, alt-screen handling, and PDU schema are concretely different from Zellij's or tmux's. Pretending otherwise produces an abstraction that's a worst-of-all-worlds compromise.
- The asupersync runtime contract is tighter than upstream WezTerm assumes.
ftowns its own async story (Cx-first, cancel-correct, structured concurrency). Reusing the vendored crates means we can (and do) patch them when their runtime assumptions don't fit ours. - Test/proof surfaces are easier on owned code. RuntimeProof, the
asupersync_test!macro, the Loom model, and the cargo-deny tokio ban only work because we control the entire dependency graph. - Bundle identity matters. FrankenTerm.app is a distinct macOS bundle (separate bundle ID, separate icon, separate update channel). Side-by-side install with upstream WezTerm is intentional.
Weekly upstream backport workflow
We never blindly merge upstream WezTerm. Upstream is treated as a read-only patch source:
- Find the upstream baseline from
frankenterm/PROVENANCE.json(divergence_point.subjectrecords the imported WezTerm commit). - Fetch upstream into a read-only tracking ref.
- Build an inventory of upstream commits since the baseline, grouped by subsystem:
term,termwiz,window,config,mux,pty,font,ssh,codec, GUI, mux-server. - Prioritize security, crash, data-loss, terminal-correctness, macOS/windowing, PTY, SSH, and font fixes before cosmetic changes.
- For each accepted upstream commit, port the smallest coherent slice manually into the renamed FrankenTerm paths (no regex-based code rewrites). Include the upstream SHA in the commit body as
Upstream-WezTerm: <sha>. - Validate package-scoped first (e.g.,
cargo check -p mux --lib), then broader workspace checks. - Update provenance/backport notes at the end of each batch.
Installation
Via install script (curl | bash)
The one-liner installs a prebuilt ft binary (no Rust toolchain required) and, on Apple-Silicon macOS, the FrankenTerm.app GUI bundle:
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/frankenterm/main/install.sh | bash
# Force a fresh fetch past any CDN cache:
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/frankenterm/main/install.sh?$(date +%s)" | bash
What it does:
- Downloads the release asset for your platform, verifies its SHA-256 checksum (and Sigstore signature when published), and installs the
ftCLI to~/.local/bin(override with--dest DIR, or--systemfor/usr/local/bin). - On macOS arm64, it also installs FrankenTerm.app (the GUI terminal emulator) to
/Applications(or~/Applicationswhen/Applicationsisn't writable), registers it with LaunchServices, and refreshes the Dock so an existing Dock pin resolves to the new version. It does not add a new Dock tile. Skip it with--no-app, force it with--with-app, relocate it with--app-dest DIR. - Falls back to a from-source build when no prebuilt asset matches your platform (Intel Mac, uncommon targets).
--version vX.Y.Z | Install a specific release (default: latest) | | --dest DIR / --system | CLI install location (~/.local/bin default; /usr/local/bin with sudo) | | --easy-mode | Append ~/.local/bin to PATH in your shell rc files | | --with-font | Also install the bundled Pragmasevka Nerd Font | | --no-app / --with-app / --app-dest DIR | macOS GUI-app control (skip / force / relocate) | | --from-source | Build from source instead of downloading (needs Rust + git) | | --offline TARBALL | Install from a local tarball; no network | | --no-verify | Skip checksum/signature verification (testing only) | | --verify | Run ft doctor after install |
The bundled GUI app is ad-hoc signed (not Developer-ID notarized). A curl/terminal-placed bundle isn't Gatekeeper-quarantined, so it launches normally; if you instead fetch the .app asset through a browser, clear the quarantine flag with xattr -dr com.apple.quarantine /Applications/FrankenTerm.app before first launch.
Via Cargo (fastest)
cargo install --git https://github.com/Dicklesworthstone/frankenterm.git --bin ft frankenterm
Post-install expectations:
ft --versionsucceeds immediatelyft doctor/ft doctor --jsonemit diagnostics immediately- On a clean host, doctor reports backend-prerequisite errors until WezTerm is available (
wezterm --versionandwezterm cli list --format jsonmust succeed) .ft, logs, and the SQLite database are created on first daemon/watch startup
From source
git clone https://github.com/Dicklesworthstone/frankenterm.git
cd frankenterm
cargo build --release
cp target/release/ft ~/.local/bin/
With optional features
# MCP server support
cargo build -p frankenterm --release --features mcp
Web API with SSE event/delta streaming
cargo build -p frankenterm --release --features web
Distributed mode (agent streaming)
cargo build -p frankenterm --release --features distributed
Semantic search (ML embeddings)
cargo build -p frankenterm --release --features semantic-search
TUI dashboard (FrankenTUI backend)
cargo build -p frankenterm --release --features ftui
Legacy ratatui parity oracle for development/regression checks
cargo build -p frankenterm --features tui-oracle
Everything
cargo build -p frankenterm --release --all-features
Requirements
- Rust nightly (Rust 2024 edition; see
rust-toolchain.toml) - WezTerm CLI + reachable mux/GUI for pane discovery, live read/write, and snapshot restore
- SQLite (bundled via
rusqliteโ no system dependency)
Setup & First-Run Checklist
For setting up a new host (font, daemon, persistent config). For learning the tool, see the 10-Minute Tour above.
1. Run setup (recommended)
ft setup
ft setup font --apply # Pragmasevka Nerd Font (auto-installs on normal usage)
FTSKIPBUNDLEDFONTINSTALL=1 ft status # opt out of automatic font install
2. Verify first-run health
ft doctor
ft status --health
wezterm cli list
3. Start the watcher
ft watch # daemonized
ft -v watch --foreground # foreground for debugging
4. Check status
ft status # human-readable
ft robot state # JSON
5. Search captured output
ft search "error" # full-text search (alias: ft query)
ft events # recent detections
ft events annotate 123 --note "Investigating"
ft events triage 123 --state investigating
ft events label 123 --add urgent
ft robot search "compilation failed" --limit 20
6. React to events
ft robot wait-for 0 "codex.usage.reached"
ft robot send 0 "/compact"
7. Stream live updates (web feature)
ft web # start local server
curl -N http://127.0.0.1:8000/stream/events
curl -N "http://127.0.0.1:8000/stream/events?channel=detections&paneid=7&maxhz=25"
curl -N "http://127.0.0.1:8000/stream/deltas?paneid=7&maxhz=50"
/stream/eventsstreams liveEventBustraffic astext/event-stream./stream/deltasstreams redacted pane output deltas and gap markers from storage.maxhzbounds fan-out rate;paneidnarrows to one pane;channelacceptsall,deltas,detections, orsignals.
Commands
Watcher management
ft watch # start watcher in background
ft watch --foreground # run in foreground
ft watch --auto-handle # enable auto workflows
ft stop # stop running watcher
Pane inspection
ft status # overview of observed panes
ft status --health # health view with operator next steps
ft show <pane_id> # detailed pane info for a live pane
ft get-text <pane_id> # recent output from pane
Pane actions
ft send <pane_id> "<text>" # send input (policy-gated)
ft send <pane_id> "<text>" --dry-run # preview without executing
ft send <pane_id> "<text>" --wait-for "ok" # verify via wait-for
ft send <pane_id> "<text>" --no-paste --no-newline
Search
ft search "<query>" # full-text search
ft search "<query>" --pane 0
ft search "<query>" --limit 50
Explainability
ft why --list # list explanation templates
ft why deny.alt_screen # explain a common policy denial
Workflows
ft workflow list
ft workflow run handleusagelimits --pane 0
ft workflow run handleusagelimits --pane 0 --dry-run
ft workflow status <execution_id> -v
Security model: source-pane trust scope (ft-j0ufc). Workflows fire when a detection pattern matches a pane's output. By default any pane may fire any workflow. That default is convenient when every pane is operated by the same trust principal and dangerous when a low-trust pane shares a runtime with a high-trust pane. Workflows that act on high-trust panes should declare an allowlist via Workflow::trigger_policy():
fn trigger_policy(&self) -> WorkflowTriggerPolicy {
WorkflowTriggerPolicy::allowlist([trustedpaneid])
}
The runner enforces the allowlist before any lock, audit row, or engine state is created; refused triggers surface as WorkflowStartResult::SourcePaneNotTrusted and the originating sourcepaneid is recorded on the workflow's persisted trigger context for post-incident forensics. The default policy (allow_all()) preserves pre-ft-j0ufc behavior.
Mission & Tx control
ft mission plan # validate mission contract + compute hash
ft mission run # advance lifecycle into execution
ft mission status # lifecycle + assignment summary
ft mission explain # legal lifecycle transitions
ft mission pause / resume / abort # canonical lifecycle transitions
ft mission objective-plan --objective "<text>" # capacity-aware objective planner (ft-auy2g)
ft tx plan # validate tx contract + summarize lifecycle
ft tx run # prepare + commit, deterministically
ft tx run --fail-step tx-step:commit
ft tx rollback # compensation for committed steps
ft tx show --include-contract # receipts and full tx payload
The mission objective planner reads the requested objective, queries the operating-envelope planner for the current admission verdict, builds a plan that fits inside the envelope, and emits a deterministic plan artifact. Planning is side-effect-free; execution is a separate step that re-validates the plan against the live envelope. See Operating Envelope below.
Rules
ft rules list # list detection rules
ft rules test "Usage limit reached" # test text against rules
ft rules show codex.usage_reached # show rule details
ft robot rules lint --fixtures --strict # validate the pack (naming + regex safety + fixture coverage)
Audit & approvals
ft approve AB12CD34 --dry-run # check approval status
ft audit --limit 50 --pane 3 # filter audit history
ft audit --decision deny # only denied decisions
Diagnostics
ft triage # summarize issues (health/crashes/events)
ft diag bundle --output /tmp/ft-diag # collect diagnostic bundle
ft reproduce --kind crash # export latest crash bundle
ft doctor # environment health check
ft doctor --json # machine-readable diagnostics
ft proof-doctor # validate evidence artifacts in docs/attestations/
Attestation
ft attestation verify docs/attestations/0.2.0.json
ft attestation verify docs/attestations/0.2.0.json --json
ft attestation verify docs/attestations/0.2.0.json --strict-required
ft attestation show docs/attestations/0.2.0.json
Bundled demo scenarios
ft demo is the side-effect-free onboarding and regression surface for the bundled demo-lab fixtures. It validates the manifest-backed scenarios (quickstart, usage_limit, and compaction) and reports retained artifact links and follow
README truncated. View on GitHub