P1-02: AttentionCapsule as the real model product (closes #25) #200

Merged
conductor merged 6 commits from p1-02-capsule-authority into master 2026-09-07 10:03:59 +01:00
14 changed files with 1128 additions and 23 deletions

No files matched your search

+1
View File
@@ -90,6 +90,7 @@ jobs:
src/rune/canary_p0_04.py \
src/rune/canary_p0_05.py \
src/rune/canary_p0_07.py \
src/rune/canary_p1_02.py \
src/rune/canary_p1_07.py \
src/rune/canary_p1_06.py \
src/rune/canary_p1_01.py \
+4 -4
View File
@@ -103,10 +103,10 @@
"evidence": "REMOVED by P0-04 (#20): _run_warm_router is a documented no-op; the ContextBroker (#19) owns all model-facing context. See docs/pipeline-removal.md."
},
{
"path": "src/rune/attention_capsule.py:96-163",
"role": "Bounded working-set candidate",
"disposition": "dormant-until-dependency",
"evidence": "Creates a bounded capsule but does not own prompt construction."
"path": "src/rune/attention_capsule.py",
"role": "AttentionCapsule — the typed ledger of admitted blocks the model receives (to_messages re-derives the runtime payload)",
"disposition": "retain-authority",
"evidence": "P1-02 (#25): the capsule IS the product the model receives — the broker renders admitted blocks into it, the safety override mutates its user block in place (re-measured), runtime.run consumes capsule.to_messages(), and the trace v4 capsule_messages/capsule_content_hashes prove the logged capsule EXACTLY equals the sent payload. Legacy create_capsule/to_prompt (string-trim fake budgeting) deprecated with no live callers."
},
{
"path": "src/rune/inference_trace.py",
+67
View File
@@ -0,0 +1,67 @@
# Capsule Fidelity (P1-02, #25)
The AttentionCapsule is the **product the model receives** — not a
side-report. This document states the invariant, the mechanism, and how to
verify it.
## The equality invariant
> The logged capsule contents EXACTLY equal the bounded dynamic context
> supplied to the model.
Concretely, three renderings of one turn must be identical:
1. `capsule.to_messages()` — re-derived from the admitted-block ledger,
2. the message list passed to `LocalToolRuntime.run(...)` — captured
pre-run via `capsule.capture_messages()` → `capsule.sent_messages`,
3. `InferenceTrace.capsule_messages` — the durable JSONL log.
Token counts alone cannot prove this; the message list and per-block
content hashes can.
## Mechanism
- **Assembly:** `ContextBroker.assemble()` collects typed
`ContributionBlock`s, measures every block (`len//4`, deterministic and
conservative), admits against the input budget, and renders the admitted
blocks into the capsule (`capsule.blocks`).
- **Payload derivation:** `AttentionCapsule.to_messages()` re-derives the
chat message list from `capsule.blocks` on every call — in-place block
mutation always flows through; there is no cached copy to diverge.
- **Capture:** `RuneOperator._execute_on_model` calls
`capsule.capture_messages(capsule.to_messages())` immediately before
`runtime.run`. The capture is a deep copy, so later mutation cannot
alter what was logged.
- **Integrity hashes:** the trace stores `{block_name: sha256(content)}`
(string content hashed as UTF-8; list/history content as canonical
JSON) — full block text is NOT logged raw (JSONL size discipline).
- **Safety-override re-measure:** the override decorates the user block in
place per escalation tier; the operator re-runs the estimator on the
mutated block, updates `block.tokens`, the broker trace's per-block
entry, and `capsule.token_count`. No re-admission (the override grows
≤ ~100 tokens and admission left headroom) — but measurement is always
truthful.
- **num_ctx propagation:** the runtime receives
`num_ctx = capsule.token_count + completion_reserve` where
`completion_reserve = broker window − broker budget`
(`context_broker.default_budget` reserves the completion room). The
runtime therefore respects the broker's admission decision instead of
the raw window. #28 owns the broader model-aware budget work.
## Verification
- Frozen tests: `tests/test_p1_02_capsule_authority.py` — asserts
`trace.capsule_messages == runtime-received messages` (identity),
hash correctness, re-measure truthfulness, and num_ctx propagation.
- Canary: `python3 src/rune/canary_p1_02.py` prints
`P1-02 CAPSULE FIDELITY CANARY PASSED` (registered in
`.gitea/workflows/ci.yml`'s canary loop).
## Retired legacy path
`attention_capsule.create_capsule` and `AttentionCapsule.to_prompt` are
**deprecated** (`DeprecationWarning`): their string-trim "budgeting" was
never real admission. The ContextBroker remains the sole prompt-assembly
authority; the CLI entry point keeps them only for standalone debugging.
`ContributionBlock.to_dict()` now includes `content`, so pending-state
(approval-resume) capsules are verifiable end-to-end.
+13 -2
View File
@@ -17,11 +17,11 @@ is gitignored, P0-05 M6) — never committed. Consumers: #25, #28, #29.
Read back with `rune.inference_trace.read_traces()` or
`python3 -m rune.inference_trace [n]` (tail n records).
## Record schema (version 1)
## Record schema (version 4)
| Field | Source | Meaning |
|---|---|---|
| `schema_version` | constant (`1`) | Record shape version for consumers |
| `schema_version` | constant (`4`) | Record shape version for consumers |
| `provider` | `OllamaChatTransport.provider` | `"ollama"` (class constant) |
| `model` | operator | Model id the run executed on |
| `task_id` / `channel` | operator (TaskState #18) | Joins the trace to TaskState |
@@ -43,6 +43,17 @@ Read back with `rune.inference_trace.read_traces()` or
| `cost_usd` | `price_for(model)` | `None` until the price map gains entries |
| `cost_basis` | derived | `"local-unpriced"` or `"price-map"` |
| `success` / `error` | final `OperatorResponse` | Run outcome; error truncated to 300 chars |
| `routing_reason` / `routing_mode` / `task_family` | router (P1-06, #29, v2) | Start-tier decision evidence; empty on pre-router rows |
| `selected_tools` / `capability_reason` / `find_capability_calls` | broker selection (P1-01, #24, v3) | Capability-authority evidence |
| `capsule_messages` | capsule capture (P1-02, #25, v4) | The EXACT message list `runtime.run` received — equality with the logged capsule is the acceptance invariant |
| `capsule_content_hashes` | operator (P1-02, #25, v4) | `{block_name: sha256(content)}` — integrity without full JSONL bloat |
| `capsule_token_count` | capsule re-measure (P1-02, #25, v4) | Post-safety-override re-measured total |
Schema history: v1 (P0-07 #23), v2 additive routing fields (P1-06 #29),
v3 additive capability fields (P1-01 #24), v4 additive capsule-fidelity
fields (P1-02 #25). All bumps are additive with defaults — older rows
without the new keys still parse (`read_traces` consumers must tolerate
missing keys).
## Authority and single-source rule
+127
View File
@@ -0,0 +1,127 @@
# P1-02 AttentionCapsule → Real Model Product — Implementation Plan (#25)
> **For agentic workers:** execution contract — FROZEN TESTS ARE THE SPEC. Implement verbatim; record every deviation in the plan tail. TDD red → green. Do not create/complete tasks; controller owns task state.
**Goal:** the logged capsule contents EXACTLY equal the bounded dynamic context supplied to the model; no token_count-only fake budgeting; the capsule is provably the product the model receives.
**Architecture:** the broker already produces the capsule and derives messages from it (P0-03 did most of the wiring). This plan closes the *evidence and integrity* gaps: (1) a content-complete capsule log on the InferenceTrace (additive v4, hashes + message list, not full block text — size discipline); (2) a capsule-fidelity canary that asserts `capsule.to_messages()` == the messages passed to `runtime.run` == the logged capsule messages; (3) safety-override decoration re-measured into the capsule; (4) legacy `create_capsule`/`to_prompt` retired; (5) `ContributionBlock.to_dict` includes content so pending-state/log capsules are verifiable; (6) authority map flipped to retain-authority.
**Tech stack:** stdlib + existing house modules. No new services, no parallel assembly.
## Global Constraints
- Acceptance criterion (from #25): logged capsule contents EXACTLY equal the bounded dynamic context supplied to model. This is provable ONLY if the log records message content and the canary asserts identity — token counts alone are insufficient.
- No token_count-only fake budgeting: `create_capsule`/`to_prompt` (the legacy string-trim "budgeting" path) retired or clearly marked CLI-only.
- Trace additive discipline: new fields have defaults; v1/v2/v3 readers unaffected.
- Degrade-not-crash: capsule logging must never fail the turn (P0-06).
- Full block content is NOT logged raw (JSONL bloat) — log message list (the actual payload) + per-block content SHA-256 for integrity.
- num_ctx ownership: #28 owns model-aware context budgets; this issue passes the broker budget (not the raw window) as num_ctx — the one-line fix that makes the runtime respect admission.
- Safety-override decorated user block MUST be re-measured so `capsule.token_count` stays truthful post-decoration.
- TDD; ruff check + format on touched/added files; canary registered in ci.yml same commit.
## Design Decisions (settled)
1. **Capsule log location:** InferenceTrace additive v4 fields — `capsule_messages: list[dict] = []` (the exact messages sent), `capsule_content_hashes: dict[str,str] = {}` (block name → SHA-256 of content), `capsule_token_count: int = 0` (post-decoration re-measured total). No separate JSONL file; rotation already handled.
2. **Equality invariant:** `capsule.to_messages()` result is captured BEFORE `runtime.run` and stored on the trace; a frozen test asserts trace.capsule_messages == the list passed to runtime.run (identity, not re-measure). The canary asserts the same against a scripted runtime.
3. **Safety-override re-measure:** after override mutates the user block content, re-run `estimate_tokens` on the mutated block, update `block.tokens` + `capsule.token_count` + the trace. No re-admission (override grows ≤ ~100 tokens; admission already left headroom).
4. **num_ctx:** pass `capsule.token_count + completion_reserve` where completion_reserve = the broker's reserved completion room (window − budget). One-line change; #28 owns the broader model-aware budget work.
5. **Runtime fallback tool prompt:** formally exempt — `_with_fallback_prompt` only fires for non-native models, and its cost is outside broker scope. Add a comment + log-only accounting note. Full integration is a #28/#34 follow-up.
6. **Legacy retirement:** `create_capsule` and `to_prompt` marked deprecated (docstring + `warnings.warn(DeprecationWarning)`), zero live callers verified by test. `to_dict` gains `content` field. Authority map: `attention_capsule.py` → retain-authority under ContextBroker.
## Tasks
### Task 1 — ContributionBlock.to_dict includes content + AttentionCapsule message capture
**Files:** modify `src/rune/context_broker.py` (to_dict + capsule), `src/rune/attention_capsule.py` (deprecation), test `tests/test_p1_02_capsule_authority.py`.
**Interfaces:**
- Produces: `ContributionBlock.to_dict()` includes `"content"`; `AttentionCapsule.capture_messages()` → stores the exact list on `capsule.sent_messages` (not a dataclass field — set post-hoc like the existing dynamic attrs); `create_capsule`/`to_prompt` emit DeprecationWarning.
- [ ] **Step 1: failing tests** — to_dict includes content matching the block's content; to_dict without content key in old fixture still loads (back-compat); capture_messages stores the list; to_messages re-derivation equals captured list for the same capsule; create_capsule raises DeprecationWarning.
- [ ] **Step 2: run → red**
- [ ] **Step 3: implement**
- [ ] **Step 4: green**
- [ ] **Step 5: commit** `feat(p1-02): capsule content logging + message capture + legacy deprecation (#25)`
### Task 2 — Safety-override re-measure + num_ctx budget fix
**Files:** modify `src/rune/rune_operator.py`, test file.
- [ ] **Step 1: failing tests** — after safety-override mutates the user block, `capsule.token_count` reflects the new estimate (not stale); trace `per_block_tokens["user"]` matches; `num_ctx` passed to runtime.run equals `capsule.token_count + completion_reserve` (not the raw window).
- [ ] **Step 2: red** → **Step 3: implement** → **Step 4: green**
- [ ] **Step 5: commit** `feat(p1-02): safety-override re-measure + num_ctx budget propagation (#25)`
### Task 3 — Trace v4 capsule fields + operator logging
**Files:** modify `src/rune/inference_trace.py` (v4 fields), `src/rune/rune_operator.py` (log messages + hashes into trace), test file.
**Interfaces:** Consumes Task 1. Produces: `InferenceTrace.capsule_messages`, `capsule_content_hashes`, `capsule_token_count` (additive, defaults preserve v1–v3 readers).
- [ ] **Step 1: failing tests** — trace record carries capsule_messages equal to the messages the scripted runtime received; content hashes present and correct; old trace rows without v4 fields still parse.
- [ ] **Step 2: red** → **Step 3: implement** → **Step 4: green**; full suite regression (baseline 588+84).
- [ ] **Step 5: commit** `feat(p1-02): trace v4 capsule fidelity fields (#25)`
### Task 4 — Authority map + docs
**Files:** `config/v1_authority_map.json` (attention_capsule → retain-authority, evidence), `tests/test_v1_authority_map.py` (if pinned), `docs/capsule-fidelity.md` (create: what the capsule is, the equality invariant, how to verify, num_ctx propagation).
- [ ] Steps: map update → docs → commit `feat(p1-02): capsule authority map + docs (#25)`
### Task 5 — Canary + CI registration
**Files:** create `src/rune/canary_p1_02.py`; modify `.gitea/workflows/ci.yml` same commit.
- [ ] Canary: scripted operator run asserts (1) trace.capsule_messages == runtime-received messages (exact list identity), (2) capsule_token_count truthful post-decoration, (3) num_ctx == budget not window, (4) content hashes match block contents, (5) no DeprecationWarning suppressed. Prints `P1-02 CAPSULE FIDELITY CANARY PASSED`.
- [ ] Full suite; ruff; henxels; commit `feat(p1-02): canary + CI registration (#25)`
## Acceptance (from #25)
- Logged capsule contents EXACTLY equal the bounded dynamic context supplied to model — proven by trace v4 `capsule_messages` == `runtime.run` messages, asserted in frozen test + canary.
- No token_count-only fake budgeting — `create_capsule`/`to_prompt` deprecated, zero live callers verified; admission remains the real budget enforcement.
## Pipeline requirements checklist
- [x] TDD red→green (frozen tests per task)
- [x] ruff check + format on touched/added files
- [ ] Spec gate PASS; Quality gate Approved (discharges if needed)
- [x] Full-suite regression green (baseline 588+84 → 604+84, +16 new)
- [x] Canary green + registered same commit; henxels exit 0
- [ ] Closed with formatted comment + PR refs
## Completion Record (P1-02 implementer, 2026-09-06)
**Status:** all 5 tasks complete. Branch `p1-02-capsule-authority`, head `96e42ae`.
**Commits:**
1. `d0550b6` feat(p1-02): capsule content logging + message capture + legacy deprecation (#25)
2. `ec0cf7a` feat(p1-02): safety-override re-measure + num_ctx budget propagation (#25)
3. `8ff6a83` feat(p1-02): trace v4 capsule fidelity fields (#25)
4. `90bcd59` feat(p1-02): capsule authority map + docs (#25)
5. `96e42ae` feat(p1-02): canary + CI registration (#25)
**Final counts:** 604 passed + 84 subtests (baseline 588+84; +16 new frozen tests, 0 regressions).
Canary: `P1-02 CAPSULE FIDELITY CANARY PASSED`. henxels exit 0 (21 pre-existing warnings).
**Red evidence** (initial run of tests/test_p1_02_capsule_authority.py, 14 failed / 2 passed):
`test_contribution_block_to_dict_includes_content`, `test_capture_messages_stores_the_exact_list`,
`test_create_capsule_emits_deprecation_warning`, `test_safety_override_reremeasures_capsule_token_count`,
`test_num_ctx_is_budget_plus_completion_reserve_not_window`, `test_inference_trace_schema_v4_fields`,
`test_operator_trace_record_carries_capsule_messages` (+7 more) — all red before their task's implementation, green after.
**Deviations from plan:**
1. **Schema v4 bump touched pinned literals:** `tests/test_p1_01_capability_authority.py` (3 assertions) and canaries
`canary_p1_01.py` / `canary_p1_06.py` pinned `schema_version == 3`. Updated those pins to 4 in the same Task-3 commit —
the additive discipline protects v1–v3 ROWS, not the literal; `docs/inference-trace.md` schema table updated to v4 with a schema history note.
2. **trace["blocks"] user-entry re-measure:** the frozen test demanded `per_block_tokens["user"]` match post-decoration; the
re-measure therefore also updates the admitted user entry in the broker trace dict (not only `block.tokens` + `capsule.token_count`).
3. **num_ctx degrade path:** added a `num_ctx <= 0` fallback to the discovered window for stand-in brokers (trace missing
window/budget keys) — degrade-not-crash, beyond the literal one-liner, pinned by `test_num_ctx_falls_back_to_window_when_budget_basis_missing`.
4. **Trace v4 extraction is Mock-hardened:** pre-existing frozen tests construct the capsule as a `Mock`; the operator's
hash/message extraction guards non-list/non-dict content and falls back gracefully (no fidelity evidence on stand-in capsules).
5. **`capture_messages` deep-copies** the sent list (test-pinned) so caller-side mutation can never diverge the logged payload.
**Reviewer flags:**
- `InferenceTrace.capsule_messages` logs the FULL message payload per run — accepted per plan (message list, not raw blocks);
watch JSONL rotation headroom if history blocks grow (#28 compaction bounds this).
- `docs/inference-trace.md` "Record schema (version 4)" supersedes the version-1 table heading; no other docs referenced v3.
- Authority map: `attention_capsule.py` entry now `retain-authority` under ContextBroker (context_construction);
`test_v1_authority_map.py` green (path existence, disposition vocabulary).
+39 -2
View File
@@ -15,6 +15,7 @@ import json
import os
import sys
import re
import warnings
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, List
@@ -89,8 +90,32 @@ class AttentionCapsule:
messages.append({"role": "user", "content": user_content})
return messages
def capture_messages(self, messages: List[dict]) -> List[dict]:
"""Record the EXACT message list sent to the runtime (P1-02 #25).
Stored post-hoc on ``capsule.sent_messages`` (dynamic attr, same
pattern as ``messages``/``system_preamble`` from the broker render)
so the equality invariant — ``to_messages()`` re-derivation ==
what the model received — is machine-checkable from the ledger.
The list is deep-copied so later caller-side mutation can never
diverge the logged payload from what was sent.
"""
self.sent_messages = json.loads(json.dumps(list(messages)))
return self.sent_messages
def to_prompt(self) -> str:
"""Convert capsule to prompt text."""
"""Convert capsule to prompt text.
.. deprecated:: P1-02 (#25)
Legacy renderer behind the retired string-trim "budgeting" path.
The ContextBroker owns prompt assembly; use ``to_messages()``.
"""
warnings.warn(
"attention_capsule.AttentionCapsule.to_prompt is deprecated (P1-02 #25): "
"the ContextBroker owns prompt assembly — use to_messages().",
DeprecationWarning,
stacklevel=2,
)
parts = []
if self.active_objective:
@@ -135,9 +160,21 @@ def estimate_tokens(text: str) -> int:
def create_capsule(task: str, context: str = "", max_tokens: int = 600) -> AttentionCapsule:
"""Create an attention capsule for a task.
Only includes what's relevant to the current task.
.. deprecated:: P1-02 (#25)
Legacy CLI helper with fake string-trim "budgeting" — token counts
here are never the real admission decision. The ContextBroker
(#19) is the sole prompt-assembly authority. Kept only for the
standalone CLI above; no live callers.
"""
warnings.warn(
"attention_capsule.create_capsule is deprecated (P1-02 #25): its string-trim "
"budgeting is not real admission — use the ContextBroker.",
DeprecationWarning,
stacklevel=2,
)
capsule = AttentionCapsule()
# Extract objective from task
+2 -2
View File
@@ -247,8 +247,8 @@ def check_scripted_operator_wiring() -> int:
if not records:
return _fail("no durable inference trace persisted")
record = records[-1]
if record["schema_version"] != 3:
return _fail(f"trace schema_version must be 3, got {record['schema_version']}")
if record["schema_version"] != 4:
return _fail(f"trace schema_version must be 4, got {record['schema_version']}")
if record["selected_tools"] != expected:
return _fail(f"trace selected_tools wrong: {record['selected_tools']}")
if not record["capability_reason"]:
+253
View File
@@ -0,0 +1,253 @@
#!/usr/bin/env python3
"""P1-02 capsule-fidelity canary (#25).
Self-bootstrapping like canary_p1_07.py (CI runs ``python3 <canary>``
without PYTHONPATH). Proves the #25 claims are machine-checked with NO
live model, NO Ollama, NO network:
1. trace.capsule_messages == the messages the runtime received (exact
list identity) — the acceptance invariant;
2. capsule_token_count is truthful post-safety-override decoration;
3. num_ctx == capsule.token_count + completion_reserve (the broker
budget basis), NOT the raw window;
4. content hashes match the block contents;
5. no DeprecationWarning is suppressed: the legacy create_capsule /
to_prompt paths still warn.
Isolates all state under RUNE_HOME so the canary never touches a real
home directory. Prints 'P1-02 CAPSULE FIDELITY CANARY PASSED' and exits 0.
"""
import contextlib
import hashlib
import json
import os
import sys
import tempfile
import warnings
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
# Self-bootstrap: CI runs `python3 <canary>` without PYTHONPATH, so put the
# repository src/ on sys.path before importing rune modules. Isolate Rune's
# state/journal paths so the canary never touches a real home directory.
_repo_src = str(Path(__file__).resolve().parents[1])
sys.path.insert(0, _repo_src)
_canary_home = tempfile.mkdtemp(prefix="p1-02-canary-home-")
os.environ["RUNE_HOME"] = _canary_home
os.environ["RUNE_MEMORY_DIR"] = tempfile.mkdtemp(prefix="p1-02-canary-mem-")
from rune import rune_operator # noqa: E402
from rune.attention_capsule import AttentionCapsule # noqa: E402
from rune.context_broker import ContributionBlock # noqa: E402
from rune.inference_trace import read_traces # noqa: E402
from rune.local_tool_runtime import OllamaChatTransport # noqa: E402
from rune.tool_protocol import OperatorResponse # noqa: E402
WINDOW = 40960
BUDGET = 32768
def _fail(message: str) -> int:
print(f"FAILED: {message}", file=sys.stderr)
return 1
class _ScriptedTransport(OllamaChatTransport):
"""OllamaChatTransport with _post scripted — real accumulation code runs."""
def __init__(self):
super().__init__()
def _post(self, path, payload):
return {
"model": "qwen3:8b",
"created_at": "2026-09-05T00:00:00Z",
"message": {"role": "assistant", "content": "ok"},
"done": True,
"prompt_eval_count": 100,
"eval_count": 5,
"total_duration": 1_000_000_000,
}
class _ScriptedRuntime:
"""Minimal runtime stand-in: one chat, then the canned final response."""
def __init__(self, transport):
self.transport = transport
self.tool_names = ("read_file", "write_file")
self.received_messages: list = []
self.received_options: dict = {}
def run(self, messages, **kwargs):
self.received_messages = [dict(m) for m in messages]
self.received_options = dict(kwargs.get("options") or {})
self.transport.chat(model="qwen3:8b", messages=list(messages), tools=None)
return OperatorResponse(success=True, user_text="All done.", model="qwen3:8b")
def _scripted_operator_run(description: str, tier_params: dict):
"""Real RuneOperator + scripted broker/transport → (envelope, runtime, capsule)."""
storage = Path(_canary_home) / "operator.json"
op = rune_operator.RuneOperator(str(storage))
capsule = AttentionCapsule()
capsule.blocks = [
ContributionBlock("persona", 90, "I am Rune.", len("I am Rune.") // 4, "persona", evictable=False),
ContributionBlock("user", 100, description, len(description) // 4, "rune_operator task text", evictable=False),
]
capsule.token_count = sum(b.tokens for b in capsule.blocks)
trace = {
"model_id": "qwen3:8b",
"window": WINDOW,
"budget": BUDGET,
"blocks": [
{"name": "persona", "tokens": capsule.blocks[0].tokens, "admitted": True, "reason": ""},
{"name": "user", "tokens": capsule.blocks[1].tokens, "admitted": True, "reason": ""},
],
"admitted": ["persona", "user"],
"admitted_total": capsule.token_count,
"dropped": [],
"memory_tokens": 0,
"degraded": [],
"compaction": [],
"elapsed_ms": 1.0,
}
broker = SimpleNamespace(assemble=lambda task, session, model_id: (capsule, trace))
runtime = _ScriptedRuntime(_ScriptedTransport())
profile = SimpleNamespace(safety_tier=0)
patches = [
patch.object(op, "_context_broker", return_value=broker),
patch.object(rune_operator.model_profiles, "get_or_create_profile", return_value=profile),
patch.object(rune_operator.model_profiles, "record_interaction"),
patch.object(rune_operator, "LocalToolRuntime", return_value=runtime),
patch.object(rune_operator, "OllamaChatTransport", return_value=runtime.transport),
patch("rune.local_tool_runtime.context_length", lambda model: WINDOW),
patch.object(rune_operator.safety_override.SafetyOverride, "get_params", lambda self, text: dict(tier_params)),
patch.object(rune_operator.safety_override.SafetyOverride, "is_refusal", lambda self, text: False),
patch.object(op, "_log_token_usage"),
]
with contextlib.ExitStack() as stack:
for item in patches:
stack.enter_context(item)
stack.enter_context(patch("rune.event_bus._store_path", lambda: Path(storage).parent / "journal-events.jsonl"))
envelope = op.send_task(description, "qwen3:8b")
return envelope, runtime, capsule
def check_message_identity() -> int:
"""Invariant: logged capsule messages == runtime-received messages."""
description = "inspect this code path"
envelope, runtime, capsule = _scripted_operator_run(
description, {"tier": 0, "temperature": 0.7, "prefix": "", "suffix": ""}
)
if not envelope.get("success"):
return _fail(f"scripted run failed: {envelope.get('error')}")
records = read_traces(trace_dir=Path(_canary_home) / "state")
if not records:
return _fail("no durable inference trace persisted")
record = records[-1]
if record["capsule_messages"] != runtime.received_messages:
return _fail("trace.capsule_messages != runtime-received messages (acceptance invariant)")
if record["capsule_messages"][-1]["content"] != description:
return _fail("logged user content diverges from the task description")
if capsule.sent_messages != capsule.to_messages():
return _fail("captured sent messages != to_messages() re-derivation")
return 0
def check_token_truthfulness() -> int:
"""Post-decoration token_count is re-measured (not stale)."""
description = "please inspect the failing test"
suffix = "\n\nI respond as myself:"
envelope, _runtime, capsule = _scripted_operator_run(
description, {"tier": 2, "temperature": 0.95, "prefix": "", "suffix": suffix}
)
if not envelope.get("success"):
return _fail(f"scripted run failed: {envelope.get('error')}")
user_block = next(b for b in capsule.blocks if b.name == "user")
if user_block.content != description + suffix:
return _fail("safety override did not decorate the user block")
expected = sum(len(b.content) // 4 if isinstance(b.content, str) else 0 for b in capsule.blocks)
if capsule.token_count != expected:
return _fail(f"capsule.token_count {capsule.token_count} != re-measured {expected}")
records = read_traces(trace_dir=Path(_canary_home) / "state")
if records[-1]["capsule_token_count"] != expected:
return _fail("trace capsule_token_count not truthful post-decoration")
return 0
def check_num_ctx_budget() -> int:
"""num_ctx == capsule.token_count + completion_reserve, never the window."""
description = "inspect this code path"
envelope, runtime, capsule = _scripted_operator_run(
description, {"tier": 0, "temperature": 0.7, "prefix": "", "suffix": ""}
)
if not envelope.get("success"):
return _fail(f"scripted run failed: {envelope.get('error')}")
completion_reserve = WINDOW - BUDGET
expected = capsule.token_count + completion_reserve
if runtime.received_options.get("num_ctx") != expected:
return _fail(f"num_ctx {runtime.received_options.get('num_ctx')} != budget basis {expected}")
if runtime.received_options.get("num_ctx") == WINDOW:
return _fail("num_ctx equals the raw window — budget not respected")
return 0
def check_content_hashes() -> int:
"""Per-block SHA-256 hashes match the actual block contents."""
description = "hash integrity probe"
envelope, _runtime, capsule = _scripted_operator_run(
description, {"tier": 0, "temperature": 0.7, "prefix": "", "suffix": ""}
)
if not envelope.get("success"):
return _fail(f"scripted run failed: {envelope.get('error')}")
records = read_traces(trace_dir=Path(_canary_home) / "state")
hashes = records[-1]["capsule_content_hashes"]
if set(hashes) != {b.name for b in capsule.blocks}:
return _fail(f"hash keys {sorted(hashes)} != block names {sorted(b.name for b in capsule.blocks)}")
for block in capsule.blocks:
payload = (
block.content.encode("utf-8")
if isinstance(block.content, str)
else json.dumps(block.content, ensure_ascii=False, sort_keys=True).encode("utf-8")
)
if hashes[block.name] != hashlib.sha256(payload).hexdigest():
return _fail(f"content hash mismatch for block '{block.name}'")
return 0
def check_deprecation_not_suppressed() -> int:
"""Legacy paths still warn — no silent suppression of the retirement."""
from rune.attention_capsule import create_capsule
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
create_capsule("fix the failing test", "context line here")
AttentionCapsule(active_objective="obj").to_prompt()
deprecations = [w for w in caught if issubclass(w.category, DeprecationWarning)]
if len(deprecations) < 2:
return _fail(f"expected 2 DeprecationWarnings from legacy paths, got {len(deprecations)}")
return 0
def main() -> int:
checks = (
("trace.capsule_messages == runtime-received messages (identity)", check_message_identity),
("capsule_token_count truthful post-decoration", check_token_truthfulness),
("num_ctx == budget basis, not the raw window", check_num_ctx_budget),
("content hashes match block contents", check_content_hashes),
("legacy deprecation warnings not suppressed", check_deprecation_not_suppressed),
)
for label, check in checks:
result = check()
if result != 0:
return result
print(f" ✓ {label}")
print("P1-02 CAPSULE FIDELITY CANARY PASSED")
return 0
if __name__ == "__main__":
sys.exit(main())
+2 -2
View File
@@ -198,8 +198,8 @@ def check_reason_recording() -> int:
if not records:
return _fail("no durable inference trace persisted")
record = records[-1]
if record["schema_version"] != 3:
return _fail(f"schema_version must be 3, got {record['schema_version']}")
if record["schema_version"] != 4:
return _fail(f"schema_version must be 4, got {record['schema_version']}")
if record["routing_reason"] != "router: measured 12/13 success task_family=code":
return _fail(f"routing_reason not recorded: {record['routing_reason']!r}")
if record["routing_mode"] != "local":
+9 -1
View File
@@ -79,10 +79,18 @@ class ContributionBlock:
self.evictable = evictable # False = never dropped (system, user)
def to_dict(self) -> dict:
"""Serialize for pending_state embedding (approval resume)."""
"""Serialize for pending_state embedding (approval resume).
P1-02 (#25): ``content`` is included so the embedded capsule is
verifiable end-to-end — the acceptance criterion is that the logged
capsule contents EXACTLY equal the model payload, which token counts
alone cannot prove. Blocks are small (admission-bounded), so the
extra size is bounded by the same budget.
"""
return {
"name": self.name,
"priority": self.priority,
"content": self.content,
"tokens": self.tokens,
"source": self.source,
"evictable": self.evictable,
+8 -1
View File
@@ -25,7 +25,7 @@ try:
except ImportError: # direct script execution
from rune.memory_env import rune_home
INFERENCE_TRACE_SCHEMA_VERSION = 3
INFERENCE_TRACE_SCHEMA_VERSION = 4
TRACE_ROTATE_BYTES = 10_000_000
TRACE_BACKUPS = 3
@@ -87,6 +87,13 @@ class InferenceTrace:
selected_tools: list = field(default_factory=list) # ordered exposed set
capability_reason: str = "" # broker selection reason ("" = unselected)
find_capability_calls: int = 0 # escape-hatch round-trips this run
# P1-02 (#25) schema v4 — capsule-fidelity fields, additive. The logged
# capsule contents must EXACTLY equal the bounded dynamic context
# supplied to the model; message list (the actual payload) + per-block
# content SHA-256 (integrity without full JSONL bloat) prove it.
capsule_messages: list = field(default_factory=list) # exact runtime.run payload
capsule_content_hashes: dict = field(default_factory=dict) # block name -> sha256
capsule_token_count: int = 0 # post-decoration re-measured total
def price_for(model: str) -> float | None:
+75 -6
View File
@@ -13,6 +13,7 @@ Usage:
rune_operator.py list # List available models
"""
import hashlib
import json
import os
import re
@@ -1654,10 +1655,27 @@ I have tools to read, write, edit, search, lint, test, format, and commit code.
# Safety override decorates the final user BLOCK in place
# (P0-03 constraint d); rendering flows from the blocks.
# P1-02 (#25): after mutation the block is RE-MEASURED so
# capsule.token_count (and the broker trace's per-block
# entry) stay truthful post-decoration — the override grows
# the payload ≤ ~100 tokens and admission left headroom, so
# no re-admission is needed; measurement is not budgeting.
for block in capsule.blocks:
if block.name == "user":
block.content = override_prefix + task.description + override_suffix
block.tokens = len(block.content) // 4
for trace_block in trace["blocks"]:
if trace_block.get("name") == "user" and trace_block.get("admitted"):
trace_block["tokens"] = block.tokens
capsule.token_count = sum(
block.tokens for block in capsule.blocks if hasattr(block, "tokens")
)
messages = capsule.to_messages()
# P1-02 (#25) acceptance: the log must capture the EXACT
# payload the runtime receives — capture BEFORE run, assert
# identity in the frozen test + canary.
if hasattr(capsule, "capture_messages"):
capsule.capture_messages(messages)
runtime = LocalToolRuntime(
transport=OllamaChatTransport(timeout=timeout),
@@ -1666,18 +1684,27 @@ I have tools to read, write, edit, search, lint, test, format, and commit code.
tools=capability_selection["tools"],
)
transport = runtime.transport
# P0-03: the runtime window must match the broker's budget
# basis — a hardcoded 8192 would silently truncate blocks the
# broker admitted against a larger discovered window. The
# other 13 num_ctx sites remain pinned (follow-up issue).
from rune.local_tool_runtime import context_length as _ctx_len
# P1-02 (#25) one-line num_ctx fix: pass the broker budget
# basis — capsule.token_count + completion_reserve (window −
# budget) — NOT the raw window, so the runtime respects the
# broker's admission decision (#28 owns model-aware budgets).
completion_reserve = 0
if trace.get("window") is not None and trace.get("budget") is not None:
completion_reserve = max(0, int(trace["window"]) - int(trace["budget"]))
num_ctx = int(capsule.token_count) + completion_reserve
if num_ctx <= 0:
# Degrade-not-crash (stand-in broker/trace): keep the
# discovered window rather than sending num_ctx=0.
from rune.local_tool_runtime import context_length as _ctx_len
num_ctx = _ctx_len(model)
response = runtime.run(
messages,
options={
"temperature": temperature,
"num_predict": 4096,
"num_ctx": _ctx_len(model),
"num_ctx": num_ctx,
"stop": ["\n\nUser:", "\n\n---"],
},
think=True,
@@ -1790,6 +1817,35 @@ I have tools to read, write, edit, search, lint, test, format, and commit code.
record_trace,
)
# P1-02 (#25) schema v4: capsule fidelity evidence. The
# captured sent messages (post-decoration, pre-run) plus
# per-block SHA-256 integrity hashes and the re-measured
# token total. Degrade-not-crash (P0-06): a stand-in
# capsule (Mock/legacy) must never fail the trace —
# everything is guarded; the record just omits evidence
# that cannot be derived.
capsule_hashes: dict = {}
for _b in list(getattr(capsule, "blocks", None) or []):
content = getattr(_b, "content", None)
name = getattr(_b, "name", "")
if not isinstance(name, str) or not name or content is None:
continue
if isinstance(content, str):
payload = content.encode("utf-8")
elif isinstance(content, (list, dict)):
payload = json.dumps(content, ensure_ascii=False, sort_keys=True).encode("utf-8")
else:
continue
capsule_hashes[name] = hashlib.sha256(payload).hexdigest()
_sent = getattr(capsule, "sent_messages", None)
if not isinstance(_sent, list):
try:
_sent = capsule.to_messages()
except Exception:
_sent = []
if not isinstance(_sent, list):
_sent = []
trace_record = InferenceTrace(
schema_version=INFERENCE_TRACE_SCHEMA_VERSION,
provider=getattr(transport, "provider", "unknown"),
@@ -1831,6 +1887,19 @@ I have tools to read, write, edit, search, lint, test, format, and commit code.
find_capability_calls=sum(
1 for event in response.tool_events if event.request.name == "find_capability"
),
# P1-02 (#25) schema v4: capsule fidelity fields —
# the logged messages EXACTLY equal the runtime.run
# payload (captured pre-run on the mutated capsule).
capsule_messages=[
dict(m) for m in _sent if isinstance(m, dict)
],
capsule_content_hashes=capsule_hashes,
capsule_token_count=(
int(capsule.token_count)
if isinstance(getattr(capsule, "token_count", 0), int)
and not isinstance(getattr(capsule, "token_count", 0), bool)
else 0
),
)
record_trace(trace_record)
# P1-06 (quality I-2 discharge): CLOSE the measured loop —
+5 -3
View File
@@ -326,7 +326,9 @@ def test_default_runtime_still_exposes_full_tools():
def test_inference_trace_schema_v3_fields():
from rune import inference_trace as it
assert it.INFERENCE_TRACE_SCHEMA_VERSION == 3
# P1-02 (#25) bumped the schema to v4 (additive); assert against the
# module constant, not a pinned literal.
assert it.INFERENCE_TRACE_SCHEMA_VERSION == 4
trace = it.InferenceTrace(
schema_version=it.INFERENCE_TRACE_SCHEMA_VERSION,
provider="ollama",
@@ -420,7 +422,7 @@ def test_operator_trace_record_carries_capability_fields(tmp_path, monkeypatch):
records = it.read_traces(trace_dir=tmp_path / "home" / "state")
assert records, "durable trace missing"
record = records[-1]
assert record["schema_version"] == 3
assert record["schema_version"] == 4
assert record["selected_tools"] == cb.select_for_job("fix this code path")["names"]
assert "capability_reason" in record and record["capability_reason"]
assert record["find_capability_calls"] == 0
@@ -496,7 +498,7 @@ def test_operator_trace_record_carries_selected_tools(tmp_path, monkeypatch):
records = it.read_traces(trace_dir=tmp_path / "home2" / "state")
assert records
record = records[-1]
assert record["schema_version"] == 3
assert record["schema_version"] == 4
assert record["selected_tools"] == tool_names
assert record["capability_reason"]
assert record["find_capability_calls"] == 0
+523
View File
@@ -0,0 +1,523 @@
"""P1-02 (#25): AttentionCapsule authority — FROZEN TESTS ARE THE SPEC.
Layers covered per plan task:
1. ContributionBlock.to_dict content completeness + capsule message capture
+ legacy deprecation (Task 1),
2. safety-override re-measure + num_ctx budget propagation (Task 2),
3. trace v4 capsule fidelity fields + operator logging (Task 3).
The canary (src/rune/canary_p1_02.py) re-proves the contract CI-side.
"""
from __future__ import annotations
import hashlib
import json
import sys
import warnings
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from rune import rune_operator # noqa: E402
from rune.attention_capsule import AttentionCapsule # noqa: E402
from rune.context_broker import ContributionBlock, default_budget # noqa: E402
from rune.local_tool_runtime import OllamaChatTransport # noqa: E402
from rune.tool_protocol import OperatorResponse # noqa: E402
# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------
class _ScriptedTransport(OllamaChatTransport):
"""OllamaChatTransport with _post scripted — real accumulation code runs."""
def __init__(self, responses):
super().__init__()
self._responses = list(responses)
def _post(self, path, payload):
if not self._responses:
raise AssertionError("scripted transport exhausted")
return self._responses.pop(0)
def _chat_response(prompt_eval_count, eval_count, total_duration_ns):
return {
"model": "qwen3:8b",
"created_at": "2026-09-05T00:00:00Z",
"message": {"role": "assistant", "content": "ok"},
"done": True,
"prompt_eval_count": prompt_eval_count,
"eval_count": eval_count,
"total_duration": total_duration_ns,
}
class _ScriptedRuntime:
"""Minimal runtime stand-in: one chat, then the canned final response."""
def __init__(self, transport, response):
self.transport = transport
self.tool_names = ("read_file", "write_file")
self._response = response
self.received_messages: list = []
self.received_options: dict = {}
def run(self, messages, **kwargs):
self.received_messages = [dict(m) for m in messages]
self.received_options = dict(kwargs.get("options") or {})
self.transport.chat(model="qwen3:8b", messages=list(messages), tools=None)
return self._response
def _broker_trace(**overrides):
trace = {
"model_id": "qwen3:8b",
"window": 40960,
"budget": 32768,
"blocks": [
{"name": "persona", "tokens": 980, "admitted": True, "reason": ""},
{"name": "user", "tokens": 20, "admitted": True, "reason": ""},
],
"admitted": ["persona", "user"],
"admitted_total": 1000,
"dropped": [],
"memory_tokens": 0,
"degraded": [],
"compaction": [],
"elapsed_ms": 1.0,
}
trace.update(overrides)
return trace
def _wire_operator(tmp_path, monkeypatch, runtime, capsule, trace):
"""Real RuneOperator; broker/collaborators scripted; RUNE_HOME sandboxed."""
monkeypatch.setenv("RUNE_HOME", str(tmp_path))
op = rune_operator.RuneOperator(str(tmp_path / "operator.json"))
broker = SimpleNamespace(assemble=lambda task, session, model_id: (capsule, trace))
profile = SimpleNamespace(safety_tier=0)
patches = [
patch.object(op, "_context_broker", return_value=broker),
patch.object(rune_operator.model_profiles, "get_or_create_profile", return_value=profile),
patch.object(rune_operator.model_profiles, "record_interaction"),
patch.object(rune_operator, "LocalToolRuntime", return_value=runtime),
patch.object(rune_operator, "OllamaChatTransport", return_value=runtime.transport),
patch("rune.local_tool_runtime.context_length", lambda model: 40960),
patch.object(
rune_operator.safety_override.SafetyOverride,
"get_params",
lambda self, text: {"tier": 0, "temperature": 0.7, "prefix": "", "suffix": ""},
),
patch.object(rune_operator.safety_override.SafetyOverride, "is_refusal", lambda self, text: False),
patch.object(op, "_log_token_usage"),
patch("rune.event_bus._store_path", lambda: tmp_path / "journal-events.jsonl"),
]
return op, patches
# ---------------------------------------------------------------------------
# Task 1 — ContributionBlock.to_dict content + capsule message capture
# ---------------------------------------------------------------------------
def test_contribution_block_to_dict_includes_content():
"""to_dict must carry the block's content so pending-state/log capsules
are verifiable (P1-02 acceptance: logged capsule contents == payload)."""
block = ContributionBlock("user", 100, "inspect this", 5, "rune_operator task text", evictable=False)
serialized = block.to_dict()
assert serialized["content"] == "inspect this"
assert serialized["name"] == "user"
assert serialized["priority"] == 100
assert serialized["tokens"] == 5
assert serialized["source"] == "rune_operator task text"
assert serialized["evictable"] is False
def test_contribution_block_to_dict_round_trips_list_content():
"""List-content (history) blocks serialize the message list verbatim."""
messages = [{"role": "user", "content": "hi"}, {"role": "assistant", "content": "hello"}]
block = ContributionBlock("history", 30, messages, 10, "history")
assert block.to_dict()["content"] == messages
def test_capsule_to_dict_embeds_block_content():
"""Capsule to_dict round-trips the block content through the ledger."""
capsule = AttentionCapsule(token_count=5)
capsule.blocks = [ContributionBlock("user", 100, "do the thing", 3, "src", evictable=False)]
data = capsule.to_dict()
assert data["blocks"][0]["content"] == "do the thing"
assert json.loads(json.dumps(data)) == data # JSON-safe
def test_capture_messages_stores_the_exact_list():
"""capture_messages() stores the sent list post-hoc (dynamic attr like
messages/system_preamble) — no dataclass field, additive only."""
capsule = AttentionCapsule()
capsule.blocks = [
ContributionBlock("persona", 90, "I am Rune.", 2, "persona", evictable=False),
ContributionBlock("user", 100, "go", 1, "user", evictable=False),
]
sent = capsule.to_messages()
returned = capsule.capture_messages(sent)
assert returned == sent
assert capsule.sent_messages == sent
# capture after in-place mutation re-captures the mutated rendering
capsule.blocks[-1].content = "decorated go"
assert capsule.to_messages() != sent
capsule.capture_messages(capsule.to_messages())
assert capsule.sent_messages == capsule.to_messages()
def test_capture_messages_copies_so_later_mutation_does_not_diverge():
"""The stored list must not alias the caller's list (log integrity)."""
capsule = AttentionCapsule()
sent = [{"role": "user", "content": "go"}]
capsule.capture_messages(sent)
sent.append({"role": "assistant", "content": "hijack"})
assert capsule.sent_messages == [{"role": "user", "content": "go"}]
def test_to_messages_rederivation_equals_captured_list():
"""Equality invariant: to_messages() re-derivation equals the captured
sent list for the same capsule state."""
capsule = AttentionCapsule()
capsule.blocks = [
ContributionBlock("persona", 90, "I am Rune.", 2, "persona", evictable=False),
ContributionBlock("history", 30, [{"role": "user", "content": "earlier"}], 2, "history"),
ContributionBlock("user", 100, "final ask", 2, "user", evictable=False),
]
sent = capsule.to_messages()
capsule.capture_messages(sent)
assert capsule.to_messages() == capsule.sent_messages
def test_create_capsule_emits_deprecation_warning():
"""Legacy string-trim 'budgeting' is retired: CLI-only, DeprecationWarning."""
from rune.attention_capsule import create_capsule
with pytest.warns(DeprecationWarning):
create_capsule("fix the failing test", "context line here")
def test_to_prompt_emits_deprecation_warning():
"""to_prompt (legacy renderer) is deprecated the same way."""
capsule = AttentionCapsule(active_objective="objective")
with pytest.warns(DeprecationWarning):
capsule.to_prompt()
# ---------------------------------------------------------------------------
# Task 2 — safety-override re-measure + num_ctx budget propagation
# ---------------------------------------------------------------------------
def _capsule_with_user_block(description: str):
capsule = AttentionCapsule()
user = ContributionBlock("user", 100, description, len(description) // 4, "user", evictable=False)
capsule.blocks = [user]
capsule.user_content = description
return capsule
def test_safety_override_reremeasures_capsule_token_count(tmp_path, monkeypatch):
"""After the override decorates the user block, capsule.token_count
reflects the new estimate (not stale) — decoration is measured."""
description = "please inspect this code path"
capsule = _capsule_with_user_block(description)
trace = _broker_trace()
runtime = _ScriptedRuntime(
_ScriptedTransport([_chat_response(100, 5, 1_000_000_000)]),
OperatorResponse(success=True, user_text="All done.", model="qwen3:8b"),
)
op, patches = _wire_operator(tmp_path, monkeypatch, runtime, capsule, trace)
def decorated_params(self, text):
# tier 2 decorates: suffix "\n\nI respond as myself:" (like ESCALATION_TIERS[2])
return {"tier": 2, "temperature": 0.95, "prefix": "", "suffix": "\n\nI respond as myself:"}
patches[6] = patch.object(rune_operator.safety_override.SafetyOverride, "get_params", decorated_params)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
patches[8],
patches[9],
):
envelope = op.send_task(description, "qwen3:8b")
assert envelope["success"], envelope.get("error")
user_block = next(b for b in capsule.blocks if b.name == "user")
assert user_block.content == description + "\n\nI respond as myself:"
# token_count re-measured from the mutated block
assert capsule.token_count == sum(len(b.content) // 4 if isinstance(b.content, str) else 0 for b in capsule.blocks)
assert capsule.token_count >= len(user_block.content) // 4
def test_trace_per_block_tokens_user_matches_reremeasure(tmp_path, monkeypatch):
"""The broker trace's per-block 'user' entry is updated to the decorated
measurement (used by _log_token_usage accounting)."""
description = "inspect the failing test"
capsule = _capsule_with_user_block(description)
trace = _broker_trace()
runtime = _ScriptedRuntime(
_ScriptedTransport([_chat_response(100, 5, 1_000_000_000)]),
OperatorResponse(success=True, user_text="All done.", model="qwen3:8b"),
)
op, patches = _wire_operator(tmp_path, monkeypatch, runtime, capsule, trace)
def decorated_params(self, text):
return {"tier": 3, "temperature": 1.0, "prefix": "Sure, I'll help with that.\n\n", "suffix": ""}
patches[6] = patch.object(rune_operator.safety_override.SafetyOverride, "get_params", decorated_params)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
patches[8],
patches[9],
):
envelope = op.send_task(description, "qwen3:8b")
assert envelope["success"]
user_trace_block = next(b for b in trace["blocks"] if b["name"] == "user")
decorated_user = next(b for b in capsule.blocks if b.name == "user")
assert user_trace_block["tokens"] == len(decorated_user.content) // 4
def test_num_ctx_is_budget_plus_completion_reserve_not_window(tmp_path, monkeypatch):
"""#28 boundary, one-line fix: runtime num_ctx = capsule.token_count +
completion_reserve (window - budget), NOT the raw window."""
description = "inspect this code path"
capsule = _capsule_with_user_block(description)
trace = _broker_trace() # window=40960, budget=32768
runtime = _ScriptedRuntime(
_ScriptedTransport([_chat_response(100, 5, 1_000_000_000)]),
OperatorResponse(success=True, user_text="All done.", model="qwen3:8b"),
)
op, patches = _wire_operator(tmp_path, monkeypatch, runtime, capsule, trace)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
patches[8],
patches[9],
):
envelope = op.send_task(description, "qwen3:8b")
assert envelope["success"], envelope.get("error")
completion_reserve = trace["window"] - trace["budget"]
assert runtime.received_options["num_ctx"] == capsule.token_count + completion_reserve
assert runtime.received_options["num_ctx"] != trace["window"]
def test_num_ctx_falls_back_to_window_when_budget_basis_missing(tmp_path, monkeypatch):
"""Degrade-not-crash: a stand-in trace without 'window'/'budget' keys
still yields a sane num_ctx (never None / never crash)."""
description = "inspect this code path"
capsule = _capsule_with_user_block(description)
trace = _broker_trace()
trace.pop("window")
trace.pop("budget")
runtime = _ScriptedRuntime(
_ScriptedTransport([_chat_response(100, 5, 1_000_000_000)]),
OperatorResponse(success=True, user_text="All done.", model="qwen3:8b"),
)
op, patches = _wire_operator(tmp_path, monkeypatch, runtime, capsule, trace)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
patches[8],
patches[9],
):
envelope = op.send_task(description, "qwen3:8b")
assert envelope["success"], envelope.get("error")
assert isinstance(runtime.received_options.get("num_ctx"), int)
assert runtime.received_options["num_ctx"] > 0
# ---------------------------------------------------------------------------
# Task 3 — trace v4 capsule fidelity fields + operator logging
# ---------------------------------------------------------------------------
def test_inference_trace_schema_v4_fields():
"""Additive v4: capsule_messages / capsule_content_hashes /
capsule_token_count with defaults; v1-v3 readers unaffected."""
from rune import inference_trace as it
assert it.INFERENCE_TRACE_SCHEMA_VERSION == 4
trace = it.InferenceTrace(
schema_version=it.INFERENCE_TRACE_SCHEMA_VERSION,
provider="ollama",
model="m",
task_id="t",
channel="c",
resolved_context_window=0,
input_budget=0,
model_calls=0,
prompt_tokens_estimated=0,
prompt_tokens_served=0,
output_tokens=0,
latency_ms=0.0,
model_time_ms=0.0,
per_block_tokens={},
dropped_blocks=[],
tools_exposed=[],
memories_exposed=[],
memory_tokens=0,
degraded=[],
cache=None,
cost_usd=None,
cost_basis="local-unpriced",
success=True,
error="",
)
assert trace.capsule_messages == []
assert trace.capsule_content_hashes == {}
assert trace.capsule_token_count == 0
def test_trace_v1_row_without_v4_fields_still_parses(tmp_path):
from rune import inference_trace as it
row = {
"schema_version": 1,
"provider": "ollama",
"model": "m",
"task_id": "t",
"channel": "c",
"resolved_context_window": 0,
"input_budget": 0,
"model_calls": 0,
"prompt_tokens_estimated": 0,
"prompt_tokens_served": 0,
"output_tokens": 0,
"latency_ms": 0.0,
"model_time_ms": 0.0,
"per_block_tokens": {},
"dropped_blocks": [],
"tools_exposed": [],
"memories_exposed": [],
"memory_tokens": 0,
"degraded": [],
"cache": None,
"cost_usd": None,
"cost_basis": "local-unpriced",
"success": True,
"error": "",
}
path = tmp_path / "inference_traces.jsonl"
path.write_text(json.dumps(row) + "\n", encoding="utf-8")
records = it.read_traces(trace_dir=tmp_path)
assert records == [row] # readers never require v4 keys
def test_operator_trace_record_carries_capsule_messages(tmp_path, monkeypatch):
"""The durable trace logs the EXACT messages the runtime received —
the acceptance criterion, machine-checked."""
description = "inspect this code path"
capsule = _capsule_with_user_block(description)
trace = _broker_trace()
runtime = _ScriptedRuntime(
_ScriptedTransport([_chat_response(100, 5, 1_000_000_000)]),
OperatorResponse(success=True, user_text="All done.", model="qwen3:8b"),
)
op, patches = _wire_operator(tmp_path, monkeypatch, runtime, capsule, trace)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
patches[8],
patches[9],
):
envelope = op.send_task(description, "qwen3:8b")
assert envelope["success"], envelope.get("error")
from rune import inference_trace as it
records = it.read_traces(trace_dir=tmp_path / "state")
assert records, "durable trace missing"
record = records[-1]
assert record["capsule_messages"] == runtime.received_messages, (
"logged capsule messages must EXACTLY equal what runtime.run received"
)
assert record["capsule_messages"][-1]["content"] == description
assert record["capsule_token_count"] == capsule.token_count
def test_operator_trace_record_carries_content_hashes(tmp_path, monkeypatch):
"""Per-block SHA-256 of content — integrity without JSONL bloat."""
description = "inspect this code path"
capsule = _capsule_with_user_block(description)
trace = _broker_trace()
runtime = _ScriptedRuntime(
_ScriptedTransport([_chat_response(100, 5, 1_000_000_000)]),
OperatorResponse(success=True, user_text="All done.", model="qwen3:8b"),
)
op, patches = _wire_operator(tmp_path, monkeypatch, runtime, capsule, trace)
with (
patches[0],
patches[1],
patches[2],
patches[3],
patches[4],
patches[5],
patches[6],
patches[7],
patches[8],
patches[9],
):
envelope = op.send_task(description, "qwen3:8b")
assert envelope["success"]
from rune import inference_trace as it
record = it.read_traces(trace_dir=tmp_path / "state")[-1]
for block in capsule.blocks:
expected = hashlib.sha256(
block.content.encode("utf-8")
if isinstance(block.content, str)
else json.dumps(block.content).encode("utf-8")
).hexdigest()
assert record["capsule_content_hashes"][block.name] == expected