Core Layer
Package: src/tunacode/core/
What
The engine. Takes a user message, routes it through a tinyagent Agent, handles streaming events, manages compaction, tracks tool calls, and persists session state.
Sub-Packages
agents/ -- Agent Loop
| File |
Purpose |
main.py |
RequestOrchestrator -- the main request lifecycle. process_request() is the public entry point. Handles: history coercion, pre-request compaction, streaming event dispatch, abort cleanup, empty-response intervention, context-overflow retry. |
agent_components/__init__.py |
Re-exports from sub-modules. |
agent_components/agent_config.py |
get_or_create_agent() -- builds or retrieves a cached tinyagent Agent. Configures: system prompt, tools, model, stream function, API key resolver, compaction transform. invalidate_agent_cache() clears both module and session caches after abort/timeout. _build_tools() constructs the tool list (bash, discover, read_file, hashline_edit, web_fetch, write_file). Validation functions: _coerce_request_delay(), _coerce_global_request_timeout(), _compute_agent_version(). |
agent_components/agent_helpers.py |
Human-readable tool descriptions for UI panels. create_empty_response_message() builds the intervention prompt when the model returns nothing. |
agent_components/state_transition.py |
AgentStateMachine -- thread-safe FSM with states: USER_INPUT -> ASSISTANT -> TOOL_EXECUTION -> RESPONSE. AGENT_TRANSITION_RULES defines valid edges. |
resume/sanitize.py |
Cleans persisted session messages for safe resume (removes dangling tool calls, fixes structural violations). |
resume/sanitize_debug.py |
Debug instrumentation for sanitization. |
compaction/ -- Context Window Management
| File |
Purpose |
controller.py |
CompactionController -- threshold check, force-compact, summary injection, compaction record management. get_or_create_compaction_controller() returns the session-scoped singleton. apply_compaction_messages() writes compacted history back to session. |
summarizer.py |
ContextSummarizer -- calculates retention boundaries, serializes messages to text, generates summaries via a pluggable SummaryGenerator callback. |
prompts.py |
Prompt templates for fresh and iterative summarization. |
types.py |
CompactionOutcome (status + reason + messages), CompactionRecord (summary + token counts + compaction history). Status/reason string constants. |
session/ -- State Persistence
| File |
Purpose |
state.py |
SessionState dataclass -- the single container for all mutable state (config, agents, conversation, runtime, usage, compaction, recursion tracking). StateManager -- singleton that owns a SessionState, loads user config, and provides save_session() / load_session() / list_sessions(). |
logging/ -- Structured Logging
| File |
Purpose |
manager.py |
get_logger() returns the singleton structured logger. Supports a TUI callback for rendering log entries in the chat. |
handlers.py |
Log handlers (file, TUI). |
levels.py |
Custom log levels (lifecycle, debug, info, warning, error). |
records.py |
Structured log record types. |
types/ -- Core Protocols
| File |
Purpose |
__init__.py |
Re-exports everything below. |
state.py |
SessionStateProtocol and StateManagerProtocol -- structural typing contracts that break circular imports between session and agents. |
state_structures.py |
ConversationState, TaskState, RuntimeState, UsageState -- decomposed sub-states slotted into SessionState. |
agent_state.py |
AgentState enum (USER_INPUT, ASSISTANT, TOOL_EXECUTION, RESPONSE). ResponseState dataclass for completion tracking. |
tool_registry.py |
ToolCallRegistry -- ordered registry tracking each tool call through PENDING -> RUNNING -> COMPLETED/FAILED/CANCELLED. |
Other
| File |
Purpose |
debug/usage_trace.py |
log_usage_update() -- structured logging of per-request usage metrics. |
ui_api/ |
Bridge between core and UI. See ui/ui.md for details. |
How
Request Lifecycle
User types message
|
v
TextualReplApp._process_request(message)
|
v
process_request(message, model, state_manager, callbacks...)
|
v
RequestOrchestrator.run()
|-- _initialize_request() reset counters, generate request_id
|-- get_or_create_agent() build/cache tinyagent Agent
|-- _coerce_tinyagent_history() validate session messages are dicts
|-- _compact_history_for_request() threshold check + summarize if needed
|-- agent.replace_messages() load compacted history into agent
|-- _run_stream(agent, ...) main event loop
| |
| | async for event in agent.stream(message):
| | message_update -> streaming_callback (UI delta)
| | message_end -> parse usage, update session totals
| | tool_execution_start -> register tool, notify UI
| | tool_execution_end -> mark complete/failed, notify UI
| | turn_end -> increment iteration, enforce max
| | agent_end -> persist messages to session
| |
|-- _retry_after_context_overflow_if_needed()
| force-compact and retry once if API returns context_length_exceeded
|
v
Return to UI for rendering
Compaction Flow
CompactionController.check_and_compact(messages, max_tokens)
|-- should_compact? estimate_tokens vs (max_tokens - reserve - keep_recent)
| no -> return skip outcome
| yes -> ContextSummarizer
| |-- calculate_retention_boundary (walk backward, find safe split)
| |-- serialize_messages (to text transcript)
| |-- _summary_generator (call LLM with summarize prompt)
| |-- return summary string
|-- update CompactionRecord on session
|-- return CompactionOutcome(status=compacted, messages=retained)
Session Persistence
StateManager.save_session() serializes to JSON:
- Messages (must be tinyagent dicts; non-dict = hard error)
- Compaction record
- Usage totals
- Model, project_id, timestamps
StateManager.load_session() deserializes and separates thought entries from message history.
System Prompt
The system prompt defines TunaCode's identity and operational rules for the tinyagent framework.
Location: src/tunacode/prompts/system_prompt.md
Loading mechanism: load_system_prompt() in agent_config.py reads the markdown file at runtime and appends dynamic context from load_tunacode_context().
Dynamic context: load_tunacode_context() loads the user's AGENTS.md guide file (cached) and injects it into the prompt under the <user_context> section.
Tool philosophy: Tools are described by purpose and intent, not by function signature. The tinyagent framework provides JSON schemas separately. This keeps the prompt focused on when and why to use each tool:
| Tool |
Purpose Description |
discover |
Natural-language code search and repository exploration |
read_file |
Read file contents with content-hash tagged lines |
hashline_edit |
Edit existing file using hash-validated line references |
write_file |
Create a new file (fails if exists; read first, then hashline_edit) |
bash |
Execute shell commands for tests, linting, git, builds |
web_fetch |
Fetch public web content as readable text |
Agent version hashing: _compute_agent_version() generates a cache key from configuration that affects agent behavior: max_retries, tool_strict_validation, request_delay, global_request_timeout, max_tokens.
Why
The RequestOrchestrator class exists to keep the streaming event loop testable and the callback wiring explicit. Each event type has its own handler method -- no giant switch statement.
Compaction is request-scoped (one compaction per request at most) to avoid compacting the same history repeatedly when the model makes multiple turns.
The StateManagerProtocol breaks the circular dependency between session state and agent creation -- agents need state, state stores agents.
tinyagent provides the agent framework (migrated from pydantic-ai), handling the underlying event streaming, tool schema generation, and message protocol conversion.
1---2name: core-layer3description: The engine. Takes a user message, routes it through a tinyagent Agent, handles streaming events, manages compaction, tracks tool calls, and persists session state.4---56# Core Layer78**Package:** `src/tunacode/core/`910## What1112The engine. Takes a user message, routes it through a tinyagent `Agent`, handles streaming events, manages compaction, tracks tool calls, and persists session state.1314## Sub-Packages1516### agents/ -- Agent Loop1718| File | Purpose |19|------|---------|20| `main.py` | `RequestOrchestrator` -- the main request lifecycle. `process_request()` is the public entry point. Handles: history coercion, pre-request compaction, streaming event dispatch, abort cleanup, empty-response intervention, context-overflow retry. |21| `agent_components/__init__.py` | Re-exports from sub-modules. |22| `agent_components/agent_config.py` | `get_or_create_agent()` -- builds or retrieves a cached tinyagent `Agent`. Configures: system prompt, tools, model, stream function, API key resolver, compaction transform. `invalidate_agent_cache()` clears both module and session caches after abort/timeout. `_build_tools()` constructs the tool list (bash, discover, read_file, hashline_edit, web_fetch, write_file). Validation functions: `_coerce_request_delay()`, `_coerce_global_request_timeout()`, `_compute_agent_version()`. |23| `agent_components/agent_helpers.py` | Human-readable tool descriptions for UI panels. `create_empty_response_message()` builds the intervention prompt when the model returns nothing. |24| `agent_components/state_transition.py` | `AgentStateMachine` -- thread-safe FSM with states: `USER_INPUT -> ASSISTANT -> TOOL_EXECUTION -> RESPONSE`. `AGENT_TRANSITION_RULES` defines valid edges. |25| `resume/sanitize.py` | Cleans persisted session messages for safe resume (removes dangling tool calls, fixes structural violations). |26| `resume/sanitize_debug.py` | Debug instrumentation for sanitization. |2728### compaction/ -- Context Window Management2930| File | Purpose |31|------|---------|32| `controller.py` | `CompactionController` -- threshold check, force-compact, summary injection, compaction record management. `get_or_create_compaction_controller()` returns the session-scoped singleton. `apply_compaction_messages()` writes compacted history back to session. |33| `summarizer.py` | `ContextSummarizer` -- calculates retention boundaries, serializes messages to text, generates summaries via a pluggable `SummaryGenerator` callback. |34| `prompts.py` | Prompt templates for fresh and iterative summarization. |35| `types.py` | `CompactionOutcome` (status + reason + messages), `CompactionRecord` (summary + token counts + compaction history). Status/reason string constants. |3637### session/ -- State Persistence3839| File | Purpose |40|------|---------|41| `state.py` | `SessionState` dataclass -- the single container for all mutable state (config, agents, conversation, runtime, usage, compaction, recursion tracking). `StateManager` -- singleton that owns a `SessionState`, loads user config, and provides `save_session()` / `load_session()` / `list_sessions()`. |4243### logging/ -- Structured Logging4445| File | Purpose |46|------|---------|47| `manager.py` | `get_logger()` returns the singleton structured logger. Supports a TUI callback for rendering log entries in the chat. |48| `handlers.py` | Log handlers (file, TUI). |49| `levels.py` | Custom log levels (`lifecycle`, `debug`, `info`, `warning`, `error`). |50| `records.py` | Structured log record types. |5152### types/ -- Core Protocols5354| File | Purpose |55|------|---------|56| `__init__.py` | Re-exports everything below. |57| `state.py` | `SessionStateProtocol` and `StateManagerProtocol` -- structural typing contracts that break circular imports between session and agents. |58| `state_structures.py` | `ConversationState`, `TaskState`, `RuntimeState`, `UsageState` -- decomposed sub-states slotted into `SessionState`. |59| `agent_state.py` | `AgentState` enum (`USER_INPUT`, `ASSISTANT`, `TOOL_EXECUTION`, `RESPONSE`). `ResponseState` dataclass for completion tracking. |60| `tool_registry.py` | `ToolCallRegistry` -- ordered registry tracking each tool call through `PENDING -> RUNNING -> COMPLETED/FAILED/CANCELLED`. |6162### Other6364| File | Purpose |65|------|---------|66| `debug/usage_trace.py` | `log_usage_update()` -- structured logging of per-request usage metrics. |67| `ui_api/` | Bridge between core and UI. See [ui/ui.md](../ui/ui.md) for details. |6869## How7071### Request Lifecycle7273```74User types message75 |76 v77TextualReplApp._process_request(message)78 |79 v80process_request(message, model, state_manager, callbacks...)81 |82 v83RequestOrchestrator.run()84 |-- _initialize_request() reset counters, generate request_id85 |-- get_or_create_agent() build/cache tinyagent Agent86 |-- _coerce_tinyagent_history() validate session messages are dicts87 |-- _compact_history_for_request() threshold check + summarize if needed88 |-- agent.replace_messages() load compacted history into agent89 |-- _run_stream(agent, ...) main event loop90 | |91 | | async for event in agent.stream(message):92 | | message_update -> streaming_callback (UI delta)93 | | message_end -> parse usage, update session totals94 | | tool_execution_start -> register tool, notify UI95 | | tool_execution_end -> mark complete/failed, notify UI96 | | turn_end -> increment iteration, enforce max97 | | agent_end -> persist messages to session98 | |99 |-- _retry_after_context_overflow_if_needed()100 | force-compact and retry once if API returns context_length_exceeded101 |102 v103Return to UI for rendering104```105106### Compaction Flow107108```109CompactionController.check_and_compact(messages, max_tokens)110 |-- should_compact? estimate_tokens vs (max_tokens - reserve - keep_recent)111 | no -> return skip outcome112 | yes -> ContextSummarizer113 | |-- calculate_retention_boundary (walk backward, find safe split)114 | |-- serialize_messages (to text transcript)115 | |-- _summary_generator (call LLM with summarize prompt)116 | |-- return summary string117 |-- update CompactionRecord on session118 |-- return CompactionOutcome(status=compacted, messages=retained)119```120121### Session Persistence122123`StateManager.save_session()` serializes to JSON:124- Messages (must be tinyagent dicts; non-dict = hard error)125- Compaction record126- Usage totals127- Model, project_id, timestamps128129`StateManager.load_session()` deserializes and separates thought entries from message history.130131### System Prompt132133The system prompt defines TunaCode's identity and operational rules for the tinyagent framework.134135**Location:** `src/tunacode/prompts/system_prompt.md`136137**Loading mechanism:** `load_system_prompt()` in `agent_config.py` reads the markdown file at runtime and appends dynamic context from `load_tunacode_context()`.138139**Dynamic context:** `load_tunacode_context()` loads the user's `AGENTS.md` guide file (cached) and injects it into the prompt under the `<user_context>` section.140141**Tool philosophy:** Tools are described by purpose and intent, not by function signature. The tinyagent framework provides JSON schemas separately. This keeps the prompt focused on *when* and *why* to use each tool:142143| Tool | Purpose Description |144|------|---------------------|145| `discover` | Natural-language code search and repository exploration |146| `read_file` | Read file contents with content-hash tagged lines |147| `hashline_edit` | Edit existing file using hash-validated line references |148| `write_file` | Create a new file (fails if exists; read first, then hashline_edit) |149| `bash` | Execute shell commands for tests, linting, git, builds |150| `web_fetch` | Fetch public web content as readable text |151152**Agent version hashing:** `_compute_agent_version()` generates a cache key from configuration that affects agent behavior: `max_retries`, `tool_strict_validation`, `request_delay`, `global_request_timeout`, `max_tokens`.153154## Why155156The `RequestOrchestrator` class exists to keep the streaming event loop testable and the callback wiring explicit. Each event type has its own handler method -- no giant switch statement.157158Compaction is request-scoped (one compaction per request at most) to avoid compacting the same history repeatedly when the model makes multiple turns.159160The `StateManagerProtocol` breaks the circular dependency between session state and agent creation -- agents need state, state stores agents.161162tinyagent provides the agent framework (migrated from pydantic-ai), handling the underlying event streaming, tool schema generation, and message protocol conversion.