From 9b13ca2026c720d9055075828594f4f443750b31 Mon Sep 17 00:00:00 2001 From: rohans0509 Date: Tue, 14 Jul 2026 00:37:56 -0700 Subject: [PATCH 1/2] fix(slice-140-review): preserve complete Yoke usage --- almanac/reference/harness-event-shape.md | 14 +++++ docs/plans/fixes-slice-140-review.md | 62 +++++++++++++++++++ pyproject.toml | 3 + .../integrations/harnesses/yoke/events.py | 3 + src/codealmanac/services/harnesses/events.py | 2 + tests/test_runs_service.py | 15 ++++- tests/test_yoke_harness_integration.py | 28 +++++++++ uv.lock | 8 +-- 8 files changed, 126 insertions(+), 9 deletions(-) create mode 100644 docs/plans/fixes-slice-140-review.md diff --git a/almanac/reference/harness-event-shape.md b/almanac/reference/harness-event-shape.md index 0cba6848..f4af97fc 100644 --- a/almanac/reference/harness-event-shape.md +++ b/almanac/reference/harness-event-shape.md @@ -11,6 +11,12 @@ sources: - id: harness-actors type: file path: src/codealmanac/services/harnesses/actors.py + - id: yoke-events + type: file + path: src/codealmanac/integrations/harnesses/yoke/events.py + - id: yoke-usage-contract + type: web + url: https://github.com/AlmanacCode/Yoke/blob/7ed04b57cbd17543f2f0576e850368f0d5fdf2a1/docs/notes/0272-usage-and-generation-control-audit.md --- # Harness Event Shape @@ -95,6 +101,7 @@ Optional text fields are validated when present. Empty strings are rejected for | Field | |---| | `input_tokens` | +| `cache_creation_input_tokens` | | `cached_input_tokens` | | `output_tokens` | | `reasoning_output_tokens` | @@ -104,6 +111,13 @@ Optional text fields are validated when present. Empty strings are rejected for Every usage count must be non-negative when present [@harness-events]. +CodeAlmanac preserves Yoke's normalized counters without recomputing them. +Cache creation and cache reads are separate counters for providers such as +Claude; cached input and reasoning output may instead be subsets of broader +input and output counters for providers such as Codex. Consumers must use the +provider-normalized totals rather than summing every field indiscriminately +[@yoke-events] [@yoke-usage-contract]. + ## Failures `HarnessFailure` carries structured failure information for an event. It records the provider kind, required message, optional fix, optional code, optional raw text, and optional JSON-compatible details [@harness-events]. diff --git a/docs/plans/fixes-slice-140-review.md b/docs/plans/fixes-slice-140-review.md new file mode 100644 index 00000000..693b64f9 --- /dev/null +++ b/docs/plans/fixes-slice-140-review.md @@ -0,0 +1,62 @@ +# Fixes: Slice 140 Yoke usage projection review + +## Scope + +- Preserve Yoke's `cache_creation_input_tokens` in CodeAlmanac's durable + `HarnessUsage` projection. +- Validate the field as a non-negative token count alongside the existing usage + fields. +- Prove the Claude-shaped Yoke usage event survives projection and SQLite + run-event persistence unchanged. + +## Out of scope + +- Recomputing provider totals or aggregating cumulative usage snapshots. +- Changing lifecycle execution, run-event storage, or user-facing rendering. +- Reimplementing Yoke's Claude or Codex usage semantics in CodeAlmanac. + +## Design + +`HarnessUsage` remains the service-owned persistence contract. The Yoke adapter +projects the additional normalized field directly; `RunEventStore` continues to +serialize the complete nested model without field-specific storage logic. + +This follows the existing boundary: “Raw external shapes don't leak past the +normalization boundary” (`MANUAL.md`) and keeps persistence behind the existing +repository abstraction rather than adding provider-aware database behavior +(`docs/reference/cosmic-python/chapter_02_repository.md`). + +## Files + +- `src/codealmanac/services/harnesses/events.py` +- `src/codealmanac/integrations/harnesses/yoke/events.py` +- `tests/test_yoke_harness_integration.py` +- `tests/test_runs_service.py` +- `almanac/reference/harness-event-shape.md` +- `pyproject.toml` and `uv.lock` pin the corrected Yoke source revision for + development while leaving published package metadata on the compatible + `>=0.1.7,<0.2` requirement. + +## Release note + +The next CodeAlmanac release must either retain the exact uv source override or +depend on a published Yoke release containing revision +`7ed04b57cbd17543f2f0576e850368f0d5fdf2a1`. Removing the override while PyPI +still resolves the older 0.1.7 artifact would make the typed projection +unexecutable. + +## Verification + +- Focused projection, validation, and run-event round-trip tests. +- Full `uv run pytest`. +- Full `uv run ruff check .`. +- Full `uv run pyright` if configured by this repository. + +## Read before coding + +- `MANUAL.md` +- `docs/python-port-live-agreement.md` +- `docs/plans/slice-140-yoke-runtime-integration.md` +- `almanac/architecture/agent-runs/harness-contract.md` +- `almanac/architecture/agent-runs/provider-adapters.md` +- `docs/reference/cosmic-python/chapter_02_repository.md` diff --git a/pyproject.toml b/pyproject.toml index c22ef31a..d9e0861c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,3 +82,6 @@ codealmanac = [ [tool.uv] package = true + +[tool.uv.sources] +almanac-yoke = { git = "https://github.com/AlmanacCode/Yoke.git", rev = "7ed04b57cbd17543f2f0576e850368f0d5fdf2a1" } diff --git a/src/codealmanac/integrations/harnesses/yoke/events.py b/src/codealmanac/integrations/harnesses/yoke/events.py index b10eb7b8..191bc353 100644 --- a/src/codealmanac/integrations/harnesses/yoke/events.py +++ b/src/codealmanac/integrations/harnesses/yoke/events.py @@ -127,6 +127,9 @@ def base_event(self, event: Event, actor: HarnessRunActor) -> HarnessEvent: usage=( HarnessUsage( input_tokens=usage.input_tokens, + cache_creation_input_tokens=( + usage.cache_creation_input_tokens + ), cached_input_tokens=usage.cached_input_tokens, output_tokens=usage.output_tokens, reasoning_output_tokens=usage.reasoning_output_tokens, diff --git a/src/codealmanac/services/harnesses/events.py b/src/codealmanac/services/harnesses/events.py index 02029692..fe7d32b4 100644 --- a/src/codealmanac/services/harnesses/events.py +++ b/src/codealmanac/services/harnesses/events.py @@ -56,6 +56,7 @@ class HarnessToolStatus(StrEnum): class HarnessUsage(CodeAlmanacModel): input_tokens: int | None = None + cache_creation_input_tokens: int | None = None cached_input_tokens: int | None = None output_tokens: int | None = None reasoning_output_tokens: int | None = None @@ -65,6 +66,7 @@ class HarnessUsage(CodeAlmanacModel): @field_validator( "input_tokens", + "cache_creation_input_tokens", "cached_input_tokens", "output_tokens", "reasoning_output_tokens", diff --git a/tests/test_runs_service.py b/tests/test_runs_service.py index 48c3588f..16cb8a27 100644 --- a/tests/test_runs_service.py +++ b/tests/test_runs_service.py @@ -14,6 +14,7 @@ HarnessEventKind, HarnessKind, HarnessTranscriptRef, + HarnessUsage, ) from codealmanac.services.runs.models import ( RunEventKind, @@ -75,11 +76,12 @@ def test_runs_service_records_run_and_events( RecordRunEventRequest( run_id=record.run_id, kind=RunEventKind.TOOL, - message="codex provider session provider-thread-1", + message="usage: 7691 tokens", harness_event=HarnessEvent( - kind=HarnessEventKind.PROVIDER_SESSION, - message="codex provider session provider-thread-1", + kind=HarnessEventKind.CONTEXT_USAGE, + message="usage: 7691 tokens", provider_session_id="provider-thread-1", + usage=HarnessUsage(cache_creation_input_tokens=7_611), ), ) ) @@ -112,6 +114,8 @@ def test_runs_service_records_run_and_events( assert event.sequence == 3 assert harness_log.harness_event is not None assert harness_log.harness_event.provider_session_id == "provider-thread-1" + assert harness_log.harness_event.usage is not None + assert harness_log.harness_event.usage.cache_creation_input_tokens == 7_611 assert attached.harness_transcript == transcript assert finished.status == RunStatus.DONE assert finished.summary == "updated wiki" @@ -126,6 +130,11 @@ def test_runs_service_records_run_and_events( ) +def test_harness_usage_rejects_negative_cache_creation_tokens() -> None: + with pytest.raises(ValidationError, match="must be non-negative"): + HarnessUsage(cache_creation_input_tokens=-1) + + def test_runs_service_filters_by_registered_repository_name( tmp_path: Path, isolated_home: Path, diff --git a/tests/test_yoke_harness_integration.py b/tests/test_yoke_harness_integration.py index f861363d..e244dc22 100644 --- a/tests/test_yoke_harness_integration.py +++ b/tests/test_yoke_harness_integration.py @@ -11,6 +11,7 @@ Readiness, Run, RunStatus, + Usage, ) from codealmanac.agents.catalog import agent_collection, load_agent @@ -214,6 +215,33 @@ def test_unknown_yoke_event_projects_to_unknown_and_is_json_safe(): assert projected.tool_result == [None, None] +def test_claude_usage_projection_preserves_cache_creation_tokens(): + [projected] = YokeEventProjector(HarnessKind.CLAUDE).project( + Event( + kind=EventKind.CONTEXT_USAGE, + message="usage: 7691 tokens", + usage=Usage( + input_tokens=2, + cache_creation_input_tokens=7_611, + cached_input_tokens=0, + output_tokens=78, + total_tokens=7_691, + total_processed_tokens=7_691, + ), + ) + ) + + assert projected.usage is not None + assert projected.usage.model_dump(exclude_none=True) == { + "input_tokens": 2, + "cache_creation_input_tokens": 7_611, + "cached_input_tokens": 0, + "output_tokens": 78, + "total_tokens": 7_691, + "total_processed_tokens": 7_691, + } + + def test_codex_agent_lifecycle_is_correlated_and_emitted_once(): projector = YokeEventProjector(HarnessKind.CODEX) projector.project( diff --git a/uv.lock b/uv.lock index b6623db2..b911d2f4 100644 --- a/uv.lock +++ b/uv.lock @@ -11,16 +11,12 @@ resolution-markers = [ [[package]] name = "almanac-yoke" version = "0.1.7" -source = { registry = "https://pypi.org/simple" } +source = { git = "https://github.com/AlmanacCode/Yoke.git?rev=7ed04b57cbd17543f2f0576e850368f0d5fdf2a1#7ed04b57cbd17543f2f0576e850368f0d5fdf2a1" } dependencies = [ { name = "jsonschema" }, { name = "pydantic" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/d6/4fb29bc1dff1fc41029caee56b7072c9ec1f3af28fc72ad2fc7ea59f446b/almanac_yoke-0.1.7.tar.gz", hash = "sha256:6876e41dbef580d99f7c2e2f1e4d1d244a620c1d8dab76f2aa5097af09a04410", size = 118706 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/ea/ea68b5adc3b87b25c6eac80cf10c16dd4a3193bfec91674f0a75ee902b7e/almanac_yoke-0.1.7-py3-none-any.whl", hash = "sha256:58547b5954dd10f582df6cad6caff6631ee8afb2c9db100042068893ad64b8d8", size = 138415 }, -] [package.optional-dependencies] claude = [ @@ -281,7 +277,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "almanac-yoke", extras = ["claude"], specifier = ">=0.1.7,<0.2" }, + { name = "almanac-yoke", extras = ["claude"], git = "https://github.com/AlmanacCode/Yoke.git?rev=7ed04b57cbd17543f2f0576e850368f0d5fdf2a1" }, { name = "beautifulsoup4", specifier = ">=4.15.0" }, { name = "charset-normalizer", specifier = ">=3.4.7" }, { name = "fastapi", specifier = ">=0.138.1" }, From 2638a3072aa269f758ca8c63c83769ef7df23845 Mon Sep 17 00:00:00 2001 From: rohans0509 Date: Tue, 14 Jul 2026 01:37:12 -0700 Subject: [PATCH 2/2] chore: pin Yoke app-server auth fix --- almanac/reference/harness-event-shape.md | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/almanac/reference/harness-event-shape.md b/almanac/reference/harness-event-shape.md index f4af97fc..0b311810 100644 --- a/almanac/reference/harness-event-shape.md +++ b/almanac/reference/harness-event-shape.md @@ -16,7 +16,7 @@ sources: path: src/codealmanac/integrations/harnesses/yoke/events.py - id: yoke-usage-contract type: web - url: https://github.com/AlmanacCode/Yoke/blob/7ed04b57cbd17543f2f0576e850368f0d5fdf2a1/docs/notes/0272-usage-and-generation-control-audit.md + url: https://github.com/AlmanacCode/Yoke/blob/cce337d1c3b80a7fbca8feef4b91db95ae850753/docs/notes/0272-usage-and-generation-control-audit.md --- # Harness Event Shape diff --git a/pyproject.toml b/pyproject.toml index d9e0861c..206d8803 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,4 +84,4 @@ codealmanac = [ package = true [tool.uv.sources] -almanac-yoke = { git = "https://github.com/AlmanacCode/Yoke.git", rev = "7ed04b57cbd17543f2f0576e850368f0d5fdf2a1" } +almanac-yoke = { git = "https://github.com/AlmanacCode/Yoke.git", rev = "cce337d1c3b80a7fbca8feef4b91db95ae850753" } diff --git a/uv.lock b/uv.lock index b911d2f4..fed7775e 100644 --- a/uv.lock +++ b/uv.lock @@ -11,7 +11,7 @@ resolution-markers = [ [[package]] name = "almanac-yoke" version = "0.1.7" -source = { git = "https://github.com/AlmanacCode/Yoke.git?rev=7ed04b57cbd17543f2f0576e850368f0d5fdf2a1#7ed04b57cbd17543f2f0576e850368f0d5fdf2a1" } +source = { git = "https://github.com/AlmanacCode/Yoke.git?rev=cce337d1c3b80a7fbca8feef4b91db95ae850753#cce337d1c3b80a7fbca8feef4b91db95ae850753" } dependencies = [ { name = "jsonschema" }, { name = "pydantic" }, @@ -277,7 +277,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "almanac-yoke", extras = ["claude"], git = "https://github.com/AlmanacCode/Yoke.git?rev=7ed04b57cbd17543f2f0576e850368f0d5fdf2a1" }, + { name = "almanac-yoke", extras = ["claude"], git = "https://github.com/AlmanacCode/Yoke.git?rev=cce337d1c3b80a7fbca8feef4b91db95ae850753" }, { name = "beautifulsoup4", specifier = ">=4.15.0" }, { name = "charset-normalizer", specifier = ">=3.4.7" }, { name = "fastapi", specifier = ">=0.138.1" },