Commit c365119
authored
feat(loop): expose turn_count, final_content, usage, request_metadata in result (#136)
The conversation loop's result contract previously surfaced only
`messages`, `tool_execution_results`, `events`, and the optional
`status`/`budget` pair. Consumers building real agents on top of the
substrate need four additional pieces of information:
* `turn_count` — how many turns actually executed
* `final_content` — text of the last assistant message
* `usage` — accumulated token counts across all turns
* `request_metadata` — most recent turn's provider request descriptor
The substrate already had access to all four — `turn_count` lives in
the loop counter, `final_content` is computable from `messages`, and
turn runners were already returning `usage` and `request_metadata` per
turn (the latter was even tested for passthrough). They just weren't
formalized as part of the result contract.
Without these fields, consumers like data-machine resort to passing
mutable state by reference through their turn runner closure so the
outer caller can read accumulators after the loop returns:
$turn_runner = build_turn_runner(
...
&$last_request_metadata,
&$total_usage,
&$turns_run,
&$final_content,
);
WP_Agent_Conversation_Loop::run( $messages, $turn_runner, $options );
// Now read the by-reference accumulators back into the result.
This is gross and a sign the substrate is missing universal-consumer
observability. Other future consumers will hit the same gap.
## What changes
* The loop accumulates `turn_count` (existing internal counter, just
surfaced now), `final_content` (extracted from messages at result
time), `total_usage` (summed from each turn runner's optional
`usage` field), and `request_metadata` (overwritten by each turn
runner's optional `request_metadata` field).
* `WP_Agent_Conversation_Result::normalize()` validates the four new
fields when present (int, string, array, array respectively).
* Two new private helpers: `accumulate_usage()` sums numeric token
counts while preserving provider-specific extras like
`cache_creation_input_tokens` or `reasoning_tokens`;
`extract_final_content()` walks messages backward for the last
non-empty assistant text content.
## Compatibility
All four fields are **additive** to the result shape — existing
consumers reading `messages`/`tool_execution_results`/`status`/`budget`
continue to work unchanged.
The turn-runner contract gains two optional return fields (`usage`,
`request_metadata`); turn runners that don't return them get the empty
default. Turn runners that already return `request_metadata` (the
existing `conversation-loop-transcript-persister-smoke` test does this)
continue to round-trip correctly.
## Verification
All 30 substrate smoke tests pass:
* conversation-loop-smoke (6 assertions)
* conversation-loop-tool-execution-smoke (19)
* conversation-loop-completion-policy-smoke (6)
* conversation-loop-events-smoke (17)
* conversation-loop-budgets-smoke (24)
* conversation-loop-transcript-persister-smoke (18 — including the
pre-existing request_metadata round-trip test)
* conversation-runner-contracts-smoke (18)
* iteration-budget-smoke (22)
* plus the broader substrate suites (compaction, identity, guidelines,
workflows, routines, subagents, etc.)
Closes #1351 parent 2cf47ae commit c365119
2 files changed
Lines changed: 103 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
89 | 89 | | |
90 | 90 | | |
91 | 91 | | |
92 | | - | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
93 | 105 | | |
94 | 106 | | |
95 | 107 | | |
| |||
109 | 121 | | |
110 | 122 | | |
111 | 123 | | |
| 124 | + | |
112 | 125 | | |
113 | 126 | | |
114 | 127 | | |
| |||
157 | 170 | | |
158 | 171 | | |
159 | 172 | | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
160 | 185 | | |
161 | 186 | | |
162 | 187 | | |
| |||
237 | 262 | | |
238 | 263 | | |
239 | 264 | | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
240 | 269 | | |
241 | 270 | | |
242 | | - | |
243 | | - | |
244 | | - | |
245 | | - | |
246 | 271 | | |
247 | 272 | | |
248 | 273 | | |
| |||
253 | 278 | | |
254 | 279 | | |
255 | 280 | | |
256 | | - | |
| 281 | + | |
257 | 282 | | |
258 | 283 | | |
259 | 284 | | |
| |||
752 | 777 | | |
753 | 778 | | |
754 | 779 | | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
755 | 835 | | |
756 | 836 | | |
757 | 837 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
100 | 117 | | |
101 | 118 | | |
102 | 119 | | |
| |||
0 commit comments