feat(core): emit more turn items instead of legacy begin/end events#30283
feat(core): emit more turn items instead of legacy begin/end events#30283owenlin0 wants to merge 6 commits into
Conversation
35c6ad7 to
ee54284
Compare
d581108 to
e9d6a09
Compare
883f6f7 to
2d0b556
Compare
acec956 to
f691f27
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f691f279c3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
4304fa3 to
d2d5882
Compare
9fd1ae2 to
9f2d003
Compare
d3204c7 to
3041d21
Compare
## Description
This PR adds canonical core `TurnItem` shapes for command execution,
dynamic tool calls, collab agent tool calls, and sub-agent activity, to
be stored in the rollout file soon.
It also teaches app-server protocol / `ThreadHistoryBuilder` how to
render those items, and adds the small legacy fanout helpers needed for
existing event-based consumers. No core producer or rollout persistence
behavior changes here, that will be done in a followup.
## Making ThreadHistoryBuilder stateless
This is the first PR in a stack to make `ThreadHistoryBuilder` stateless
enough that we can materialize app-server `ThreadItem`s from only a
given slice of `RolloutItem` history, without ever needing to replay the
whole thread from the beginning.
The persisted legacy `RolloutItem::EventMsg` records are mostly shaped
like live UI events, not like materialized `ThreadItem`s. They work if
we replay the full rollout in order, but they often do not contain
enough stable identity or complete item state to project an arbitrary
suffix on its own.
A few examples:
- `UserMessageEvent` and `AgentMessageEvent` have content, but
historically do not carry the persisted app-server item ID that should
become the SQLite primary key.
- `AgentReasoningEvent` and `AgentReasoningRawContentEvent` are
fragments. `ThreadHistoryBuilder` currently merges them into the last
reasoning item, which means a slice starting in the middle of reasoning
cannot know whether to append to an earlier item or create a new one.
- `WebSearchEndEvent`, `McpToolCallEndEvent`, collab end events, and
similar legacy events can often render a final-looking item, but they
usually rely on prior replay state to know which turn owns the item.
- Begin/end legacy events are partial views of one logical item. The
builder correlates them by `call_id` and mutates prior state to
synthesize the final `ThreadItem`.
That is the problem this direction fixes. A persisted canonical
lifecycle record looks much closer to the read model we actually want
later:
```rust
ItemCompletedEvent {
turn_id,
item: TurnItem { id, ...full snapshot... },
completed_at_ms,
}
```
Once rollout has explicit `turn_id`, stable `item.id`, and a canonical
completed item snapshot, the future SQLite projector can reduce only the
new rollout suffix and upsert the affected `thread_items` rows. It no
longer needs to synthesize `item-N`, infer item ownership from the
active turn, or replay earlier events just to reconstruct the current
item snapshot.
## What changed
- Added core `TurnItem` variants and item structs for command execution,
dynamic tool calls, collab agent tool calls, and sub-agent activity.
- Added conversions from those canonical items back into the legacy
event shapes where current consumers still need them.
- Added app-server v2 `ThreadItem` conversion for the new core item
variants.
- Taught `ThreadHistoryBuilder` and rollout persistence metrics to
recognize the new item variants.
## Follow-up
The next PR #30283 switches the live
core producers for these item families onto canonical `ItemStarted` /
`ItemCompleted` events.
3041d21 to
5d4da6f
Compare
3a6d87b to
0c3b191
Compare
|
I would make the acceptance boundary the canonical item lifecycle, not the event-name migration. The important property is that every tool or runtime activity that becomes visible to the app-server has one durable item identity and one completion boundary. If core emits ItemStarted and ItemCompleted as the live source of truth, then command execution, dynamic tool calls, collab agent calls, and sub-agent activity should all converge through the same item lifecycle before the app-server renders or resumes dependent state. The regression shape I would want is:
That gives the stack a useful invariant: live clients consume canonical item lifecycle, while legacy event conversion is a projection for old consumers, not a parallel authority path. The migration is then safe because there is only one live item record that owns visibility, completion, and downstream resume semantics. Boundary: architecture and regression-test feedback only; no claim about using this project, running this branch, validating implementation behavior, implementation correctness, performance measurements, merge readiness, security review, production readiness, partnership, customer interest, official alignment, OpenAI usage, Codex usage, conformance certification, or Neura usage. |
Description
This PR makes canonical
TurnItemlifecycle the live source of truth for these items:These are new TurnItems added in #30282, which only previously had the legacy-style
EventMsg::*Begin/Endevents. This PR switches core to emit these asItemStarted/ItemCompleted+TurnIteminstead, and app-server v2 handles those canonical item events directly.Core still fans canonical item events back out into the old legacy
EventMsgforms where we still need them for legacy rollout persistence, rollout trace, and raw core event consumers. App-server ignores that compatibility fanout so v2 clients do not get duplicate item notifications.Rollout persistence behavior is unchanged in this PR.
Also, I moved all the legacy conversion code to a new module
protocol/src/legacy_events.rsinstead of being split across item and event schema modules.Why
This is the second PR in the stack for making ThreadHistoryBuilder stateless, which relies on using
TurnItem. First PR is at: #30282Follow-up
The next PR #30188 actually writes
TurnItemto rollout files when usingthread.history_mode = "paginated".