# ServerFS v0.22.0 Development Plan Status: **IMPLEMENTED / PHASE F ACCEPTED / PHASE G ACCEPTED / RELEASED.** v0.22.0 was the stable release produced by this plan and is now superseded by stable v0.22.1. This document began as the frozen v0.22.0 design and acceptance contract and now also records implementation/acceptance progress without rewriting its original design decisions. Planning date: 2026-10-22. Acceptance update: 2026-10-23. Current acceptance state: - Linux/VPS Phase F: **PASS**. The latest root suite recorded 1,735 passed and 317 skipped; root Ruff and format checks passed. Agent Bridge Ruff/format and pytest passed (459 passed, 15 skipped). Live diagnostics, MCP compatibility, Compose and scratch-image gates passed; Phase E performance evidence remains in its report. - Supported macOS Phase F: **PASS**. The accepted full run recorded 1,540 passed and 394 skipped in root, and 428 passed and 46 skipped in Agent Bridge. The later isolated cross-platform test-only checks passed Ruff/format and six affected modules (86 passed, 48 skipped); live AF_UNIX diagnostics snapshot/task acceptance passed. - Supported Windows Phase F: **PASS**. Final Windows evidence includes root non-D9 pytest (`1,754 passed, 220 skipped`), root Ruff/format, Agent Bridge pytest (`600 passed, 52 skipped`) plus Bridge Ruff/format, and isolated D9 native lifecycle pytest (`30 passed`) after a zero-active-task proof and temporary release of the production Tunnel mutex. The installed current-user service was restored to enabled/running state. - Phase G: **PASS**. Release-facing README/operator/maintainer/site alignment, final stale-current-state and diff checks, and the exact-copy static site build are complete. v0.22.0 was released as stable and has since been superseded by v0.22.1. ## 0. Decision summary v0.22.0 is **Correlated Agent Diagnostics**. The release does **not** add OpenTelemetry, an exporter, a dashboard, a trace store, a network listener, or another MCP capability. Its purpose is narrower: ServerFS must be able to explain one Agent task as a bounded causal sequence inside the Agent Bridge, while preserving the v0.21 observation/metric semantics and every existing authority boundary. The governing design principle is: > **v0.22 defines how ServerFS explains one Agent execution; v0.23 may later define how that explanation is projected into OpenTelemetry.** Frozen decisions: 1. `DiagnosticTraceContext` is generated by the **Agent Bridge itself** when a new task submission is accepted for diagnostic staging. It is not derived from the public `correlation_id`, an inbound W3C `traceparent`, the MCP SDK span, a task ID, a provider session ID, or any caller-controlled value. 2. v0.22 RPC carries **no diagnostic identity from MCP Core to the Bridge**. There is no Core-side diagnostic carrier in this release. If a future version introduces Core-owned diagnostic phases, any private carrier must still be stripped before persistence and public serialization. 3. The Bridge is the sole owner of diagnostic trace state. Diagnostics are process-local, memory-only, bounded, non-blocking, fail-open relative to the task path, and reset on Bridge restart. 4. The v0.22 operation catalog is intentionally small and Agent-focused. It uses typed, allowlisted fields only; there is no free-form `attributes`, `metadata`, `extra`, payload, exception-text, prompt, path, workdir, model-ID, session-ID, or provider-output field. 5. Diagnostic recording and existing v0.21 metrics are emitted from the **same authoritative state-transition seams** and reuse the same bounded outcome/reason/runtime-family vocabularies. Diagnostics do not derive metrics from a second event stream, and metrics do not reconstruct diagnostics after the fact. 6. Local audit, local diagnostics, and future exportable operational telemetry are three distinct trust domains. A boolean export setting can never convert audit records into exportable telemetry. 7. There are two operator surfaces with different purposes: `serverfs diagnostics snapshot` answers “what is the Bridge diagnostic subsystem doing now?” without task identity; `serverfs diagnostics task ` answers “where did this retained task spend time?” using the task ID only as a local lookup key. 8. JSON is the diagnostic contract. Human text is only a rendering of that JSON. Every diagnostic JSON result includes `catalog_version`. 9. No new endpoint is opened. New diagnostic RPC methods use the existing `AgentBridgeClient`: AF_UNIX on Linux/macOS and the existing current-user SID-verified Named Pipe path on Windows. 10. v0.23 OTLP/OpenTelemetry topology is only a future decision pre-declaration in this plan. It adds no v0.22 dependency, configuration, runtime path, listener, egress, or delivery requirement. ## 1. Goal and completion outcomes At v0.22 completion, a local operator must be able to answer, without an exporter or external backend: 1. whether the diagnostic recorder is healthy, bounded, retaining task traces, dropping/truncating evidence, or has reset since Bridge restart; 2. for one retained task, which measured Agent phases occurred, in what order, how long each phase lasted, what terminal outcome/reason was sealed, and how much elapsed time remains unaccounted for; 3. whether human/external waiting is separated from active Agent execution rather than being misreported as ServerFS execution latency; 4. whether writer-lease acquisition outcomes are represented truthfully (`busy` is a rejection/contention outcome, not lease wait latency); 5. whether a task trace is complete, prefix-truncated, restarted, restarted-and-truncated, no longer retained, unknown to the durable task store, or unreachable because the Bridge is unavailable; 6. whether the terminal diagnostic outcome agrees with the existing metric/task terminal outcome whenever a diagnostic slot was admitted; 7. whether all diagnostic evidence stays out of SQLite, public MCP schemas, provider prompts/results, model/session identifiers, workdir/path identity, and future exportable telemetry by construction. The release is complete only when the shared Bridge implementation, private RPC/CLI surfaces, deterministic tests, fault-injection tests, performance evidence, Linux/macOS/Windows acceptance evidence, and current documentation agree with this contract. ## 2. Non-goals v0.22.0 does **not**: - add OpenTelemetry API/SDK/exporter packages, OTLP, Prometheus, Grafana, Jaeger, Tempo, Loki or any hosted telemetry integration; - inherit or parse caller-supplied `traceparent` for ServerFS diagnostics; - create a ServerFS child span under the MCP SDK `tools/call` span; - add a Core-to-Bridge diagnostic carrier or expose a diagnostic identity in `task.submit` parameters; - add a public MCP tool/resource/prompt for diagnostics; - add HTTP, Streamable HTTP, TCP, WebSocket or any new diagnostic listener; - persist diagnostic traces, phases, trace/span IDs or diagnostic buffer metadata to SQLite, task events, result spools, manifests or files; - reconstruct a trace from durable Agent events after restart; - propagate diagnostics into Codex, Claude, Qoder, ACP, provider environment/argv, prompts, tool arguments, workdir files or provider results; - change v0.21 metric names, label domains, reset semantics, task-duration semantics or accepted metric values merely to fit the diagnostic model; - make diagnostics operator-configurable or disable-able in v0.22; diagnostics are always present but strictly bounded and fail-open; - turn audit records containing workdir/path/task/session context into exportable telemetry; - add a telemetry database, trace store, dashboard, alert engine or background collector; - change Agent task authority, leases, recovery guards, cancellation, provider policy, task retention, durable event retention or platform service lifecycle. ## 3. Baseline and repository boundaries The stable baseline is v0.21.0. Relevant existing surfaces are: | Boundary | Current source of truth | v0.22 rule | |---|---|---| | Shared observation vocabulary/catalog | `src/serverfs_mcp/observability.py` | Preserve v0.21 public/current semantics; do not make root package a Bridge dependency | | Bridge process-local metrics | `agent_bridge/src/serverfs_agent_bridge/observability.py` | Reuse its bounded value domains and authoritative emission seams | | Terminal transition | `BridgeService._transition_terminal()` | Terminal metric + diagnostic seal are emitted from this same authority seam | | Lease observation | `BridgeService.submit_task()` around `LeaseManager.acquire_exclusive()` | Preserve the existing metric; add diagnostic evidence without changing lease behavior | | Human interaction | `BridgeService._wait_for_request()` | Delimit `waiting_external` and split active execution segments | | Runtime execution | `BridgeService._run_task()` | Record bounded runtime-start/execution/finalization phases without changing provider behavior | | Recovery/guard decisions | Bridge startup/finalization reconciliation paths | Recovery may form an independent diagnostic trace after restart | | Durable task/event state | `agent_bridge/.../store.py`, models and result spool | Diagnostics never become durable state | | Private Bridge RPC | `agent_bridge/.../protocol.py` + root `AgentBridgeClient` | Add narrow read-only diagnostic RPC methods on the same endpoint | | Operator CLI | `src/serverfs_mcp/cli.py` | Add local-only `diagnostics` commands; no MCP registration | | Audit logging | root/Bridge structured logs | Audit remains distinct from diagnostic/exportable schemas | Before implementation, Phase A must re-read these exact seams and the accepted v0.21 evidence. If current code has changed, implementation follows current executable truth while preserving the frozen outcomes in this plan. ## 4. Diagnostic identity and ownership ### 4.1 `DiagnosticTraceContext` The Bridge defines an internal typed `DiagnosticTraceContext`. It uses cryptographically random opaque identifiers generated locally with the Python standard library. The type may use trace/span-shaped fixed-size opaque IDs so a future adapter can map them, but it must not import or expose OpenTelemetry types. Hard identity rules: - generated by the Bridge, never by the MCP client/Core/provider; - not derived from `correlation_id`, `task_id`, idempotency key, model ID, provider session/turn ID or inbound W3C context; - never accepted as RPC input in v0.22; - never stored in `TaskRecord`, SQLite, `BridgeEvent`, result spool, manifest, pending request or recovery guard; - never returned by `get_agent_task`, `read_agent_task_events`, task result APIs or any MCP surface; - never forwarded to provider code or the workdir; - never used for authorization, idempotency, lease identity or recovery decisions; - not printed by `diagnostics snapshot`; v0.22 task diagnostic JSON also has no requirement to expose trace/span IDs and should omit them unless a later reviewed operator requirement proves value. The MCP SDK owns protocol spans. ServerFS owns a separate Bridge-internal diagnostic trace. v0.22 establishes no parent/child relationship between them and does not depend on an SDK span being sampled or exported. ### 4.2 Bridge-only state The Bridge process owns all v0.22 diagnostic state. MCP Core owns only CLI/RPC invocation code and formatting. There is no Core diagnostic buffer, cross-process trace registry or duplicate task history. A diagnostic slot is associated with a ServerFS task ID only after a new task is actually created. Bridge-local staging may measure submission/lease work before task creation, then attach that bounded staging evidence to the newly created task. If submission returns an existing task because of `idempotency_key`, the new staging context is discarded: one task ID has at most one diagnostic slot in one Bridge process lifetime, and retry-safe resubmission never appends to or reopens the existing trace. If a submission fails before a task is created (for example writer lease `busy`), no queryable task trace is invented because there is no returned task ID. Existing metrics/audit evidence continues to represent that failed submission outcome. ## 5. Catalog v1: typed Agent causal operations `catalog_version = 1` is frozen for v0.22 JSON and the internal operation catalog. Changes that alter operation meaning, required fields or timing formulas require an explicit catalog-version decision rather than silently reusing version 1. ### 5.1 Allowed operation kinds The required v0.22 task operation kinds are: - `agent.queue_wait` - `agent.lease_acquire` - `agent.runtime_start` - `agent.execution` - `agent.waiting_external` - `agent.finalization` `agent.task` is a logical root/summary, not an accounted leaf duration. A terminal **seal** stores the task outcome/reason/wall-clock summary exactly once. `agent.recovery` is also allowed but may exist as an independent trace after Bridge restart. A recovery trace is never forced to claim a parent task trace that was lost with the prior process. The same operation kind may occur multiple times. In particular, `agent.execution` and `agent.waiting_external` are sequences, not single overwriteable slots: ```text execution #1 -> waiting_external #1 -> execution #2 -> ... ``` Rollups sum retained segments by operation meaning. ### 5.2 Typed fields only Each operation uses only catalog-defined typed fields such as: - operation kind; - monotonic start/end or duration; - bounded outcome value when meaningful; - bounded `reason_code` when meaningful; - bounded `runtime_family` from the v0.21 domain (`codex`, `claude`, `qoder`, `acp`, `unknown`); - interaction class (`approval` / `question`) for `waiting_external` where required; - sequence index internal to the retained task trace. No arbitrary map is allowed. Specifically forbidden are `attributes`, `extra`, free-form metadata, task prompt, provider text, exception message, URL, hostname, IP, username/principal, workdir alias/path, filesystem path, task/session/turn/correlation/idempotency identity as operation attributes, provider-native model ID, argv or environment. The Bridge diagnostics implementation should import/reuse the finite v0.21 Bridge-side domains already defined for metrics where possible instead of defining incompatible duplicate strings. For the terminal seal, reason normalization is deliberately coarse and deterministic so arbitrary task/provider error codes never become a second label vocabulary: | Terminal task outcome | Seal `reason_code` | |---|---| | `succeeded` | `ok` | | `cancelled` | `safe` | | `failed` | `degraded` | | `interrupted` | `unavailable` | Recovery-specific `recovery_guard` / `stop_unproven` semantics remain on the recovery observation path and do not rewrite the task terminal outcome. A later version may refine a bounded mapping only through an explicit catalog-version review; v0.22 does not export arbitrary `error_code` strings as diagnostic reason values. ### 5.3 Timing definitions v0.22 preserves the accepted v0.21 metric semantics. It does not silently redefine `serverfs_agent_task_duration_seconds`, queue wait or lease histograms. The diagnostic trace defines its own explicit timing basis: - **diagnostic wall clock**: Bridge-local monotonic time from diagnostic staging at new task submission through the terminal transition; this may include lease acquisition, task creation/queueing, external waiting and finalization. It is diagnostic-only and is not a redefinition of the v0.21 task-duration histogram. - **active execution**: sum of all retained `agent.execution` leaf segments; - **external wait**: sum of all retained `agent.waiting_external` segments; approval/question response time is not active ServerFS/Agent execution latency; - **lease wait rollup**: sum only `agent.lease_acquire` segments whose outcome is `acquired`; a `busy` diagnostic operation may record how long the rejected acquisition call took, but `busy` is not lease-wait latency; - **accounted**: sum of retained **leaf** phase durations only. Never add an `agent.task` root/summary duration to accounted time; - **unaccounted**: `diagnostic_wall_clock - accounted`. `unaccounted` must not be clamped to zero. A negative value is valid evidence that measured leaf phases overlap; hiding it would make the diagnostic report falsely precise. The existing v0.21 lease histogram already includes `outcome` (`acquired`, `busy`, `failure`). v0.24 latency SLI work can therefore filter to `outcome=acquired` without altering the v0.21 catalog. v0.22 only documents/tests this derivation rule. ### 5.4 Observable boundaries, not fabricated provider facts `agent.runtime_start` measures only the Bridge-owned runtime-dispatch/start boundary that implementation can actually observe. It must not claim that an external provider process/model is ready unless the provider contract supplies that evidence. `agent.execution` measures Bridge-observed provider execution intervals and is split around `_wait_for_request()` external waits. It does not infer provider-internal token-generation phases or remote queueing that ServerFS cannot observe. ## 6. Terminal seal and completeness contract Every **admitted diagnostic task slot** reserves capacity for exactly one terminal seal before ordinary phase recording consumes its budget. The seal contains only bounded fields: - terminal `outcome` from the existing task terminal vocabulary; - bounded `reason_code` normalized from the authoritative terminal transition; - diagnostic `wall_clock_seconds`; - terminal completion timestamp/monotonic boundary needed for ordering and rendering; - completeness/truncation state. The seal is not a suffix trace. It is a fixed final summary slot. Ordinary middle/late phases may stop recording after truncation, but `_transition_terminal()` must still write the seal exactly once for an admitted task. Contract tests must prove that terminal metric/task outcome and terminal seal outcome agree from the same transition seam, including prefix-truncated tasks. ### 6.1 Completeness states For an available retained task trace, JSON uses a closed completeness vocabulary: - `complete` - `truncated_prefix` - `restarted` - `restarted_truncated_prefix` `truncated_prefix` means only a continuous prefix of ordinary operations was retained before recording stopped; the terminal seal is still present. `restarted` means this Bridge generation observed later activity for a durable task that existed before the current process diagnostic lifetime. It must never be presented as a complete reconstruction of the pre-crash task. If that restarted trace also reaches an ordinary recording limit, use `restarted_truncated_prefix`. A recovery action after process restart may instead be represented as its own `agent.recovery` trace. v0.22 never persists trace identity merely to splice two process generations together. ## 7. Bounded in-memory buffer The v0.22 diagnostic recorder is always enabled but has hard compile-time/internal limits. These are not operator configuration knobs in v0.22. ### 7.1 Frozen limits - `MAX_DIAGNOSTIC_RECORDS = 2_048` retained ordinary/seal records across admitted task/recovery traces; - `MAX_DIAGNOSTIC_BYTES = 1_048_576` bytes (1 MiB) of retained logical record payload, measured deterministically from the compact UTF-8 JSON representation of the typed retained record; - `MAX_TASK_DIAGNOSTIC_RECORDS = 64` total record slots per task, with **one slot reserved for the terminal seal**, so at most 63 ordinary operation records are admitted; - `MAX_TASK_DIAGNOSTIC_BYTES = 32_768` bytes (32 KiB), including the reserved terminal seal budget; - `MAX_DIAGNOSTIC_OPERATION_BYTES = 1_024` bytes for one ordinary typed operation; an oversized operation is rejected as a whole and never partially stored; - `TERMINAL_SEAL_RESERVED_BYTES = 512` bytes within each admitted task's byte budget; - task-slot retention remains bounded by the global record/byte ceilings; no separate unbounded task index is permitted. If implementation measurements show the fixed typed seal cannot fit in 512 bytes, the implementation must shrink the seal schema rather than silently increasing the budget during coding. Any budget change requires an explicit plan amendment with evidence. ### 7.2 Admission and reservation When a new task diagnostic slot is admitted, the recorder reserves one record slot and 512 logical bytes for the future seal before admitting ordinary records. Ordinary recording stops early enough that this reservation remains available. If a new diagnostic slot cannot reserve its seal without violating the hard global ceilings after whole-terminal-task eviction, **no partial task trace is admitted**. The Agent task still proceeds normally; the recorder increments a bounded `diagnostic_admission_failures` counter exposed only through the snapshot. A later query for that durable task reports `not_retained` with a bounded reason such as `capacity_exhausted`, not a fabricated partial trace without a seal. This exceptional path preserves both hard memory bounds and the rule that every retained/admitted task trace can eventually receive exactly one terminal seal. ### 7.3 Eviction and truncation Eviction rules are frozen: 1. whole completed task traces are the only normal eviction unit; 2. completed traces are evicted by oldest `terminal_completed_at` first; 3. active task traces are never evicted and records are never removed from the middle of an active trace; 4. if whole-terminal-task eviction still cannot make space, stop ordinary recording for the **current task**, mark it prefix-truncated, and let business execution continue; 5. a single operation larger than `MAX_DIAGNOSTIC_OPERATION_BYTES` or the remaining task/global ordinary budget is rejected whole; never write a partial operation; 6. the reserved terminal seal remains writable even after ordinary recording stops; 7. one task ID has at most one slot in a Bridge process lifetime; an idempotent re-submit that resolves to the existing task does not append, reopen or create a second trace. No disk spill, SQLite fallback or background flush exists. ### 7.4 Internal failure counters Diagnostic recording errors must not be reduced to an untestable `except: pass`. The recorder maintains bounded process-local counters exposed only by `diagnostics snapshot`, including at minimum: - `record_failures`; - `admission_failures`; - `ordinary_records_dropped` / truncation count; - oversized-operation rejections; - completed-task evictions; - buffer reset/start timestamp. Recorder failure never changes task state, lease state, recovery state, provider cancellation or RPC success/failure. Tests inject recorder failures and prove task behavior remains unchanged while the failure counter advances. ## 8. Query and JSON contract ### 8.1 `serverfs diagnostics snapshot` Purpose: bounded current-state information about the diagnostic subsystem, not a task-history API. Required JSON includes `catalog_version` and bounded aggregate facts such as: - buffer process start/reset timestamp; - retained record count and logical bytes; - retained task-slot count; - active/terminal retained counts; - truncation/eviction/admission/record-failure counters; - whether the buffer has reset since durable tasks visible to the current Bridge predate this diagnostic generation, where this can be established safely. The snapshot must not include: - task IDs; - trace IDs or span IDs; - public `correlation_id`; - workdir/path; - provider session/turn IDs; - model IDs; - prompt/result/event payloads; - per-task lists. ### 8.2 `serverfs diagnostics task ` Purpose: local operator lookup for one durable Agent task. The task ID is a lookup key, not an exportable diagnostic attribute. The output may echo the requested task ID so the operator can verify which query was rendered, but it must not include `correlation_id`, workdir, path, provider session/turn ID, native model ID, prompt, provider output, environment or arbitrary errors. The JSON envelope has a closed `disposition` vocabulary: - `available` — a retained slot exists; - `unknown_task` — the durable Bridge store has no such task ID; - `not_retained` — the durable task exists but no diagnostic slot is retained/admitted in the current process; - `bridge_unavailable` — emitted by the CLI wrapper when the existing private Bridge endpoint cannot be reached safely. `not_retained` may include a bounded non-sensitive reason such as `evicted`, `bridge_restarted`, `capacity_exhausted` or `recording_unavailable`; it must not guess when the cause cannot be established. An `available` response includes: - `catalog_version`; - requested `task_id`; - `runtime_family` only (never model ID); - current/terminal bounded task outcome/status as appropriate; - completeness state; - ordered retained typed operations; - terminal seal when terminal; - rollups for diagnostic wall clock, accounted, unaccounted, active execution, external wait and acquired-only lease wait where applicable. After a Bridge restart, a task that has no current-generation slot must not return an empty successful trace: it is `not_retained`/`bridge_restarted`. If later current-generation activity for that same durable task is diagnostically observed, the retained result is `restarted` (or `restarted_truncated_prefix`), never `complete`. ### 8.3 RPC methods Add narrow read-only private RPC methods on the existing Bridge protocol, for example: - `diagnostics.snapshot` - `diagnostics.task` The exact method names may use the existing protocol naming convention, but their semantics above are frozen. They must: - use `AgentBridgeClient` and the existing endpoint only; - preserve Linux/macOS AF_UNIX peer boundary and Windows current-user Named Pipe SID verification; - open no new port/socket/pipe name; - read the Bridge recorder/store through service methods, not by letting the CLI read SQLite directly; - not appear in the MCP tool list or any MCP server schema; - remain read-only and non-mutating. Human output is rendered from the JSON object and has no additional hidden semantics. ## 9. Instrumentation seams and consistency rules Instrumentation must attach to existing authoritative transitions rather than creating an independent observer state machine. ### 9.1 Submission, lease and task slot In `BridgeService.submit_task()`: 1. preserve the current in-lock idempotency check first; if an existing task is returned, do not create diagnostic staging and do not mutate its existing diagnostic slot; 2. only after the idempotency check misses, create a Bridge-local staging `DiagnosticTraceContext` for this genuinely new submission path; 3. measure the existing lease acquire call without changing `LeaseManager`; 4. once a new task ID is created, admit/attach the staged diagnostic slot and its already-measured eligible phases; 5. if diagnostics cannot admit a slot, continue the business path and increment recorder admission failure evidence. The current lease metric remains emitted at the existing call site. Diagnostic lease outcome and metric lease outcome must be generated from the same caught/success condition, not reclassified later. ### 9.2 Task execution and external waiting In `_run_task()` and `_wait_for_request()`: - queue/start boundaries follow existing durable task transitions; - runtime-start measures only Bridge-owned dispatch setup; - provider execution is represented by one or more `agent.execution` segments; - **do not** start `agent.waiting_external` at `_wait_for_request()` entry: approval advice/validation performed before the durable wait remains Bridge work, not human wait; - close the current execution segment and start `agent.waiting_external` only after `store.create_pending_request()` successfully commits the task into `WAITING_FOR_APPROVAL` / `WAITING_FOR_QUESTION`; - close `agent.waiting_external` when `store.resolve_request()` successfully commits the task back to `RUNNING`, or at the corresponding timeout/stale/cancel terminal boundary; this excludes human response time without pretending scheduler handoff is provider execution; - start the next `agent.execution` segment only when `_wait_for_request()` actually returns control to the adapter/provider coroutine; any small resolve-to-resume gap remains honestly `unaccounted`; - timeout/cancel/error paths close any open diagnostic segment without changing authoritative task semantics; - finalization covers the bounded Bridge-owned work after provider completion/error detection through terminalization where measurable. No provider adapter is required to emit a new tracing protocol for v0.22. ### 9.3 Terminal transition `BridgeService._transition_terminal()` remains the terminal authority. From the same successfully applied transition: - existing `observe_terminal()` updates v0.21 metrics; - the diagnostic recorder writes exactly one terminal seal for an admitted slot; - both consume the same resulting `TaskRecord`/terminal status and bounded error classification. If either observational projection fails, the durable terminal transition remains authoritative and successful. Diagnostic/metric failure cannot roll back task state. Phase C must therefore harden the current terminal observation seam so **any ordinary `Exception` raised by metric or diagnostic projection after the durable transition is failure-isolated**; do not catch `BaseException` classes such as process termination/interrupt signals. Tests must inject an unexpected metric-projection exception (not only `TypeError`/`ValueError`) and prove the task terminal result remains authoritative. ### 9.4 Recovery Recovery observation uses the existing reconciliation/guard decision seams. After process restart, `agent.recovery` may be a new independent diagnostic trace. Do not persist a diagnostic identity or read historical task events merely to fabricate cross-generation continuity. ## 10. Trust domains and data handling v0.22 formalizes three separate schemas/trust domains: ### Local audit May retain richer local operational context where already allowed (for example the current audit `tool_call` record). Existing redaction rules continue to apply. Local audit is not exportable telemetry merely because a future setting enables telemetry. ### Local diagnostics Contains bounded timing/outcome evidence for causal task diagnosis. The task ID may be a local query key/output identity in the task command, but diagnostic operations contain no workdir/path/prompt/session/model/provider payload. State is memory-only and ephemeral. ### Future exportable operational telemetry Not implemented in v0.22. Its future adapter may consume only an explicitly exportable typed catalog. It must never accept arbitrary local audit records. This is a schema boundary, not a runtime boolean. ## 11. Performance and resource budgets Diagnostics are always on in v0.22, so their bounded cost must be measured independently from the future v0.23 rule that an exporter-disabled path has zero exporter/network cost. Phase A captures a fresh pre-v0.22 baseline using the existing v0.21 Bridge benchmark shape before instrumentation. The following release budgets are frozen unless the Phase A baseline demonstrates that a threshold is below reproducible measurement resolution; any revision must be recorded **before** implementation of the affected hot path. ### 11.1 Recorder microbudget Using a deterministic in-process benchmark with preconstructed typed operations and no filesystem/provider/network activity, three independent runs report the cross-run median of per-run p95: - ordinary non-evicting `record()` p95: **<= 0.20 ms**; - terminal seal write p95: **<= 0.25 ms**; - whole-completed-task eviction + record p95: **<= 1.00 ms**; - snapshot generation p95 with a saturated buffer: **<= 10 ms**; - one retained task JSON projection p95 at the per-task 64-record ceiling: **<= 5 ms**. `record()` is synchronous, performs no network/disk I/O, contains no `await`, starts no thread/task/timer, and never blocks on provider/runtime work. ### 11.2 Process/resource budget On the Linux/VPS reference benchmark: - no background diagnostics loop/thread/timer; - at the frozen global logical-payload ceiling, incremental Bridge RSS attributable to v0.22 diagnostics must be **<= 4 MiB** over the same-process baseline; - after the buffer reaches a steady retained set, idle RSS must show no continuing growth attributable to diagnostics; - no diagnostic file/SQLite growth exists because diagnostics are memory-only; - cross-run median `submit_task` p95 may increase by at most **5.0 ms** versus the fresh Phase A v0.21 baseline using the same workload shape; - ordinary non-Agent filesystem MCP handling remains structurally untouched by the Bridge diagnostic recorder. Task/provider end-to-end time remains diagnostic evidence rather than a tight performance gate because external/runtime scheduling variance can dominate it. ### 11.3 Overflow behavior is part of the performance contract When budgets are exhausted, the recorder drops/truncates according to §7 rather than applying backpressure, blocking, flushing to disk or allocating beyond the hard ceiling. Overflow behavior must itself pass the recorder microbudget/failure-isolation tests. ## 12. Security and compatibility invariants The following are release blockers if violated: - no diagnostic identity in SQLite schema/rows, `TaskRecord`, Bridge events, manifests, result spools or pending interactions; - no public MCP schema/tool/resource change for diagnostics; - no diagnostic `trace_id`/`span_id` in `diagnostics snapshot`; - no `correlation_id`, workdir, path, provider session/turn ID, model ID, prompt/result, environment or arbitrary error text in task diagnostic JSON; - no inbound W3C `traceparent` inheritance into `DiagnosticTraceContext`; - no OpenTelemetry dependency required by root or Bridge packages; - no new listener/port/socket/pipe endpoint; - no weakening of AF_UNIX peer validation or Windows current-user Named Pipe SID validation; - no change to workdir authorization, writer lease, recovery guard, provider sandbox/approval, cancellation, task state, idempotency or platform lifecycle semantics; - no diagnostic failure may cause a task/lease/recovery failure or alter its outcome; - no diagnostics persistence added to improve restart continuity; - no hidden text-only fields outside the JSON contract; - protocol stdout remains clean and diagnostic CLI follows existing stdout/stderr conventions without contaminating MCP stdio framing. ## 13. Implementation phases ### Phase A — Current-state inventory, baseline and contract freeze Inspect the v0.21 stable implementation and record exact source seams for: - new submission/idempotency/lease/task creation; - queue/start/running transitions; - approval/question waiting and resume; - provider completion/error/cancel/timeout; - terminal transition + metric emission; - recovery/guard outcomes; - Bridge RPC transport and CLI endpoint discovery on Linux/macOS/Windows. Capture a fresh three-run Linux/VPS pre-instrumentation performance baseline and a microbenchmark harness skeleton. Freeze catalog v1 JSON fixtures, operation/value domains, completeness/disposition enums, buffer constants and the exact measured timing boundaries before source instrumentation. **Gate:** no unresolved identity/timing/persistence boundary and reproducible baseline evidence exists. If an intended phase cannot be measured from an existing authoritative seam, narrow the phase meaning rather than inventing provider facts. ### Phase B — Typed diagnostic model and task-atomic buffer Primary expected source: - new `agent_bridge/src/serverfs_agent_bridge/diagnostics.py` (or an equivalently narrow Bridge-local module); - focused `agent_bridge/tests/test_diagnostics.py`. Implement: - `DiagnosticTraceContext`; - typed operation/seal/result models; - catalog version 1 validation; - task slot and restart/truncation state; - record/byte accounting; - terminal-seal reservation; - task-atomic terminal eviction; - prefix truncation; - process-local aggregate failure/drop/eviction counters; - deterministic snapshot/task projection helpers. Do not wire provider/task behavior in this phase beyond synthetic tests. Required tests include exact byte/record ceilings, oversized-record rejection, seal reservation, active-task non-eviction, terminal eviction order, idempotent slot identity, complete/truncated/restarted results, negative `unaccounted`, multi-segment execution/wait rollups and failure-counter behavior. **Gate:** buffer/model tests prove hard bounds and no partial-hole semantics without touching durable state. ### Phase C — Bridge lifecycle instrumentation and metric consistency Primary expected edits: - `agent_bridge/.../service.py`; - minimally `agent_bridge/.../observability.py` only where an existing bounded value domain/helper must be reused, not to redesign v0.21 metrics; - focused service/observability tests. Wire the diagnostic recorder at the authoritative seams in §9. Keep all diagnostic calls failure-isolated. Do not alter `LeaseManager`, durable task transitions, provider adapters or recovery authority merely to make diagnostics convenient. Add contract tests proving: - terminal task status == existing terminal metric outcome == terminal seal outcome for admitted tasks; - prefix truncation still receives the matching seal; - multiple approval/question cycles split execution and external waiting correctly; - `busy` is not included in acquired lease-wait rollup; - v0.21 wall-clock task metric behavior is unchanged; - idempotent resubmission does not append/reopen diagnostics; - injected recorder exceptions/capacity failures do not change task result or lease/recovery behavior. **Gate:** Agent causal diagnostics are complete enough to explain measured Bridge phases without a second state machine or semantic drift from metrics. ### Phase D — Private diagnostic RPC and operator CLI Primary expected edits: - `agent_bridge/.../protocol.py`; - Bridge service read-only diagnostic methods; - `src/serverfs_mcp/cli.py`; - root/Bridge CLI/protocol tests. Add `serverfs diagnostics snapshot` and `serverfs diagnostics task ` with JSON and human rendering. Reuse `AgentBridgeClient`; do not read SQLite directly from the CLI and do not add a new transport endpoint. Test: - JSON schema fixtures including `catalog_version`; - `available`, `unknown_task`, `not_retained`, `bridge_unavailable`; - `complete`, `truncated_prefix`, `restarted`, `restarted_truncated_prefix`; - restart/reset behavior; - JSON ↔ human semantic parity; - snapshot has no task/trace/span identity; - task output contains none of the forbidden sensitive/business identity fields; - diagnostic RPC methods are absent from the MCP tool list. **Gate:** local operator queries work through the same private IPC/security path on supported fixtures and expose only the frozen JSON contract. ### Phase E — Security, fault injection and performance acceptance Run deterministic fault injection for: - recorder raises internally; - global buffer full with evictable terminal traces; - global buffer full with only active traces; - per-task record limit; - per-task/global byte limit; - oversized operation; - terminal seal after truncation; - Bridge restart with durable tasks still present; - restarted task records new current-generation phases; - task evicted but still present in SQLite; - unknown task; - Bridge unavailable; - corrupt/unexpected diagnostic internal state (must fail boundedly without affecting task authority). Run sentinel scans/tests proving no diagnostic IDs in SQLite/public task/event/result/manifest state and no forbidden content in diagnostic JSON. Run the recorder microbenchmarks and the same three-run Bridge benchmark shape used for Phase A. Compare against §11 hard budgets. **Gate:** security/failure isolation and all measurable Linux/VPS performance budgets pass, or the release stops with explicit failed evidence. ### Phase F — Native platform acceptance and documentation Linux/VPS: - full root + Bridge deterministic suites; - real private AF_UNIX diagnostic RPC/CLI evidence against a local Bridge; - Phase E performance evidence. macOS supported native boundary: - root/Bridge/platform tests; - read-only diagnostic CLI over the existing AF_UNIX Bridge endpoint under the current user lifecycle; - no new Login Item/SMAppService mutation is required solely for diagnostics acceptance. Windows supported native boundary: - root/Bridge/platform tests; - diagnostic CLI over the existing current-user Named Pipe; - evidence that the existing server-SID assertion path is still used before request bytes are sent; - no new Scheduled Task/lifecycle mutation is required solely for a read-only diagnostic query. Update current-state README/maintainer/operator/website documentation only after behavior is accepted. Keep English/Simplified Chinese release-facing meaning aligned. Historical v0.21 evidence remains historical. **Gate:** platform evidence supports only what was actually measured; missing local checks are `Not verified`, not silently inherited from Linux. ### Phase G — Release readiness Run all required root/Bridge/platform/site gates, compatibility review, secret/sensitive-output review, stale-current-state scan, and produce a v0.22 acceptance record mapping every frozen criterion in §15 to direct evidence. Current evidence: Linux/VPS root Ruff/format and full pytest pass (1,735 passed, 317 skipped); Agent Bridge Ruff/format and pytest pass (459 passed, 15 skipped). Supported macOS full Phase F remains accepted (root 1,540 passed, 394 skipped; Bridge 428 passed, 46 skipped), with later isolated cross-platform Ruff/format and six affected modules passing (86 passed, 48 skipped) and live AF_UNIX diagnostics evidence. Windows root Ruff/format and non-D9 pytest pass (1,754 passed, 220 skipped); Agent Bridge Ruff/format and pytest pass (600 passed, 52 skipped); the isolated D9 native lifecycle suite passes (30 passed) after a zero-active-task proof, with the current-user service restored to enabled/running. Phase F is accepted on Linux/VPS, supported macOS and supported Windows. Phase G is PASS: release-facing docs/site alignment, stale-state and diff checks, and the exact-copy site build are complete. v0.22.0 is release-ready but remains unreleased until an explicit maintainer release action. No tag/release is created merely by completing this plan. Release remains an explicit maintainer action. ## 14. Test matrix | Area | Required deterministic cases | Primary evidence | |---|---|---| | Identity | random Bridge-generated context; no caller/SDK/correlation inheritance | model + service tests | | Persistence exclusion | SQLite schema/rows, task/event/result/manifest contain no diagnostic identity | store/service sentinel tests | | Catalog | version, closed operation/value domains, no arbitrary attributes | diagnostics contract tests | | Multi-segment execution | execution/wait/execution cycles; rollup sums all segments | service diagnostics tests | | Wall/accounting | leaf-only accounted; negative unaccounted preserved | diagnostics tests | | Terminal seal | exactly once; matching terminal outcome; still written after prefix truncation | service/diagnostics tests | | Capacity | 2048 records / 1 MiB global; 64 records / 32 KiB task; 1 KiB operation | buffer boundary tests | | Eviction | oldest terminal first; active never evicted; no middle holes | buffer tests | | Admission failure | no seal reservation available => no partial slot; task continues | service fault tests | | Idempotency | retry returns same task without second slot/append | service tests | | Restart | old task not retained; later phase => restarted; recovery may be independent | restart/recovery tests | | Query disposition | available/unknown_task/not_retained/bridge_unavailable | protocol/CLI tests | | Snapshot privacy | no task/trace/span/correlation/workdir/path/model/session data | JSON sentinel tests | | Task privacy | only task lookup identity + bounded catalog; forbidden fields absent | JSON sentinel tests | | RPC containment | existing AF_UNIX/Named Pipe path only; Windows SID assertion preserved | client/platform tests | | MCP boundary | no diagnostic MCP tool/resource/prompt | root MCP schema tests | | Metric consistency | same transition yields same terminal/lease/recovery bounded outcome | service + observability tests | | Failure isolation | recorder exceptions/overflow never alter task/lease/recovery result | fault injection | | Performance | record/seal/eviction/query microbudget + RSS/submit budget | benchmark evidence | ## 15. Release acceptance criteria v0.22.0 is release-ready only if all applicable criteria pass: 1. `DiagnosticTraceContext` is Bridge-generated and independent from public `correlation_id`, task/provider identity, MCP SDK/W3C inbound trace context and authorization. 2. v0.22 adds no Core diagnostic carrier and no diagnostic identity field to `task.submit` RPC. 3. Diagnostic identity/state is absent from SQLite, durable events, task/result/manifest/pending-request/public MCP state. 4. Catalog v1 is typed/closed and the required Agent causal phase semantics are documented/tested. 5. Multiple execution/external-wait segments are preserved and roll up correctly; active execution excludes external waiting. 6. Existing v0.21 task/queue/lease/recovery metric semantics remain compatible; `busy` is excluded only from derived acquired lease-wait rollup/SLI semantics, not rewritten from historical metrics. 7. Every admitted terminal task receives exactly one seal even after ordinary prefix truncation, and terminal seal outcome agrees with the authoritative terminal transition/metric outcome. 8. Buffer limits equal the frozen constants in §7; active traces are not fragmented; terminal eviction order and admission-failure behavior are proven. 9. `accounted` uses leaf phases only and `unaccounted` is not clamped, including negative overlap evidence. 10. Snapshot JSON includes `catalog_version` and contains no task/trace/span identity or forbidden sensitive/business context. 11. Task query distinguishes `available`, `unknown_task`, `not_retained`, and CLI-level `bridge_unavailable`; available traces distinguish complete/truncated/restarted states without fabricating pre-restart history. 12. Diagnostic CLI uses existing `AgentBridgeClient` transport/security semantics and no new listener/endpoint/MCP capability exists. 13. Recorder failures/overflow are observable through bounded snapshot counters but never alter task, lease, recovery or provider authority/outcomes. 14. Linux/VPS recorder/resource/submit performance budgets in §11 pass with reproducible before/after evidence. 15. Linux, supported macOS and supported Windows claims are backed by matching platform evidence; Windows retains current-user SID-verified Named Pipe semantics. 16. Root/Bridge regressions, Ruff/format, applicable platform suites, `git diff --check`, secret/sentinel scans and documentation/site gates all pass where changed. 17. Current documentation accurately describes v0.22 without turning future v0.23 exporter plans into current product claims. Any failed applicable item blocks v0.22 release. Unavailable platform evidence is recorded as `Not verified`; it is never converted into a pass by inference. ## 16. Expected code-change map The implementation should stay surgical. Expected files/modules include: - `agent_bridge/src/serverfs_agent_bridge/diagnostics.py` — new typed catalog, buffer, projection and failure counters; - `agent_bridge/src/serverfs_agent_bridge/service.py` — authoritative lifecycle instrumentation and read-only diagnostic service methods; - `agent_bridge/src/serverfs_agent_bridge/protocol.py` — private diagnostic RPC dispatch only; - `agent_bridge/src/serverfs_agent_bridge/observability.py` — preferably unchanged except narrow reuse/refactoring of existing bounded vocabulary if necessary without metric semantic changes; - `src/serverfs_mcp/cli.py` — diagnostics command parsing, existing endpoint/client use, JSON/human rendering; - focused new/updated tests under `agent_bridge/tests/` and root `tests/`; - `agent_bridge/benchmarks/` — reproducible v0.22 baseline/microbenchmark evidence harness; - `docs/maintainers/invariants.md` / workflow docs only if implementation establishes durable maintainer rules that must survive this release; - README/site/operator docs only after the feature passes acceptance. Avoid unrelated refactors, provider adapter changes, store migrations, new dependencies or platform lifecycle rewrites. ## 17. Validation commands At minimum, implementation changes touching the Bridge run: ```bash # repository root uv sync --frozen uv run ruff check . uv run ruff format --check . uv run pytest # Agent Bridge cd agent_bridge uv sync --frozen uv run ruff check . uv run ruff format --check . uv run pytest ``` Then run the v0.22 deterministic benchmark/fault-injection harnesses and platform-specific acceptance required by Phase F. If release-facing site files change: ```bash cd site npm ci npm run build ``` Run `git diff --check` and repository secret/sensitive-output checks before acceptance. Exact-current commands must follow `docs/maintainers/workflow.md` if that file changes before implementation. ## 18. Future v0.23 exporter decision pre-declaration — not a v0.22 deliverable A later v0.23 review may reopen the v0.21 exporter decision only after v0.22 semantics are accepted. The current intended review input is: | Decision input | Pre-declared candidate for v0.23 review | |---|---| | Named consumer | OpenTelemetry Collector | | Topology | ServerFS optional OTLP push -> local Collector | | Default endpoint | loopback `127.0.0.1:4318` candidate, never `0.0.0.0` by default | | Trust assumption | single-user local loopback is not multi-user isolation; non-loopback requires a new explicit security review | | Enablement | disabled by default | | Logs | disabled by default; audit schema remains non-exportable regardless of setting | | Content | no prompt/response/tool payload recording | | Platform matrix | Linux + supported macOS + supported Windows must each be evaluated | | Metric reset | process-local metrics reset on process restart unless a later reviewed design says otherwise | | Dependencies | optional and isolated from default root/Bridge path | | Disabled exporter cost | zero exporter network activity and no mandatory exporter dependency | | Failure behavior | exporter/Collector failure cannot affect ServerFS/Bridge availability | | Correlation with MCP SDK | if useful, adapter may evaluate an explicit **span link**; v0.22 diagnostic trace never becomes a child of caller-controlled SDK/W3C trace context | This table is planning context only. v0.22 must not add OTLP settings, dependencies, egress, listener behavior or tests merely to prepare for v0.23.