agent-harness GitHub

Claude Code to agent-loop v0: mapping note

Status: proposed adapter design, 2026-09-23. Native qualification and adapter execution: NOT RUN. This file implements documentation only. Parent #299, relates #303, tracked by #312. Canonical contract: AGENT_LOOP_SPANS.md.

Telemetry ≠ merge gate. No mapping, missing span, failed exporter, cost estimate or LLM/vendor score changes merge eligibility. This note adds no mandatory review process. Existing deterministic oracles retain their independent meaning.

Classification/date convention: FACT describes first-party documentation inspected 2026-09-23; every proposed mapping, control, example and unmarked recommendation is INFER dated 2026-09-23. OVERSOLD marks an interpretation rejected as proof.

Source boundary

FACT: The monitoring guide documents beta tracing, a hierarchy containing interaction/model/tool spans with permission and execution children, and tool-call correlation. It documents non-interactive inbound trace context and deferred tools rejoining earlier turn traces. Some surrounding spans stay UNSET despite child errors. These are documented capabilities, not measurements of this workstation. C1

Only map a source family whose version/configuration has been identified. Record agent_harness.source.name=claude_code and the observed application version. Missing version means version-sensitive fields remain unqualified, not that the newest documented behavior applies. A missing plan or execution child can be a visibility gap, denied operation or version difference; do not fabricate a successful span.

Native boundary to canonical record

The left column names documented native records C1. The remaining columns are proposed adapter decisions, not upstream requirements. Safe names still pass a harness-owned allowlist; unknown native text never passes through by default.

Native record Proposed representation Units, availability and loss
claude_code.interaction invoke_agent claude_code; retain native IDs and actual parent Native interval and interaction.duration_ms; do not force an upstream child to root
claude_code.llm_request One logical inference span; operation only when API semantics are known duration_ms includes retries; retain observed status, not a vendor quality verdict
claude_code.tool One execute_tool span per native tool invocation Wrapper duration includes wait/execution; choose this as the tool-invocation counting surface
claude_code.tool.execution Auxiliary agent_harness.tool.execution INTERNAL span Execution duration/outcome only; not another execute_tool count
claude_code.tool.blocked_on_user Auxiliary agent_harness.tool.permission_wait INTERNAL span Wait milliseconds and safe accept/reject category; no permission-policy change
gen_ai.request.attempt native span event agent_harness.request.attempt event on the logical inference Preserve source attempt identity; do not claim the native event name is a standard GenAI event
claude_code.subagent_completed agent_harness.subagent.completed event Keep completion observation; no fabricated start interval or extra cumulative usage
Native subagent model/tool children Retain actual parentage under launching tool Do not invent invoke_agent or plan intervals solely to match a prettier tree
Tool-result/API-request log events Correlated auxiliary observations, or explicit events-only mode Not additional requests/tools beside equivalent spans; do not reconstruct unknown intervals
Hook records Safe category observations only where present Not required; detailed native features are not activated by this document

Auxiliary span names are local extensions, not GenAI operation values. Omit gen_ai.operation.name on auxiliary timing spans. An events-only source remains an events-only observation set; do not mix its invocation counts with a spans-mode set without proving event-to-span equivalence.

Identity, errors and model fields

FACT: Claude’s table assigns native gen_ai.response.id the same request-header value as request_id. OTel defines its attribute as a completion identifier. This is a documentation-level semantic conflict, not a measured runtime bug. C1, O1

Recommendation: copy the safe transport ID into the adapter extension agent_harness.provider.request.id, not canonical gen_ai.response.id. Leave the latter unavailable unless an actual completion ID is independently exposed through an approved content-off source. Never enable raw API bodies to satisfy the field.

Native field Proposed destination / interpretation Missing or unsafe treatment
session.id gen_ai.conversation.id, genuine safe session identity Omit; do not replace with trace ID, run ID or UUID
tool_use_id / gen_ai.tool.call.id Canonical tool-call ID within producer/session scope Conflict between both values is invalid, not an arbitrary winner
request_id, native gen_ai.response.id agent_harness.provider.request.id, string See conflict above; disagreements stay diagnostic
client_request_id agent_harness.provider.client_request.id, final attempt Earlier attempt IDs belong on their own observations
model, gen_ai.request.model Requested model, after agreement/semantic checks Actual response model stays unavailable unless independently observed
gen_ai.system Candidate mapping to gen_ai.provider.name Legacy name alone does not prove a custom proxy’s upstream; record uncertainty
tool_name_safe, query_source_safe Allowlisted tool/source categories No automatic raw tool_name or query_source fallback
error_class Allowlisted error.type on a known error _OTHER for known failure of unknown class; discard free-form error
success, native span status Separate source outcome and status An UNSET wrapper never upgrades a failed execution child
agent_id, parent_agent_id Safe optional native agent identity extensions Not a second conversation or proof of a captured subagent invocation
interaction.sequence, event.sequence Source ordering within a process instance Never a global session sequence or standalone dedup key

Do not derive repository/PR/tested revision or a deterministic verifier verdict from model text. Bind those through the existing harness/evidence context when available. A tool named Bash or test succeeding does not establish what property was checked; verification-phase tagging requires an identified named check.

Token and clock normalization

FACT: Anthropic Messages API input is split into uncached input, cache read and cache creation; its total is their sum. C2 Claude’s completion-event token field is a final-request footprint, not cumulative subagent usage. C1

Only apply the following arithmetic after confirming that the native usage fields retain the documented API definitions. Components must refer to the same request, be finite nonnegative integers, and be present or explicitly documented as zero.

Native observation Proposed normalized field / rule
input_tokens + cache_read_tokens + cache_creation_tokens gen_ai.usage.input_tokens; all three source components are needed for this mapping
cache_read_tokens gen_ai.usage.cache_read.input_tokens, a subset of normalized input
cache_creation_tokens gen_ai.usage.cache_write.input_tokens, a subset of normalized input
output_tokens gen_ai.usage.output_tokens; do not synthesize a reasoning breakdown
result_tokens on tools Optional tool-output-size diagnostic, never provider input/output billing
attempt agent_harness.request.attempt_count; not a token multiplier
ttft_ms agent_harness.latency.first_token_ms, if valid
first_content_ms Adapter extension agent_harness.latency.first_content_ms; not TTFT or first chunk
Native cost counters/events Advisory observation with basis/source/currency; only attribute to a loop with an explicit join
Cumulative/delta metrics A separate observation population, never added to equivalent spans/events

A canonical input total already includes cache subsets: do not apply the native addition rule a second time to normalized data. Record missing components as unavailable; no guessing from prompt length. Failed-attempt usage may be unexposed, so available usage is only an observed subtotal. Do not multiply final usage by attempt count or treat a final-request footprint as total cost.

Tool-invocation count, execution-attempt count and delivered-job count are distinct. Use the core source-record contract for reimports and conflicts, not an adapter-local key. Preserve deferral/resume causality. Original job/acceptance denominators remain in #288/#296, not this adapter.

Source-record identity mapping

This documents the #314 contract’s application to Claude, not native qualification or a second schema. FACT: The guide scopes event.sequence to process lifetime: it continues across /clear, while a resumed session may repeat sequences in a new process. C1 INFER: Session plus sequence therefore cannot prove original event identity. No content-off native process-lifetime field was qualified in this documentation slice; absence of a qualified mapping is not a claim that no such field can exist.

Inside attributes, retain agent_harness.source.name=claude_code. Use the core’s exact fields below; source.* abbreviates agent_harness.source.* only here.

Source observation source.record.identity and required binding Missingness and conflict treatment
Completed native span native_span; stable originating source.namespace plus genuine trace/span IDs Equal accepted normalized reexports count once; different safe representations at that key conflict
Log with qualified lifetime and original sequence producer_record; originating namespace, genuine source.instance.id, source.record.id=event:<decimal-sequence> Sequence alone, PID/session, or importer UUID is insufficient; missing lifetime means unavailable
Independently numbered event stream producer_record; same lifetime rule, distinct stable stream prefix in record ID The mapping must establish uniqueness across streams, not reuse another stream’s event: prefix
Span event, completion event or partial observation without stable original event identity unavailable, unless a producer-scoped event identity is independently established Associated trace/span/tool/request IDs or a filtered array position cannot identify the event

For the sequence mapping use the exact nonnegative source integer, rendered without leading zeros as event:0, event:1, and so on, within the core’s token bound. Do not use an interaction counter or an ordinal invented after filtering. Namespace and genuine lifetime binding must survive rotation, re-export and reimport; never mint them retroactively to repair an old export. A capture-assigned token is allowed only under the core’s actual-instrumented-boundary rule, not enabled by this note.

A real process restart changes the lifetime scope; /clear alone does not. Arrival order cannot establish a restart, and session resume cannot establish continuity. Different span IDs sharing a tool-call ID remain separate source records; wrapper, execution and event counts still follow the semantic counting surface above. Conflicts and missing keys keep deduplicated totals unavailable under the core rules, not last-writer or success-wins. Even fully identified observed records do not establish complete capture or disjoint billing; unknown usage stays unknown.

Version and content checks, not installation instructions

FACT: The guide dates safe-name/error-class/first-content fields to v2.1.268, effort to v2.1.274, and the assistant-response switch to v2.1.193. Unset assistant capture can inherit prompt capture. Its content limit is UTF-16 code units. C1

Source/configuration to inspect later Adapter rule
app.version; feature availability Qualify each field, not the whole agent from a version string
CLAUDE_CODE_ENABLE_TELEMETRY, CLAUDE_CODE_ENHANCED_TELEMETRY_BETA, OTEL_TRACES_EXPORTER Record enabled capability without changing it; console support is not proof of JSONL output
OTEL_LOG_USER_PROMPTS, OTEL_LOG_ASSISTANT_RESPONSES Verify both off for content-off runs; don’t rely on unset implying independent consent
OTEL_LOG_TOOL_DETAILS, OTEL_LOG_TOOL_CONTENT Keep off; absent content events cannot be interpreted as failed tool calls
OTEL_LOG_RAW_API_BODIES, OTEL_LOG_MANAGED_SETTINGS Exclude bodies, body references, settings and pre-redaction hashes from v0 inputs/outputs
CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH Truncation is not redaction or a UTF-8 byte bound
ENABLE_BETA_TRACING_DETAILED, BETA_TRACING_ENDPOINT Exclude detailed-mode setup from this slice; don’t enable a new endpoint to obtain hook detail
Native user/account/repository attributes Drop unless explicitly safe and needed; do not dump the native resource object

The core contract’s never-secrets rule applies even after opt-in. Filter before persistence or console output; don’t capture first and scrub later. Raw source retention and remote export are separate decisions, not enabled by this mapping. Unsupported source shapes should produce bounded safe diagnostics, never a raw payload/error fallback, a coding-operation failure or a merge veto.

Synthetic mapping controls: NOT RUN

These are authored expected outcomes, not empirical integration results.

Control Expected interpretation
Native uncached input 300, cache read 600, cache creation 100, output 200 Canonical input 1000, output 200, total 1200; cache subsets not added again
Same observation but cache creation absent and no zero-default contract Input total unknown, available components retained; not 900
Two attempt events, only final usage 1200 One logical inference, two observed attempts, total retry spend unknown; not 2400
One wrapper W, permission child P, execution child X, result event E, same call ID One tool invocation, one execution attempt, one separate wait; not four tool calls
Another equal export of W with same originating namespace and native trace/span IDs Duplicate export, no additional invocation
Same source key but conflicting outcome/usage representations Whole key group conflicting; no success-wins or last-writer total
Two log events with equal session/sequence but no qualified process lifetime Two safe observations; deduplicated event total unavailable
New genuine process lifetime repeats sequence 0 in the same resumed session Distinct source event; session/sequence cannot collapse it
New native wrapper W2 for resumed call, same tool-call ID Another observed invocation, not dropped as a duplicate; logical requested-call association retained
Wrapper UNSET, execution ERROR Failed execution remains visible; no overall success inference
Only request-header ID available Custom transport ID populated, canonical completion ID unavailable
Completion event final-request footprint 1200 plus request spans Do not add 1200 again or claim it totals the subagent run
User-selected tool name or unsafe error text without a safe category Omit/category fallback and gap reason, no raw content
Interrupted native record / unsupported beta field Partial event or unavailable observation, never a fabricated completed span

Next bounded work and done-when

Documentation is complete when these tables, the core contract link and controls receive source/semantic review. A later local qualification proposal should name one safe synthetic coding task, actual agent version and configuration, input capture format, content-off destination, and independent named verifier. Compare known native IDs/timing/usage manually against the proposed mapping; include one failed or denied tool and one unavailable signal. Do not manufacture a live 429 or spend a quota to test retry arithmetic. Publish only sanitized/synthetic results.

The next implementation, if separately approved, is a small adapter within the existing harness, not a new runner. It must prove malformed/partial/duplicate input handling and privacy/failure isolation without touching CI policy. Only after a real bounded trial should BENCHMARKS acquire a measured row. Keep later skill packaging under #302. No collector, SCITT, opentelemetry-sdk, workflow changes, SDK install, native activation or UX #281 work belongs here. Verify reds on #295/#296/#298 are not prerequisites for this documentation; its own applicable existing checks still apply.

OVERSOLD: documented tracing or a successful dashboard import proves complete coding-loop coverage, correct code, an accurate subscription allowance or merge readiness. Native qualification remains NOT RUN even after this docs PR merges.

Sources

All accessed 2026-09-23; C1/C2 are living first-party pages, not installed-version proof. O1/O2 are pinned to upstream revision 8ffdf568e1b4391a99adb081db16e8102e36918e.