Skip to content

fix: cache session usage and clarify turn field scopes - #173

Merged
joshrotenberg merged 4 commits into
mainfrom
feat/150-typed-turn-results
Nov 2, 2026
Merged

joshrotenberg merged 4 commits into
mainfrom
feat/150-typed-turn-results

Conversation

@joshrotenberg

@joshrotenberg joshrotenberg commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #171, which was already merged when these review changes were applied. This branch includes current main, including #170, and the diff is limited to the six follow-up files.

  • Document precise Turn field scopes without changing the original outcome rule. Status, error and terminal_event cover the entire event stream; final_message and usage reset at each turn.started. Completion, failure and top-level errors followed by a new start now have explicit regression tests.
  • Cache a running usage_total from the existing projection when Session.send/3 records a turn. Session.total_usage/1 and SessionServer.total_usage/1 read it in constant time without parsing historical stdout.
  • Preserve unknown counters versus explicit zero, raw Result history, and exclusion of rejected nonzero exits and thread mismatches. Tests record actual sends, exercise repeated reads after raw-output redaction, and verify subsequent accumulation and failed-call state preservation.
  • Leave the Structured line unchanged: mix format did not alter it.

Compatibility

Structured.decode/2 intentionally changes error precedence for existing callers. Given turn.completed followed by a top-level error with no later terminal event, it returns {:error, {:turn_failed, detail}} at both zero and nonzero process exits. Previously, a zero exit could decode successfully, while a nonzero exit returned {:error, {:process_failed, detail}}. This follows the documented event-order rule and issue #150. A later terminal event still supersedes an earlier error. The follow-up commit message also records this compatibility change.

For example, turn.started, turn.completed, turn.started still yields status :completed and the earlier terminal_event, but nil final_message and usage. Status reports the last terminal outcome in the stream, not completion of the last started turn. The module and README now describe this distinction explicitly.

Validation

Before the first follow-up push, both runtime matrices passed with 734 tests each. After syncing with current main, repeated every local CI check before the final push:

Runtime Result
Elixir 1.19.7 / OTP 27.3.4.19 in Docker 765 passed, 0 failures, 4 integration exclusions
Elixir 1.20.4 / OTP 29.1.2 on macOS 765 passed, 0 failures, 4 integration exclusions

Both runtimes passed mix deps.get, mix deps.unlock --check-unused, mix hex.audit, mix compile --warnings-as-errors, mix format --check-formatted, mix credo --strict, mix dialyzer, mix test, and mix docs --warnings-as-errors.

With Node 22.23.4, mix codex.contract passed for CLI 0.149.0, 0.164.0, and latest 0.166.0. The 95 focused projection, structured, session and evidence tests passed before syncing main. git diff --check passed. No live provider inference was invoked; the repository's four opt-in integration tests remain excluded. Release publication steps are main-only side effects and were not run.

Final GitHub CI passed all five jobs on 00e9d57adaa2af8abe0b6aacf5500aa492fd921b: both Elixir/OTP jobs, Docs, and both pinned CLI contracts. CI run.

Accumulate usage when Session.send/3 records a turn so repeated Session
and SessionServer reads do not parse retained stdout. Preserve unknown
values, explicit zero, and exclusion of rejected turns.

Document that status, error and terminal_event cover the event stream,
while final_message and usage reset at turn.started. Test completion,
failure and top-level errors followed by a new turn start.

This PR changes Structured.decode/2 error precedence: turn.completed
followed by error now returns {:turn_failed, detail}. Previously a zero
exit could decode successfully and a nonzero exit returned
{:process_failed, detail}. A later terminal still supersedes an earlier
error.
@joshrotenberg
joshrotenberg marked this pull request as ready for review November 1, 2026 14:16
@joshrotenberg
joshrotenberg merged commit 49273c4 into main Nov 2, 2026
5 checks passed
@joshrotenberg
joshrotenberg deleted the feat/150-typed-turn-results branch November 2, 2026 23:09
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