Skip to content

Reconcile divergent 'run' vs 'trace' (and 'span') terminology across TokenOps / Chronicle / control-plane #113

Open

Description

@susheem-k

Problem

The three repos use inconsistent vocabulary for the same concepts, and the one place they are tied together is about to be deleted (see #55, #60, and the tokenops.server removal work).

Concept TokenOps Chronicle control-plane
Top-level unit of work run (run_id, RunRegistration/Record/State, tokenops_run) trace (trace_id, begin_trace) both, unjoined — runs / run_registrations / ledger_* and traces / envelopes, separate schemas in one SQLite file, no FK
One boundary crossing step (BoundaryStep) / observation (Observation) envelope (Envelope, envelope_id) envelopes row
span_id per agent-hop id, minted new_id("span") in attribution.py — one per agent per run alias for envelope_id — one per crossing (every LLM/tool call) stores envelope_id, indexes parent_envelope_id
parent link parent_span_id / header X-TokenOps-Parent-Span-Id parent_span_id = parent_envelope_id parent_envelope_id
ordering RunState.window list order sequence (int), invocation_index sequence column
custom attributes user_dims, tags, boundary_tags, RunRecord.dims (4 names) dims (flat str→str; trace-level copied onto every span) dims on runs and traces and envelopes; trace search indexes dims.{session_id,message_id,user_id} (Chronicle conventions, not TokenOps')

Divergences that bite

  1. run != trace, coupled by one line. run_id == trace_id is set only in mount_run_registration (src/tokenops/control/http.py): reset_session().begin_trace(reg.run_id). The control-plane's register_run deliberately drops this ("Chronicle session coupling intentionally omitted — agents own their traces"). Envelope.trace_id otherwise defaults to a fresh uuid4(). After tokenops.server / mount_run_registration is removed there is no guaranteed join key between ledger rows (run_id) and envelope rows (trace_id).
  2. "span" spans two granularities. TokenOps span = a whole agent invocation (coarse, header-propagated). Chronicle span = one boundary crossing (fine, = envelope). A TokenOps span contains many Chronicle spans; X-TokenOps-Parent-Span-Id and parent_envelope_id are different trees.
  3. One event, three names: Chronicle Envelope -> tokenops Observation (crossing hook) -> BoundaryStep (Ledger.record, adds price + cum_spent). No doc names the pipeline.
  4. control-plane "one file" doesn't join — store.py and envelope_store.py share no id.

Desired outcome

  • One canonical id that spans the ledger and the envelope stream. Options:
    • A — plane mandates trace_id == run_id at registration (re-add the coupling plane-side). Simplest; blocks a child agent having its own trace.
    • B (preferred) — stamp run_id as a dim on every envelope (Chronicle already propagates trace-level dims down); plane indexes dims.run_id; join = envelopes.dims.run_id -> runs.run_id. Keeps trace_id independent, one extra index.
    • C — never join (rejected; blocks deriving the ledger from the envelope stream).
  • Naming: rename the coarse per-hop id to agent_span / hop_id; reserve "span" for the envelope/OTel level. Collapse the four attribute names where possible. (Overlaps Standardize policy names across code/YAML/UI/docs #53.)
  • Docs: name the Envelope -> Observation -> BoundaryStep pipeline in docs/architecture.md; add a glossary mapping the three vocabularies.

Must not be dropped NOW (cheap, in the decoupling PR)

When mount_run_registration / tokenops.server is removed, preserve the run_id <-> trace binding — whatever registers the run (plane, or SDK on the entry hop) must either call begin_trace(run_id) or stamp run_id into the Chronicle session dims. 1–3 lines; do not merge the removal without it.

Everything else here (schema, indexes, join queries, UI correlation, renames) is an inexpensive later rewrite and does not block the HTTP hard-dependency work.

Related: #55, #60, #53.

Activity

  1. susheem-k commented on Sep 10, 2026

    @susheem-k
    CollaboratorAuthor

    Update from the remote-only planning discussion: TokenOps will drop its own span_id entirely (no minting, no X-TokenOps-Parent-Span-Id header, strip span_id/parent_span_id from SpanContext / Observation / BoundaryStep, stop writing runs.parent_span). It drives no ledger keying and no policy decision — it only ever duplicated Chronicle's envelope chain.

    That removes the "span means two things" half of this issue. "span" becomes unambiguously the Chronicle envelope. What's left here is just: give the run_id-keyed ledger and the trace_id-keyed envelope stream a shared join key — option B: stamp run_id as a dim on every envelope and index it.

    Cross-hop linkage in TokenOps is now X-TokenOps-Run-Id only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationenhancementNew feature or requestgood first issueGood for newcomershacktoberfestApproved for Hacktoberfest 2026

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions