Repository navigation
feat(stats): bill Codex active time from real per-turn intervals + model compute - #40
Merged
Merged
Conversation
…del compute The Codex import adapter derived active time the same way as the Claude adapter: gap-capped point timestamps (the "engaged-turns" method). But a Codex rollout records explicit per-turn boundaries -- task_started / task_complete, paired by turn_id, with a reported duration_ms -- so the in-turn duration can be the log's real wall-clock span instead of a gap-capped approximation. Active time for Codex sessions is now the union of the real per-turn intervals (in-turn, uncapped, and crediting the session's final turn) and the gap-capped engagement series (between-turn bridging). The active-time SEMANTICS are unchanged: still human-engaged time with idle gaps over 5 minutes excluded; only the in-turn precision improves, so a re-imported Codex session's active time typically rises (long turns are no longer truncated at the gap cap, and the final turn is counted). active_time_method is "turn-intervals" when any turn interval contributed, else "engaged-turns". Claude imports are unchanged: their transcripts carry no per-turn duration. Adds session.metrics.machine_active_time_ms (additive, optional): model compute time = the summed per-turn duration, a subset of a session's active time. It is a plain sum, NOT wall-clock-deduplicated, so two concurrent sessions can sum past their billable (union) active wall-clock: intended, as two models working at once did two machine-hours in one wall-clock hour. Surfaced as a "Model working" line in stats (and --by-source / --by-day / --json), a "machine" segment on session show's Work line, and the view Stats tab. Re-import (--force, or refresh) to backfill existing Codex sessions. Robustness: - De-duplicate task_complete by turn_id so a repeated completion cannot double-count machine time and break the machine <= active subset. - Emit machine time only when every completed turn carried a duration; a session that mixes duration-bearing and duration-less completions would otherwise report a partial compute total as if it were complete. - Clamp a turn start reconstructed from duration (when task_started is absent) to the session floor, so no active interval predates the session, and bound each turn's machine contribution to its in-session span so a clamped turn stays a subset of active time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
takashi-matsuyama
force-pushed
the
feat/billing-grade-v2-codex-turns
branch
from
June 4, 2026 02:03
e857441 to
e7525eb
Compare
This was referenced Jun 4, 2026
takashi-matsuyama
added a commit
that referenced
this pull request
Jun 4, 2026
…s) (#43) Promote the Unreleased section to 0.6.0 and bump all four package manifests (core / cli / sdk / basou) to 0.6.0 in lockstep. Highlights since 0.5.0: - billing-grade work-measurement v2: Codex active time from real per-turn intervals, plus a machine_active_time_ms model-compute measure (#40). - @basou/sdk v0.2: a runtime, read-only programmatic API over @basou/core's readers for reading .basou/ provenance (#41). - @basou/core JSON Schema artifacts for the on-disk formats, generated from the Zod schemas and shipped under ./schemas/* (#42). Also: document the four-package lockstep bump in CONTRIBUTING, and record the 0.6.0 pre-publish dry-run tarball contents in the release checklist (core now ships its schemas/, cli ships two entry points). This commit only prepares the release; tagging v0.6.0 and the OIDC publish remain maintainer actions. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Billing-grade work-measurement v2: improve the fidelity of Codex active time and add a model-compute measure. Builds directly on the
basou statswork shipped in 0.5.0.The Codex import adapter previously derived active time the same way as the Claude adapter — gap-capped point timestamps (the
engaged-turnsmethod). But a Codex rollout records explicit per-turn boundaries (task_started/task_complete, paired byturn_id, with a reportedduration_ms), so the in-turn duration can be the log's real wall-clock span instead of a gap-capped approximation.What changed
active_time_methodbecomesturn-intervalswhen any turn interval contributed, elseengaged-turns.session.metrics.machine_active_time_ms(additive, optional): model compute time = summed per-turn duration. A subset of a session's active time. A plain sum, not wall-clock-deduplicated, so two concurrent sessions can sum past their billable (union) active wall-clock — intended (two models working at once is two machine-hours in one wall-clock hour). Surfaced as aModel workingline inbasou stats(and--by-source/--by-day/--json), amachinesegment onbasou session show'sWork:line, and thebasou viewStats tab. Re-import (--force, orbasou refresh) to backfill.Robustness (from adversarial review)
task_completebyturn_idso a repeated completion cannot double-count machine time and break themachine <= activesubset.machine_active_time_msonly when every completed turn carried a duration; a session mixing duration-bearing and duration-less completions would otherwise report a partial compute total as if complete.duration_ms(whentask_startedis absent) to the session floor, so no active interval predates the session, and bound each turn's machine contribution to its in-session span so a clamped turn stays a subset of active time.Compatibility
machine_active_time_msand theturn-intervalsmethod are additive and optional; no schema_version bump. Existing imported sessions are unaffected until re-imported (consistent with howactive_*was introduced).Test plan
pnpm -r build && pnpm typecheck && pnpm test && pnpm lintall green (707 core + 417 cli + 1 sdk; lint clean apart from pre-existing unrelated warnings).turn_idde-dup, partial-duration omission, reconstructed-start clamping, schema validation, work-stats roll-up (per-session / by-source / by-day / totals), and the new rendered lines instats/session show.machine <= activeholds for every session, no active interval precedesstarted_at, and older rollouts withoutduration_mscorrectly omit machine time while still deriving turn-interval active time.🤖 Generated with Claude Code