ArmorCodex and ArmorClaude Observability
Understand how ArmorCodex and ArmorClaude assemble, complete, and ship one trace per agent turn.
ArmorCodex and ArmorClaude both map one agent turn to one iap.plan trace, but they cannot use the same delivery design. ArmorCodex receives each hook in a new process; ArmorClaude normally keeps a daemon alive across hooks.
| Behavior | ArmorCodex | ArmorClaude |
|---|---|---|
| Runtime | New short-lived process for every hook | Long-lived daemon, with an in-process fallback |
| Turn state | Append-only NDJSON on disk | Per-session recorder registry in memory |
| Trace completion | Assemble and ship during Stop | End the trace during Stop; background shipper sends it |
| Session cleanup | Stale-file sweep on a later SessionStart | Flush and remove the recorder on SessionEnd |
| Typical visibility | After the Stop hook finishes shipping | After Stop and the next background flush |
Both integrations are additive and fail-open. A telemetry exception does not modify the hook's policy result. Boundary work is still real work: ArmorCodex awaits shipping in Stop, and ArmorClaude awaits final shipping in SessionEnd, so an unavailable endpoint can add latency at those boundaries.
ArmorCodex
ArmorCodex has no persistent observability daemon. A recorder created by one hook process cannot be resumed by the next process, so the bridge accumulates a turn on disk and constructs a fresh recorder only when it is ready to ship.
Turn lifecycle
UserPromptSubmit ── append trace metadata + plan-start span
PreToolUse / PermissionRequest ── append check + policy spans
PostToolUse ── append tool-report span
Stop ── rename, assemble, add token usage, ship, deleteUserPromptSubmitcreates the turn metadata and aniap.plan.startspan. If a later hook arrives without metadata,Stopcan synthesize a minimal trace record.PreToolUseandPermissionRequestappend aniap.checkspan and a nestedpolicy_callspan. Allow and deny decisions are preserved.PostToolUseappends atool.reportspan withokorerrorstatus inferred from the hook payload.Stopclaims the turn file, reconstructs the trace, adds per-turn generation usage from the transcript, and posts the complete trace once.
The bridge uses a file named for a SHA-256 hash of the hook session ID rather than placing the raw session ID in a path. The backend receives the session ID only when it is a valid UUID.
Append-only NDJSON
Each hook appends one self-contained JSON line to a per-turn NDJSON file. The file is opened with O_APPEND | O_CREAT | O_WRONLY, and each record is written in a single operation. This avoids the lost-update race of reading a JSON document, modifying it, and renaming a replacement while another hook process is doing the same.
Attribute objects are capped before serialization and every NDJSON line is limited to 3,800 bytes. If the process is killed during a write, assembly skips only the malformed final line and keeps other complete records.
Assembly and shipping at Stop
Stop first atomically renames the live .ndjson file to a .ndjson.shipping file. This gives the Stop process exclusive ownership of the claimed file before it reads anything. It then:
- reads the metadata and all complete span records;
- starts a fresh in-memory
ObservabilityRecorder; - replays the spans under a new recorder-owned trace ID;
- adds transcript token deltas as generation spans;
- ends the trace and awaits the POST to
/observability/spans; - removes the claimed file.
Because the hook process is short-lived, it cannot rely on the SDK's five-second periodic timer. The explicit flush is required for delivery. It also means an unavailable backend can make Stop wait for retries even though failures never change the policy outcome.
If the shipper exhausts its retries, the batch is dropped and the claimed file is removed. If the Stop process fails before cleanup, the .shipping file remains but is not treated as a durable retry queue.
Stale cleanup
A turn that never reaches Stop leaves an unshipped file. On a later SessionStart, ArmorCodex removes live or claimed observability files older than six hours. Partial stale traces are deleted rather than sent long after their original turn.
There is a narrow late-hook edge case: a hook that appends after Stop has renamed and shipped the prior file creates a new file at the original path. That record can be associated with a later turn or eventually removed by stale cleanup. The normal hook order completes tool hooks before Stop.
Enable or opt out
Observability is enabled when an API key is available. It uses the configured ArmorIQ backend base URL and sends product armorcodex.
The configuration bridge honors a forwarded disable_observability plugin option, but the current packaged plugin metadata may not expose it in a configuration UI. The environment variable below is the reliable customer-facing opt-out.
Set the opt-out environment variable before starting Codex:
export ARMORCODEX_OBSERVABILITY_DISABLED=trueAccepted true values include true, 1, yes, y, and on. If no API key is configured, ArmorCodex does not create observability records or make ingest requests.
ArmorClaude
ArmorClaude normally routes hooks through one long-lived daemon. The observability bridge keeps a module-level registry keyed by the hook session ID, so a recorder and active trace persist across the turn's hook events.
The non-daemon fallback is best-effort and process-local. It cannot provide the same cross-hook in-memory nesting as the daemon path.
Per-request effective configuration
Each hook forwards the current authentication and observability configuration to the daemon. The daemon creates an immutable effective snapshot for that request by overlaying those values onto its startup configuration. This prevents an old, environment-poor daemon configuration from silently disabling telemetry for new hook requests and avoids mutating shared configuration while sessions run concurrently.
The first event for a session creates its recorder from the effective request configuration. The registry correlates later events under the same hook session ID. Only UUID session and user IDs are sent in UUID-typed backend fields; the free-form agent ID is preserved.
Turn and session lifecycle
UserPromptSubmit ── start iap.plan trace
PreToolUse ── add check + policy spans
PostToolUse / PostToolUseFailure ── add tool-report span
Stop ── end trace for background shipping
SessionEnd ── end any active trace, flush, remove recorder
Daemon shutdown ── flush already-ended tracesUserPromptSubmitcloses any prior active trace, starts the nextiap.plantrace, and records a bounded prompt preview.PreToolUserecordsallow,deny, oraskas a nested policy call.PostToolUseandPostToolUseFailurerecord tool output withokorerrorstatus.Stopcloses the current trace. Ended traces become eligible for the SDK background shipper, which flushes every five seconds by default.SessionEndcloses any remaining trace, explicitly flushes the recorder, stops its per-session timer, and removes it from the registry.- Daemon shutdown and idle-timeout cleanup flush already-ended traces from every live recorder.
ArmorClaude also reports cumulative transcript token usage through the separate dashboard token-usage path at Stop. The current observability bridge itself records policy and tool spans; it does not add transcript-derived generation spans to the iap.plan trace.
Enable or opt out
Observability is enabled by default when an API key is available. It uses the configured ArmorIQ backend base URL and sends product armorclaude.
Disable it with the disable_observability plugin option or with:
export ARMORIQ_OBSERVABILITY_DISABLED=trueThe plugin option takes precedence over the legacy environment value. Accepted true values include true, 1, yes, y, and on.
Data visibility
Neither integration streams spans to the dashboard while a turn is active.
- ArmorCodex sends the assembled trace during
Stop. - ArmorClaude ends the trace during
Stopand normally sends it on the next shipper flush. - The dashboard polls every 30 seconds.
It is normal for the turn to finish before its trace appears. See the dashboard guide for refresh behavior and troubleshooting for missing or delayed data.
Failure and privacy boundaries
All bridge operations catch telemetry failures so they do not change enforcement. Failed batches can still be dropped, and awaited boundary shipping can still delay hook completion.
Telemetry can include prompts, policy inputs, tool inputs, and tool outputs. The bridges apply bounded sanitization such as depth, item-count, string-length, attribute-size, and known secret-pattern limits. ArmorCodex also redacts a bounded set of sensitive field names before writing observability records. These controls are not general-purpose data-loss prevention and do not guarantee that every secret or personal value is removed.
Avoid placing credentials in prompts or tool arguments, restrict access to the observability dashboard, and use safe placeholders when debugging. See the shared data model for exact span fields and the observability overview for the end-to-end architecture.