Skip to content

Align Item, event, reasoning and usage serialization - #77

Merged
SaladDay merged 11 commits into
mainfrom
codex/item-serialization
Sep 23, 2026
Merged

SaladDay merged 11 commits into
mainfrom
codex/item-serialization

Conversation

@SaladDay

@SaladDay SaladDay commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Items, events, reasoning and usage now carry the official field presence and sequencing where the pinned types define them. Model output text is never altered and no model-derived defaults or counters are invented. The pinned baseline is unchanged (SDK 3.13.0 / d7c41ef / agents=v1).

Behavior

  • Null field presence (S1–S3):

    • item.added/item.done carry output_index: null when there is no index.
    • Message Items always carry phase: null for user messages, the native value or null for assistant messages.
    • function_call_output always carries error (null when none) and output.

    These are applied at rendering. Stored Item payloads keep their previous encoding (Item.MarshalStored), so replayed child Items and creation-retry hashes are unchanged.

  • Assistant message framing (S4/S5):

    • item.added is in_progress with content: [], then content_part.added with text "", then deltas, output_text.done, content_part.done and item.done.
    • A message that the native harness delivers complete, such as Claude structured output, uses the same sequence with the exact final text in one delta.
    • Stored Items are unchanged.
  • Reasoning (S6): Session and saved Agent responses serialize {effort, summary} with explicit nulls instead of {}. No model default is filled in (official reports model-specific values; that remains a documented difference).

  • Session usage (S7): public usage is the sum of root-Turn usage only when every root Turn has ended with recorded usage. Otherwise it is null, including while any root Turn is queued, running or waiting. This follows EVT-13 and the scan-5 ST-03 official samples.

  • Core observability is unaffected by S7:

    • Runtime history token points use a separate tenant-scoped query that keeps summing measured usage continuously. This is a documented Core extension.
    • The Core Web dashboard summary total holds each Session's last reported value while public usage is null. The live trend shows those intervals as gaps, never zero, and spreads the rate over the elapsed time once usage is reported again.
  • Cancelled Codex Turns (S8):

    • The all-zero usage came from the Codex adapter. It published a zero difference between two unchanged native thread totals as a real measurement.
    • An unchanged total now leaves the Turn's usage unknown (null).
  • Client and Core Web: accept the new nulls and still accept older Cores that omit them.

Evidence

  • EVT-09, EVT-10 and EVT-13 (campaign scan 2, raw official frames).
  • SES-23 and SES-25 (scan 1).
  • VA-11.
  • ST-03 (scan 5, 3 official samples).
  • The live-kit observation EVT-24.

Recorded in history-events-usage.md, execution-tools.md, runtime-history-api.md and operation-evidence.md (register SR, rows 7, 12, 15).

Validation

  • Live acceptance: real Core, the daemon and native harnesses on environment: none. Every image, the Claude bridge included, was built from each tree. 5 Turns per phase. All events and reads were validated with the pinned SDK's strict parse.

    Row Codex + Kimi K3 Claude SDK + Kimi K3 MiniMax Code + M2.7
    S1 output_index: null FAIL → PASS FAIL → PASS FAIL → PASS
    S2 phase present FAIL → PASS FAIL → PASS FAIL → PASS
    S3 error/output present FAIL → PASS – –
    S4 framing FAIL → PASS – FAIL → PASS
    S5 one byte-identical delta – FAIL → PASS –
    S6 reasoning nulls FAIL → PASS – –
    S7 Session usage (null in all 212 samples during Turns, sum after) FAIL → PASS PASS → PASS PASS → PASS
    S8 cancelled Turn usage FAIL (zeros) → PASS (null) – –

    Cleanup and secret scans passed.

  • Tests:

    • Contract, store (real PostgreSQL), API, Items, runtime observation and runtime history tests.
    • Codex adapter tests with -race, including a reproduction of the zero-usage cancel.
    • TS client and Core Web tests.
    • The pinned-SDK official client suite.
  • Server gate on this head: all make check targets plus Web typecheck, core-doctor, unit tests and build pass. The Playwright browser cases were not run on the server (no Google Chrome; the user approved the skip). make openapi and make sqlc-generate are byte-identical.

  • Independent blind review by a fresh Claude Code subagent (the user-approved replacement for GPT-6 Astra). It found no blockers and confirmed in code: stored Item payloads and creation-retry hashes are unchanged; the Claude structured-output path produces exactly one byte-identical delta; S7 counts only root Turns; every public caller uses the public rule; and the telemetry query is tenant-scoped. Its follow-ups are fixed in the last two commits:

    • The Core Web live trend now shows gaps, not zero, while usage is held, and spreads the rate over the elapsed time when usage returns.
    • Stale observability and sqlc docs are corrected.
    • The docs record that a root Turn cancelled while queued keeps public Session usage null, an inference with no official sample.

    These were verified by Web tests and the gate on the final head, without a new review, per the user's rule.

No full protocol compatibility is claimed.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.

Official responses always carry these fields (EVT-09, SES-23, SES-25, VA-11):
Item events carry output_index, null for input Items; messages carry phase,
null for user messages and when the harness reports none; function results
carry output and error, null when not submitted; Agent and Session responses
carry reasoning.effort and reasoning.summary, null when unset. No
model-derived default effort is filled in.

These are wire-only. Stored Item payloads keep their original encoding through
Item.MarshalStored, so replayed child Items still compare equal, and stored
configuration keeps omitting unset reasoning keys, so creation retry identity
is unchanged. The OpenAPI marks phase and output_index nullable.

The TypeScript client accepts output_index and phase null and still accepts
older Cores that omit them; Core Web accepts a null message phase. The pinned
SDK scripts now expect the explicit nulls.
Official streams add an assistant message in progress with empty content,
then an empty content part, deltas, output_text.done, content_part.done and
item.done, and stream structured output the same way (EVT-10). Core no longer
pre-fills the part in item.added. A message first observed complete, such as
Claude structured output or a legacy final answer, now follows the same
sequence with its unchanged text in exactly one delta; this frames the text
and never alters it. Stored Items and reads are unchanged.
Official Session usage stayed null after a root Turn ended with unknown usage
(EVT-13), and in three more samples while a root Turn was in progress or
waiting, even after earlier Turns were measured (ST-03). Session usage is now
the sum of recorded root Turn usage only when every root Turn has ended with
known usage, and null otherwise. Queued Turns count as not ended; no official
read sampled one. A snapshot an active Turn records stays on that Turn.
A cancelled Codex Turn read all-zero usage where official keeps it null
(EVT-24). Native reports a cumulative thread total and the adapter publishes
its difference from the Turn's baseline. After an interrupt before any
response reported usage, native sent a total that had not advanced, and the
adapter published the all-zero difference as a complete measurement, which
the cancellation outcome repeated. Core and storage only stored it.

The adapter now ignores a total that has not advanced past the Turn's
baseline. A later advanced total is still published, and an explicit native
zero in a per-Turn usage payload is still kept.
Add the dated item serialization section (S1-S8) with its official evidence
(EVT-09, EVT-10, EVT-13, SES-23, SES-25, VA-11, ST-03) and the EVT-24 finding,
register SR in the operation evidence with updated rows 7, 12 and 15, and
update the execution tools matrix, the deferral notes, the Session usage and
runtime history token descriptions and the contributor rules for wire nulls,
message framing and usage.
Public Session usage now stays null while a root Turn runs and after an
unmeasured Turn. Runtime observation telemetry, history token points and
their OTLP export are Core extensions and should not lose that signal.

Add a separate tenant-scoped internal query with the previous rule: the sum
of every recorded root Turn snapshot, active Turns included, null only when
nothing is recorded. The Runtime observation resolver reads it instead of
the public value. Session retrieve, list, update and every Session event
snapshot keep the public rule.
The live trend reads public Session usage, which is now null while a root
Turn runs. Keep a listed Session's last reported totals instead of dropping
it, so the series neither falls to zero nor breaks; usage measured
meanwhile appears in the interval where it is reported again. Durable
history is limited to hosted Sessions, so the live trend keeps its source.
Runtime history token points and telemetry count measured usage
continuously and differ from public Session usage by design. Record the
Core Web live trend behavior and which consumers use which rule.
The Runtime summary's reported token total summed public Session usage, so
it dropped while a Turn withheld usage. Share the live trend's hold as
holdLastReported: the summary keeps each listed Session's last reported
total across snapshots, a newly reported value replaces it, and unlisted
Sessions drop out. Rows still show the current public value.
The live trend re-stamped a held Session total with each sample time, so a
running Turn read as 0/min and its whole consumption landed as one spike.
Held totals now keep their report time and are marked held; tokenRate
treats them as unknown, so those intervals are gaps, never zero. The next
reported total is spread over the time since the last report. The Runtime
summary keeps its held total.
The resolver and the OTLP token gauges read MeasuredSessionUsage, sqlc also
adds SessionMeasuredTokenUsage, and held live-trend intervals are gaps. Note
that a root Turn cancelled while queued leaves public Session usage null, an
inference with no official sample in ST-03.
@SaladDay
SaladDay merged commit 67a5b48 into main Sep 23, 2026
2 of 3 checks passed
@SaladDay
SaladDay deleted the codex/item-serialization branch October 7, 2026 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant