Agent context and data stores
Start here if you are asking: what state is the agent looking at right now? or where do I debug the prompt that was sent to the model?This page maps the four in-memory/on-disk stores, how they relate, and where to look when something diverges between shell, gateway, and headless paths.
Quick lookup
| I want to… | Look at… |
|---|---|
| See live session state (integrations, history, metrics) | Session — core/agent_harness/session/state.py |
| See the chat transcript the assistant uses | session.agent.messages (MutableAgentState) |
| See what one turn looked like at turn start | TurnContext.from_session(text, session) |
| Resume or trace a past session | JSONL file — ~/.opensre/sessions/{session_id}.jsonl |
See the system prompt sent to Agent.run | AgentRunResult.final_system_prompt (in-memory) or JSONL role=system entries |
| See the user-facing composed prompt for the assistant | Session JSONL message user rows, or ~/.config/opensre/prompt_log.jsonl (shell only) |
| Understand turn routing (action vs answer) | run_turn — core/agent_harness/agents/turn_orchestrator.py |
Architecture (four stores)
One picture: the four surfaces funnel into one core engine; the four stores (numbered) carry state through the turn; the four prompt builders live in one home; and the assembled prompt is captured for debugging. Rule of thumb| Store | One-line job |
|---|---|
Session | Everything this process remembers |
MutableAgentState (session.agent) | The chat transcript + last tool observation |
TurnContext | A frozen photo of session state at turn start |
| JSONL session file | Durable copy for /resume, /trace, and audit |
Turn flow (one message)
All surfaces call the samerun_turn engine. Gateway uses
Agent.dispatch_message_to_headless_agent with DefaultToolProvider(session, …)
so action tools resolve from the live session integrations each turn (same as
shell).
Glossary (disambiguation)
| Name | What it is not |
|---|---|
MutableAgentState | Not core.agent.Agent state; not investigation AgentState TypedDict |
TurnContext | Not ReplRuntimeContext, GroundingContext, or ActionToolContext |
AgentContextInput | Not a store — selector output from select_agent_context_input() |
Agent.run(agent_context=…) | Not used on live shell/gateway paths yet (tests only); production uses AgentConfig |
PromptRecorder | Not the system prompt — records user prompt + assistant response (shell telemetry) |
Store 1 — Session
File: core/agent_harness/session/state.pyLifecycle:
SessionManager — core/agent_harness/session/manager.py
Composition (not a grab-bag):
| Slice | Type | Role |
|---|---|---|
session.agent | MutableAgentState | Transcript |
session.storage | SessionStorage | JSONL writer |
session.tokens | TokenUsage | Token accounting |
session.metrics | TerminalMetrics | Terminal metrics |
session.resolved_integrations_cache | dict | Integration configs for tools |
SessionResolver before each turn.
Store 2 — MutableAgentState (audit)
File: core/context/state/agent_state.pyAccess:
session.agent (compatibility: session.cli_agent_messages,
session.last_command_observation)
Production API (use these)
| API | Purpose |
|---|---|
.messages / setter | Conversation (role, text) pairs |
.last_observation | Last tool/command output for grounding |
.clear() | Reset on /new |
Unused in production (tests / future only)
| API | Status |
|---|---|
set_system_prompt, set_model, set_*_tools | Never called on live paths |
begin_run / end_run, mark_tool_pending | Never called on live paths |
subscribe(), snapshot() | Test-only |
record_turn() | Orchestrator appends to cli_agent_messages directly instead |
core.agent.Agent does not read MutableAgentState. Tools
and system prompts are assembled per turn via TurnContext + AgentConfig.
Follow-up: slim the class to transcript + observation only (issue #3434).
Store 3 — TurnContext (unified)
File: core/agent_harness/models/turn_context.pyThere is exactly one type — no
ShellTurnContext or surface variants.
Built once per turn:
Session is still passed separately for writes (history, tokens, persistence).
Field reference
| Field | Populated by from_session? | Used by |
|---|---|---|
text | Yes | All agents |
conversation_messages | Yes (capped) | Action + assistant prompts |
configured_integrations | Yes | Prompts, tool availability |
last_state | Yes | Follow-up grounding (RCA) |
last_synthetic_observation_path | Yes | Synthetic failure context |
reasoning_effort | Yes | LLM calls |
last_observation | Yes (session → agent → runtime input) | Assistant grounding |
system_prompt, active_tools, … | Only if select_agent_context_input populated | Agent.run(agent_context=…) tests |
working_directory, terminal_capabilities, … | No (reserved / unused) | Do not rely on these |
MutableAgentState is
never populated with tools/prompt — builders read TurnContext snapshot fields
and assemble AgentConfig separately.
Gather alignment: prefer build_gather_system_prompt_from_turn_context(turn_ctx)
when a snapshot exists; the session-only overload remains for adapters.
Store 4 — JSONL session file
Path:~/.opensre/sessions/{session_id}.jsonlProtocol:
SessionStorage — core/agent_harness/session/types.py
| Entry type | Contents |
|---|---|
session | Header (version, working directory, opensre version) |
message | User/assistant/system chat rows + metadata |
tool_call / tool_result | Tool execution audit |
compaction | Context compaction summary |
investigation_result | RCA output |
custom_message / turn_stub | Turn kind stubs from Session.record |
SessionRepo.load_session → SessionManager.restore_context.
Prompt recording (debug)
Three layers — know which you need:| Layer | What is captured | Where | Surfaces |
|---|---|---|---|
A. AgentRunResult.final_system_prompt | Exact system string after _before_provider_request | In-memory on Agent.run result | Action + gather (Agent.run) |
B. persist_turn_system_prompt | Same system prompt appended to JSONL | message row, role=system, metadata.debug=system_prompt | Action + gather (wired in harness) |
C. append_turn_detail | User prompt + assistant response (+ optional metadata.system_prompt from LlmRunInfo) | Session JSONL message rows | Gateway/headless via DefaultTurnAccounting; shell via PromptRecorder.flush |
D. PromptRecorder | User prompt + response text (telemetry) | ~/.config/opensre/prompt_log.jsonl | Shell only |
Debug cookbook
Find action/gather system prompt in session file:Agent.run:
Verify it yourself
Real session (needs an LLM key). Run a turn, then read the prompt back out of the newest session file — no need to know the session id:kind = action_agent or
gather_agent — the thing this issue said was never recorded.
No key (deterministic). Proves capture → persist → read-back with a fake LLM
and a throwaway session:
Prompt builders (single harness home)
| Phase | Builder | Module |
|---|---|---|
| Action | build_action_system_prompt | core/agent_harness/prompts/ |
| Assistant | build_assistant_system_prompt | core/agent_harness/prompts/ |
| Gather | build_gather_system_prompt / _from_turn_context | core/agent_harness/prompts/gather.py |
| Investigation | build_investigation_system_prompt | tools/investigation/stages/gather_evidence/prompt.py |
invoke_stream(prompt) and does not use Agent.run (see
core/agent_harness/AGENTS.md).
Related docs
core/agent_harness/AGENTS.md— package boundaries, session lifecycle, agent constructiongateway/AGENTS.md— gateway turn handler and per-chat sessions
Follow-ups (issue #3434)
- Slim
MutableAgentStateto transcript + observation only - Wire assistant composed prompt split into system vs user blocks in JSONL
- Route production action/gather through
Agent.run(agent_context=TurnContext)instead of parallelAgentConfigassembly - Populate or remove reserved
TurnContextshell fields (working_directory, etc.)
Tracer