TurnFinalizer¶
File: brain/agent/turn_finalizer.py
Purpose¶
TurnFinalizer consolidates all post-loop side effects for a completed agent
turn into a single method call. After _execute_turn_loop_v2() exits and
state.close_writes() seals the write gate, TurnFinalizer.finalize() runs
seven sequential steps and returns the public TurnResult.
The finalizer is stateless: all mutable state is passed per call. One instance can be reused across many turns and sessions.
Important: finalize() never raises — every sub-step is individually
exception-wrapped so a partial failure (e.g. memory extraction crashing) cannot
prevent the turn result from being returned to the caller.
Construction¶
finalizer = TurnFinalizer(
event_log=event_log,
callbacks=callbacks,
decision_store=decision_store, # None to skip decision records
memory_extractor=memory_extractor, # None to skip memory extraction
autonomous_research_learner=arl, # None to skip
provider=provider, # Used by judge scheduler
prompt_id="agent.profile.default",
prompt_version="1.0.0",
prompt_tags=frozenset({"chat"}),
redact_logs=False,
)
finalize() — Public API¶
result: TurnResult = finalizer.finalize(
session_id="sess_abc",
user_message="What are my open loops?",
final_text="Here are your open loops...",
context_pack={...},
context_hash="abc123",
prompt_messages=[...],
prompt_render_hash="def456",
tool_results=[...],
input_tokens=1234,
output_tokens=567,
elapsed_ms=4200,
show_citations=True,
local_citations=["notes/project.md"],
web_citations=[],
rendered_content_paths=[],
cache_read_tokens=0,
cache_creation_tokens=0,
turn_number=3,
)
Finalization Steps (in order)¶
1. Citation Append¶
Appends up to 8 local citations and 8 web citations to the response text.
Local citation format:
Web citation format:
2. Usage Callback¶
Fires only if either token count is non-zero.
3. Log Assistant Message¶
assistant_message_id = self._event_log.log_chat_message(
session_id, "assistant", text, redact=self._redact_logs
)
Persists the assistant response to the SQLite event log. The returned ID is
used as the source_event_id for the research learner observation.
4. Memory Extraction¶
self._memory_extractor.extract_turn(
session_id=session_id,
user_message=user_message,
assistant_message=text,
tool_results=tool_results,
trace_id=session_id,
)
Skipped if memory_extractor is None. Exception-wrapped.
5. Research Learner Observation¶
self._arl.observe_turn(
session_id=session_id,
user_text=user_message,
assistant_text=text,
source_event_id=f"chat_message:{assistant_message_id}",
)
Skipped if autonomous_research_learner is None. Exception-wrapped.
6. Judge Scheduling¶
If prompt_id is set and the prompt template has associated judges
(e.g. faithfulness, relevance evaluators), schedule_prompt_judges() queues
async LLM-based evaluation of the response quality.
Skipped if prompt_id is empty or the template has no judges.
7. Decision Record¶
self._emit_turn_decision(
session_id, user_message, final_text, context_hash,
tool_results, input_tokens, output_tokens, elapsed_ms, turn_number,
)
Classifies the turn strategy based on which tools were used:
| Tools used | Strategy |
|---|---|
| None | direct_answer |
web_search |
web_augmented |
local_search or get_context_pack |
retrieval_augmented |
| Other tools | tool_assisted |
Emits a RuntimeDecisionRecord to the decision store with decision key
"support.chat.turn".
8. Turn End Callback¶
TurnResult Return¶
return TurnResult(
text=text, # Final response (with citations if show_citations)
local_citations=local_citations,
web_citations=web_citations,
rendered_content_paths=rendered_content_paths,
context_pack_json=context_pack,
input_tokens=input_tokens,
output_tokens=output_tokens,
cache_read_tokens=cache_read_tokens,
cache_creation_tokens=cache_creation_tokens,
)
Helper Functions¶
_append_citations(text, local_citations, web_citations) → str¶
Appends citation lists to the response. Caps at 8 items each to prevent oversized responses.
_messages_to_text(messages) → str¶
Converts the message list to a flat text representation for judge scheduling:
[system] You are SecondBrain...
[user] What are my open loops?
[assistant] Here are your open loops...
_emit_turn_decision(...) (private)¶
Builds and emits a RuntimeDecisionRecord. Exception-wrapped.
_schedule_turn_judges(...) (private)¶
Looks up the prompt template by prompt_id, extracts relevant facts from the
context pack, normalizes messages, and calls schedule_prompt_judges().
Exception-wrapped.