Skip to main content

Agent State

One AgentState TypedDict flows through the entire graph. Every node reads from it and writes back partial updates (deltas).

Fields with reducers (operator.add, _merge_dicts) are merged automatically by LangGraph — nodes return only their own keys and the reducer handles accumulation across the graph and across turns via the checkpointer.

Fields without reducers use last-write-wins — the most recent delta replaces the previous value.

Click any field below to see what it does, who sets it, and its type.

inputpersistedreducerturn-scopedloaded
messagestrinput
Current user message being processed
channelChannelinput
Normalized transport: TEST, WEB, SMS, WHATSAPP, TELEGRAM, VOICE
user_idstr | Noneinput
Optional until auth/session plumbing exists, reserved for ownership checks
session_idstr | Noneinput
Thread identifier used by the persistence layer
installed_skillslist[str]input
Skill names resolved into prompt behavior by the graph
historyAnnotated[list[dict], operator.add]reduceroperator.add
Accumulated via operator.add reducer. Each turn emits only new entries; the checkpointer merges them automatically across turns.
transcriptAnnotated[list[dict], operator.add]reduceroperator.add
Full durable conversation record. Same reducer semantics as history — each turn appends, never reconstructs.
working_memorylist[WorkingMemoryEntry]loaded
Structured dicts: SemanticWorkingMemoryEntry (evidence_quote) and EpisodicWorkingMemoryEntry (summary, themes, is_catch_up). Formatted on demand by prompt builders and CLI.
memory.summarystrloaded
Retrieval summary for diagnostics: hit counts, store sizes, retrieval path
memory.procedural_ruleslist[str]loaded
Style rules from the user's procedural profile — directives that shape response style
memory.proactive_recall_enabledboolloaded
Whether to surface recalled context proactively. From the procedural profile's recall toggle.
progressAnnotated[SessionProgressState, _merge_dicts]reducer_merge_dicts
Uses _merge_dicts reducer so per-turn fields (turn_count, stage) merge with cross-turn fields (exercise_type, exercise_step) from the checkpoint.
progress.turn_countintreducer
Persistent callers derive from checkpoint; one-shot callers count from history
progress.stagestrreducer
Conversation arc: opening, deepening, stabilizing, closing
progress.exercise_typestr | Nonereducer
Multi-turn exercise identifier. Persists via merge reducer across turns without manual carry-forward.
progress.exercise_stepint | Nonereducer
Current step index within the active exercise. Cleared when exercise completes.
crisisCrisisAssessmentturn-scoped
Level 0–3, confidence, reason, needs_crisis_response, needs_clarification
routing.routestrturn-scoped
"crisis" or "therapeutic" — decides which branch runs
routing.modestrturn-scoped
supportive, reflective, clarifying, psychoeducation, guided_exercise, closing
routing.mode_sourcestrturn-scoped
How the mode was selected: keyword, llm, default, or crisis_gate
routing.mode_typeModeTypeturn-scoped
THERAPEUTIC, OPERATIONAL, or CRISIS
routing.modalitystr | Noneturn-scoped
Therapeutic modality: MI, CBT, ACT, DBT, grief, IPT, PFA, or None
response.textstrturn-scoped
Generated reply from whichever node wins the route
response.kindResponseKindturn-scoped
THERAPEUTIC or CRISIS
response.guidancestrturn-scoped
Turn-specific prompt shaping hint
diagnosticsAnnotated[dict, _merge_dicts]reducer_merge_dicts
Per-turn timing and write-count metadata. Uses _merge_dicts reducer so nodes write their own keys independently — no manual dict spreading needed. Parallel extractors write simultaneously without racing.
diagnostics.load_memory_msfloatreducer
Total time for memory retrieval (all 3 namespaces)
diagnostics.crisis_gate_msfloatreducer
Time for crisis classification (regex + optional LLM)
diagnostics.extract_facts_msfloatreducer
Time for semantic fact extraction (LLM call)
diagnostics.extract_procedural_msfloatreducer
Time for procedural rule extraction (LLM call)

Lifecycle

Not all fields survive between turns. Understanding which persist and which are re-derived matters for debugging and eval design.

Input — set by build_initial_state() from AgentInput. Only the current user turn is emitted into history and transcript — the operator.add reducer appends it to the checkpoint's accumulated list.

Reducer-backed — fields with Annotated[..., reducer] that accumulate across the graph and across turns:

  • history / transcriptoperator.add appends new turns. build_initial_state emits [user_turn]; finalize_turn_node emits [assistant_turn]. The checkpointer restores prior turns.
  • progress_merge_dicts merges per-turn fields (turn_count, stage) with cross-turn fields (exercise_type, exercise_step) from the checkpoint. This is how exercise state persists without a manual carry-forward hack.
  • diagnostics_merge_dicts lets all I/O nodes write their own timing/write-count keys independently. Parallel extractors write simultaneously without racing.

Loaded per turnload_memory_node populates working_memory with structured WorkingMemoryEntry dicts (semantic facts + episodic arcs via hybrid RRF retrieval), memory.procedural_rules (full rule set from the user's procedural profile), and memory.proactive_recall_enabled (the recall toggle). These are raw structured data — formatting happens on demand at prompt-build time.

Turn-scoped — reset at the start of each turn, then filled by nodes as the graph executes:

  • crisis — safety assessment (level, confidence, reason, flags) from crisis_gate_node
  • routing.route"therapeutic" or "crisis" from the gate
  • routing.mode — which therapeutic mode was selected (supportive, reflective, clarifying, psychoeducation, guided_exercise, closing)
  • routing.mode_source — how the mode was selected (keyword, llm, default)
  • routing.mode_type — category (therapeutic, operational, crisis)
  • routing.modality — therapeutic modality for this turn (MI, CBT, ACT, DBT, grief, IPT, PFA, or none)
  • response — the generated reply (text, kind, guidance)

Working memory structure

working_memory carries structured WorkingMemoryEntry dicts, not pre-formatted strings. Two entry types:

TypeFieldsExample formatted output
SemanticWorkingMemoryEntrytype="semantic", evidence_quote"Previously noted: I have a sister named Sarah."
EpisodicWorkingMemoryEntrytype="episodic", summary, primary_themes, is_catch_up"Last session (grief): talked about loss after my dog died."

Formatting happens via format_working_memory_entries() at three surfaces: the therapeutic prompt builder, the dispatcher prompt, and the CLI's context panel. Legacy str entries from older checkpoints pass through unchanged.

Public contract

The external API sees AgentInput and AgentOutput — not AgentState. The state is internal to the graph.

ModelKey fields
AgentInputmessage, channel, user_id, session_id, history, working_memory, installed_skills
AgentOutputresponse_text, response_type, crisis, mode, mode_type, mode_source, should_persist_memory, diagnostics
CrisisAssessmentlevel (0-3), confidence, reason, needs_crisis_response, needs_clarification
Messagerole, content, mode (`str

Runtime context

WorkflowContext is a frozen dataclass (@dataclass(slots=True, frozen=True)) injected via runtime.context. All nodes access it via attribute access (runtime.context.llm_client), not dict access.

FieldTypeDefault
llm_client`BaseLLMClientNone`
memory_storeMemoryStorerequired
crisis_log_backendCrisisLogBackendrequired
memory_modeMemoryModerequired
embedding_provider`EmbeddingProviderNone`

Key files

FileWhat it defines
agent/state.pyAgentState TypedDict with reducer annotations (operator.add, _merge_dicts)
agent/working_memory.pyWorkingMemoryEntry types, factory functions, format_working_memory_entry/entries
agent/models.pyAgentInput, AgentOutput, Message, CrisisAssessment, Channel, MessageRole, stream event types
agent/graph.pybuild_initial_state() (input -> state), state_to_output() (state -> output)
agent/runtime_context.pyWorkflowContext frozen dataclass — runtime dependencies via runtime.context