Skip to content

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

if show_citations:
    text = _append_citations(text, local_citations, web_citations)

Appends up to 8 local citations and 8 web citations to the response text.

Local citation format:

Local citations:
- notes/project.md
- docs/meeting-notes.md

Web citation format:

Web citations:
- Title of page: https://example.com/page

2. Usage Callback

self._callbacks.on_usage(input_tokens, output_tokens)

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

self._schedule_turn_judges(session_id, user_message, context_pack, response, ...)

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

self._callbacks.on_turn_end(session_id)

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.