audit-emit
A single, append-only emit point. Every other module calls audit_emit.row(...) instead of writing to swarm.jsonl themselves. Buys consistency, schema enforcement, and one place to plug an external sink.
Why this exists
Today: draft_review.py, flow_engine.py, and the existing bots each write to .harness/swarm/swarm.jsonl independently. Schema drifts as new modules are added. Bug surface: forgetting to log a transition.
After this skill: every module calls audit_emit.row(type, **fields) and the schema is enforced at one boundary.
Schema (canonical)
{
"ts": "ISO-8601",
"type": "draft_posted" | "draft_reaction" | "draft_expired"
| "flow_start" | "step_start" | "step_complete" | "step_error" | "flow_end"
| "cos_intent_classified" | "cos_routed"
| "curator_proposal" | "curator_accepted" | "curator_rejected"
| "kill_switch_triggered" | "kill_switch_resumed"
| "pii_redacted",
"actor_role": "CoS" | "Margot" | "Scribe" | "Dispatcher" | "Curator" | "Guardian" | "Builder" | "Click",
"session_id": "...", // when applicable
"flow_id": "...", // when applicable
"step_id": "...", // when applicable
"draft_id": "...", // when applicable
"fields": { ... } // type-specific structured payload
}
Schema validation: a small _VALID_TYPES: set[str] + minimal field requirements per type. Unknown types are rejected at emit time (not silently dropped) — surfaces missing entries before they spread.
Optional Langfuse sink
Per Hermes v0.12 release, Langfuse plugin is bundled. When LANGFUSE_HOST + LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY are set, audit_emit.row() ALSO posts the row to Langfuse. Otherwise, local-only.
Failure mode: Langfuse send failure logs at WARNING and never raises. The local jsonl write is the source of truth.
Contract
audit_emit.row(
type: str,
actor_role: str,
*,
session_id: str | None = None,
flow_id: str | None = None,
step_id: str | None = None,
draft_id: str | None = None,
**fields: Any,
) -> None
Synchronous. Local jsonl write is atomic-append. Langfuse post is fire-and-forget on a thread-pool.
Migration plan (no breaking changes)
- Land
audit_emit.pywith the schema + emit function. Empty_VALID_TYPESwhitelist initially. - Update each module to call
audit_emit.row()instead of inline_append_audit(). Whitelist the types as the calls go in. - Remove the per-module audit-write helpers.
- CI gate:
grep -rn "swarm.jsonl" --include='*.py'returns ONLYaudit_emit.py.
Migration is safe to ship one module at a time — old + new emitters produce the same schema until the migration completes.
Safety bindings
- Append-only. No edits, no deletes. Rotation by date file (e.g.
swarm.jsonl.2026-05-01) handled by an external log-rotate cron, not by this skill. - PII guard at emit. Before writing, every string field with length >32 chars is passed through
pii_redactor.redact(strictness="standard"). Caller can opt out per-field viafields["__no_redact"]: ["field_name"]. - No emit during kill-switch. When
TAO_SWARM_ENABLED=0, emit still WRITES (audit is supposed to capture every state — including kill-switch transitions) but Langfuse sink is suppressed (avoid noisy alerts on a halted system). - Size cap. Single jsonl row capped at 64KB. Over-cap rows truncated with a
truncated_at: <bytes>marker.
Verification
- Call
audit_emit.row("flow_start", "Dispatcher", flow_id="f1", name="test")→ expect 1 line inswarm.jsonlwith the canonical schema. - Call with unknown type → raises
ValueErrorat the boundary; no write. - Call with a 100KB string in a field → row is written but truncated with marker.
- Call with PII in a field (
message="Card 4532...") → field is redacted before write; original never lands in jsonl. - With Langfuse env unset → local-only write succeeds, no warning.
- With Langfuse env set + Langfuse unreachable → local write succeeds, WARNING log, no exception.
Out of scope
- Replay / time-travel debugging from audit log — Wave 4.
- Audit-driven analytics dashboards — separate task in
dashboard/. - Replacing the dashboard's mission-control aggregator — that has its own concerns.
References
- Hermes Sprint 1 SWARM-006 (replaced by Langfuse per Path C verdict):
~/Pi-CEO/Hermes-Swarm-Recommendation-2026-04-14.md - Existing audit producers (to be migrated):
swarm/draft_review.py—_append_jsonl(_audit_swarm_jsonl(), ...)swarm/flow_engine.py—_append_audit(...)swarm/orchestrator.py— daily report emits
- Topology:
~/Pi-CEO/Second-Brain-Agent-Topology-2026-05-01.md