← Ledger


title: Phase 7b (sniffer slice) close — console emits agent.* for SNIFFER date: 2026-06-29 status: Accepted phase: 7b (sniffer slice) predecessor: docs/decisions/2026-06-29-phase-7b-council-close.md spec: docs/specs/2026-06-29-agent-registration-v1.md plan: docs/plans/2026-06-29-phase-7b-sniffer.md

Phase 7b sniffer-slice close

Shipped

Locked decisions

1. Subsystem assignment: SS-04 WORKSPACE

Sniffer is operator-scoped tooling adjacent to the workspace surface and not yet a discrete SS#. The spec's owner.subsystem enum requires SS-01..SS-08. SS-04 is the closest fit (mirrors how COUNCIL lives under SS-04). A future SS# split-out would be additive at the projection layer.

2. invocation_id = randomUUID() per request

Sniffer has no native execution id (unlike RELAY's result.executionId). Mint once at agent.registered emit time, reuse across agent.invoked + agent.completed. Deterministic per request; not threaded into the response body.

3. Single capability in v1

sniffer.analyze_repo is the only registered verb. Future per-language / per-mode analyzers register as additional verbs in the same capability set (additive; consumer dedupes).

4. Failure path emits the triple

Unlike RELAY (no 5xx emit because no executionId available), sniffer mints invocation_id up-front, so the triple stays consistent across success and failure. AnalysisError and unexpected throws both produce completed{outcome:"error"}. The HTTP status map (404/400/429/502/500) preserves the failure mode for operators reading the route response.

5. Rate-limit 429 short-circuits emission

The per-user 5/hour limit fires before any agent emission. Request never reached the analyzer; the agent projection records no invocation. Operator-side observability of rate-limit hits stays in the existing 429 response + server logs.

6. Header gate consistent with prior slices

x-rocky-workspace required for emission. No header → no agent identity → no triple. Existing sniffer audit (rate-limiter recordSuccess) continues independently.

Deferred

Polar entitlement check at invocation time

tier_floor: "solo" + seats_required: 0 → no-op for this slice. Real check waits for Phase 7c.

Airlock agent.approve verb

approval.required: false. Sniffer is operator-curated; no per-invocation approval gate. First approval.required: true candidate is now hearth driver (per producer-order from 2026-06-29-agentic-layer-projection.md).

Per-stage sub-events (fetch → select → analyze → respond)

The sniffer pipeline has internal stages (GitHub fetch, file selection, Anthropic call, validation). Each is a logical sub-step but not a distinct agent invocation. Sub-event decomposition deferred until the LORE causality graph proves it adds value.

Carry-forward to remaining 7b producers

This slice extends the pattern from 2026-06-29-phase-7b-relay-close.md §Carry-forward with one new wrinkle: synchronous producers without a native execution id mint invocation_id via randomUUID() up-front so the failure path can emit a coherent triple. STRATT will inherit this when it lands (also one-shot, no native id at the strategy-entry surface). Hearth driver is async (Go process, RPC over Unix socket) and reverts to the RALPH split-emit shape.

Remaining producers: STRATT (next, per producer-order), hearth driver (cross-submodule; Go producer in hearth/). Then 7b-ralph-2 (terminal observer) and 7c (hatch consumer + Polar RATE column).

Acceptance verified

Sources