TurnRuntimeState¶
File: brain/agent/turn_state.py
Purpose¶
TurnRuntimeState is the single mutable container for all state scoped to one
agent turn. It is created at the start of run_turn(), passed as an explicit
parameter to every collaborator that needs to read or write turn data, and
discarded when the turn ends.
Because the harness instance itself holds no mutable per-turn state, concurrent or sequential turns cannot contaminate each other.
Construction¶
All other fields are initialised to safe empty defaults.
Fields¶
Token Accounting¶
state.input_tokens: int # Total input tokens across all LLM calls
state.output_tokens: int # Total output tokens
state.cache_read_tokens: int # Provider cache read tokens
state.cache_creation_tokens: int
Updated via state.add_tokens(input_tokens=..., output_tokens=..., ...).
Citations¶
state.citations: CitationAccumulator
.local: set[str] # Vault anchor strings (e.g. "notes/paris.md")
.web: list[dict[str, Any]] # Web source dicts
.rendered_paths: list[str] # Paths to rendered web content files
Populated by harness._apply_outcome_to_state() after each tool call.
Tool Tracking¶
state.tool_trace: dict[str, ToolTraceEntry] # trace_id → entry
state.tool_results: list[dict[str, Any]] # Lightweight feed for ReflectionEngine
state.blocked_tool_names: set[str] # Tools blocked for the rest of this turn
tool_trace is the thread-safe late-write gate. tool_results is written only
from the main thread after the complete_trace() gate returns True.
Validation Repair¶
Tracks how many ToolArgValidator repair attempts have been made per call ID.
Once the count reaches MAX_REPAIR_ATTEMPTS, the call is denied.
Turn Outcome¶
state.final_text: str # The LLM's final text response
state.timed_out: bool # True if the turn expired before producing a response
state.prompt_render_hash: str # Per-turn render hash set by TurnPreparer
state.context_was_compacted: bool # True if ContextCompactor ran this turn
Budget Reference¶
Thread-Safe APIs¶
Tool Trace Lifecycle¶
The trace is the mechanism that prevents late-arriving tool threads from corrupting the turn.
state.set_trace_running(trace_id, tool_name)
# Called by BoundedToolExecutor before submitting the tool thread.
# Creates a ToolTraceEntry(status="running").
state.mark_trace_timed_out(trace_id)
# Called by the harness when future.result(timeout=...) raises TimeoutError.
# Marks the entry status="timed_out".
ok = state.complete_trace(trace_id, latency_ms=1234, retryable=True)
# Called by the tool thread when execution finishes.
# If status was "timed_out" → marks as "timed_out_late", returns False.
# If status was "running" → marks as "completed", returns True.
# Callers must check the return value and suppress side effects if False.
Race condition handled:
Main thread Tool thread
──────────────────────────── ──────────────────────────────
set_trace_running()
submit(future)
... executing tool ...
future.result(timeout=20s)
→ TimeoutError
mark_trace_timed_out()
return ToolTimeout outcome
complete_trace() → False ← suppressed
(no event log write, no tool_results append)
Late-Write Gate¶
state.is_accepting_writes() # → bool: False after close_writes()
state.close_writes() # Called by harness after the loop exits
Any background thread that completes after close_writes() will find
is_accepting_writes() == False and must skip all side effects.
Helper Types¶
CitationAccumulator¶
@dataclass
class CitationAccumulator:
local: set[str] # Vault anchors
web: list[dict[str, Any]] # Web source objects
rendered_paths: list[str] # Rendered web content file paths
Collected incrementally during the turn. Handed to TurnFinalizer.finalize()
at the end.
ToolTraceEntry¶
@dataclass
class ToolTraceEntry:
tool_name: str
status: str # "running" | "completed" | "timed_out" | "timed_out_late"
trace_id: str
latency_ms: int
retryable: bool
Lightweight record used only for late-write suppression. Not persisted.