Back to skills
SKILL.md
061 Architecture 37005839
ASecurityTunacode is a TUI (Terminal User Interface) code agent built with a **layered, component-based architecture**. The system is designed with clear separation of concerns between the presentation layer (UI), the core agent logic, tool capabilities, and configuration management.
- 9 stars
- 0 votes
- 0 copies
- 1 view
- Added October 11, 2026
Works with
Security analysis
100/100npx -y skills add tools-only/X-Skills --skill 061-architecture_37005839 --agent claude-codeAre you the author of 061 Architecture 37005839?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tools-only-061-architecture-37005839)# Tunacode Architecture Documentation
## Overview
Tunacode is a TUI (Terminal User Interface) code agent built with a **layered, component-based architecture**. The system is designed with clear separation of concerns between the presentation layer (UI), the core agent logic, tool capabilities, and configuration management.
**File**: `<repo_root>/src`
**Version**: 0.1.20
**Python Version**: 3.11-3.13
---
## 1. Architectural Pattern
The project follows a **pragmatic layered architecture** with strong component-based design principles:
```
┌─────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ (src/tunacode/ui/) │
│ Built with Textual framework - handles user interaction │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ (src/tunacode/core/) │
│ Agent orchestration, request processing, state management │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Domain Layer │
│ (src/tunacode/tools/) │
│ Tool functions - bash, grep, read_file, etc. │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ (src/tunacode/configuration/, indexing/, lsp/, utils/) │
│ Configuration, indexing, LSP integration, utilities │
└─────────────────────────────────────────────────────────────┘
```
### Key Architectural Principles
1. **UI/Core Decoupling**: The core agent logic is completely independent of the Textual UI, enabling headless execution and testing
2. **Callback-based Communication**: Layers communicate through callbacks rather than direct dependencies
3. **Centralized State Management**: A `StateManager` holds all session state, avoiding scattered state management
4. **Modular Tool System**: Tools are self-contained functions with clear interfaces
5. **Composable System Prompts**: Prompts are composed from smaller sections for maintainability
---
## 2. Component Relationships
### Major Components
```
┌──────────────────────────────────────────────────────────────────┐
│ UI Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ TextualApp │ │ Screens │ │ Renderers │ │
│ │ (app.py) │ │ (setup, │ │ (tools, │ │
│ │ │ │ picker) │ │ panels) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│ │ │
│ requests │ renders │ callbacks
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────┐
│ Core Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Agent │ │ Request │ │ State │ │
│ │ Config │ │Orchestrator │ │ Manager │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│ │ │
│ configures │ orchestrates │ manages
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────┐
│ Tools Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Bash │ │ Grep │ │ Read File │ │
│ │ Tool │ │ Tool │ │ Tool │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Glob │ │ Tool │ │ Decorators │ │
│ │ Tool │ │ Utilities │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│ │ │
│ reads │ uses │ provides
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────┐
│ Configuration Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Models │ │ Defaults │ │ Settings │ │
│ │ Registry │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────────┘
```
### Dependency Graph
```
┌─────────────┐
│ UI │
└──────┬──────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Core │───▶│ Tools │ │ LSP │
└─────┬─────┘ └─────┬─────┘ └───────────┘
│ │
└────────┬───────┘
▼
┌──────────────┐
│Configuration │
└──────────────┘
```
**Note**: Dependency direction is one-way here:
- `core` imports tool functions for orchestration
- tools do not import from `core`
---
## 3. Data Flow
### Complete Request Lifecycle
```
USER INPUT
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 1. INPUT CAPTURE (UI Layer) │
│ - User types in Editor widget (ui/app.py) │
│ - EditorSubmitRequested message posted │
│ - Message placed on request_queue │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 2. REQUEST PROCESSING (Core Layer) │
│ - _request_worker dequeues from request_queue │
│ - Calls process_request() (core/agents/main.py) │
│ - RequestOrchestrator instantiated │
│ - Agent created/retrieved from cache │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 3. AGENT EXECUTION (Core Layer) │
│ - System prompt composed from sections │
│ - Agent.iter() loop begins │
│ - Model request sent to LLM API │
│ - Response streamed back │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 4. TOOL SELECTION (Core Layer) │
│ - Node processor inspects response │
│ - Tool calls extracted from structured output │
│ - tool_callback invoked (ui/repl_support.py) │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 5. TOOL DISPATCH (Core Layer) │
│ - Tool calls registered and batched │
│ - Parallel execution scheduled │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 6. TOOL EXECUTION (Tools Layer) │
│ - Tool function executed (e.g., bash, grep) │
│ - Return value captured │
│ - Results recorded in the run message history │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 7. RESPONSE RENDERING (UI Layer) │
│ - streaming_callback updates streaming_output │
│ - tool_result_callback creates Panel for results │
│ - Final response written to RichLog │
│ - Token usage and cost calculated │
└───────────────────────────────────────────────────────────────┘
│
▼
STATE UPDATE
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 8. STATE MANAGEMENT (Core Layer) │
│ - SessionState messages synced from agent_run history │
│ - Token usage updated │
│ - Context window managed via compaction │
└───────────────────────────────────────────────────────────────┘
```
### Callback Communication Flow
```
┌─────────────────┐ ┌─────────────────┐
│ UI │ │ Core │
│ (TextualApp) │ │ (Orchestrator) │
└────────┬────────┘ └────────┬────────┘
│ │
│ streaming_callback(text) │
│ tool_callback(tool_name) │
│ tool_result_callback(result) │
│ tool_start_callback() │
│------------------------------------>│
│ │
│ │ Execute tool
│ │ Stream response
│ │
│ Update UI widgets │
│ Show confirmation dialog │
│ Render tool output │
│<-------------------------------------│
│ │
```
---
## 4. Design Patterns
### 1. Factory & Singleton Pattern
**Location**: `src/tunacode/core/agents/agent_components/agent_config.py`
```python
def get_or_create_agent(model: ModelName, state_manager: StateManager) -> PydanticAgent:
# Factory: Creates agent instances
# Singleton: Caches agents per model
if model not in _agent_cache:
agent = Agent(
model=model_instance,
system_prompt=system_prompt,
tools=tools_list,
)
_agent_cache[model] = agent
return _agent_cache[model]
```
**Purpose**: Avoid expensive re-initialization of agents while allowing dynamic creation.
### 2. Strategy Pattern
**Location**: `src/tunacode/tools/grep.py`
```python
def grep(..., search_type: Literal["smart", "ripgrep", "python", "hybrid"] = "smart"):
# Dynamically selects search algorithm based on context
if search_type == "smart":
# Choose best algorithm automatically
elif search_type == "ripgrep":
# Use ripgrep
elif search_type == "python":
# Use pure Python
```
**Purpose**: Allow runtime selection of search algorithms based on file count and context.
### 3. Decorator Pattern
**Location**: `src/tunacode/tools/decorators.py`
```python
@file_tool
def read_file(file_path: str) -> str:
# Decorator adds error handling and LSP diagnostics
...
@base_tool
def bash(command: str) -> str:
# Decorator adds error handling and retry logic
...
```
**Purpose**: Add cross-cutting concerns (error handling, retries) without cluttering tool logic.
### 4. Observer Pattern
**Location**: Throughout UI/Core communication
```python
# Core notifies UI of state changes
async def process_request(
streaming_callback: Callable[[str], None],
tool_callback: Callable[[ToolCall], None],
tool_result_callback: Callable[[ToolResult], None],
...
):
# Core (Subject) notifies UI (Observer) via callbacks
await streaming_callback(token)
await tool_callback(tool_call)
await tool_result_callback(result)
```
**Purpose**: Enable UI to react to core events without tight coupling.
### 5. Composite Pattern
**Location**: `src/tunacode/core/agents/delegation_tools.py`
```python
# A tool that is itself an agent
def research_codebase(query: str) -> str:
# Main agent delegates to specialized research agent
research_agent = get_or_create_agent(model, ...)
return await research_agent.run(query)
```
**Purpose**: Enable hierarchical agent organization where agents can delegate to specialized sub-agents.
### 6. State Management Pattern
**Location**: `src/tunacode/core/state.py`
```python
class StateManager:
def __init__(self):
self.session = SessionState(
messages=[],
model=None,
token_usage=TokenUsage(),
...
)
# Centralized state store passed to all components
state_manager = StateManager()
app = TextualReplApp(state_manager=state_manager)
```
**Purpose**: Single source of truth for application state, avoiding scattered state management.
---
## 5. Module Dependencies
### Dependency Hierarchy
```
Level 0 (Foundation):
├── constants
├── types
└── exceptions
Level 1 (Utilities):
├── configuration
│ ├── models (model registry, pricing)
│ ├── defaults (default config)
│ └── settings (API keys, user config)
├── utils
│ ├── config
│ ├── messaging
│ ├── parsing
│ └── system
├── lsp
├── prompting
│ └── sections
└── indexing
Level 2 (Core Logic):
├── core
│ ├── agents (agent orchestration)
│ ├── background (async task management)
│ ├── logging
│ ├── setup
│ └── token_usage
└── tools
├── grep_components
├── prompts
└── utils
Level 3 (Application):
├── ui
│ ├── screens (setup, picker, confirm)
│ ├── renderers (tools, panels)
│ ├── components
│ ├── widgets
│ └── styles
└── services (currently empty)
```
### Import Flow Analysis
**Tightly Coupled**:
- `core` -> `tools` (one-way orchestration dependency)
- `core` imports tool functions for agent configuration and dispatch
**Loosely Coupled**:
- `configuration` - Standalone, only depends on foundation types
- `constants`, `types`, `exceptions` - No internal dependencies
- `indexing`, `lsp`, `prompting` - Limited, specific dependencies
**Highly Coupled to UI**:
- `ui` depends on nearly all modules (core, tools, configuration, indexing, lsp)
### External Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| `textual` | ^4.0.0 | TUI framework |
| `pydantic-ai` | ^1.18.0 | AI agent framework |
| `pydantic` | ^2.12.4 | Data validation |
| `typer` | ^0.15.0 | CLI framework |
| `rich` | ^14.2.0 | Terminal formatting |
| `pathspec` | ^0.12.1 | Gitignore pattern matching |
| `html2text` | ^2024.2.26 | HTML to Markdown conversion |
| `python-Levenshtein` | ^0.21.0 | String similarity |
| `ruff` | ^0.14.0 | Linting and formatting |
| `textual-autocomplete` | ^4.0.6 | Autocomplete widget |
| `defusedxml` | * | Secure XML parsing |
| `prompt_toolkit` | ^3.0.52 | REPL prompt handling |
| `click` | ^8.1.0 | CLI utilities |
---
## 6. Key Architectural Decisions
### Decision 1: UI/Core Decoupling
**Rationale**: Enable headless execution, simplify testing, support future UI implementations
**Implementation**:
- Callback-based communication
- `StateManager` as shared state object
- Core logic has no Textual dependencies
**Files**:
- `<repo_root>/src/tunacode/ui/app.py`
- `<repo_root>/src/tunacode/core/agents/main.py`
### Decision 2: Modular System Prompts
**Rationale**: Make prompts maintainable, composable, and customizable per agent type
**Implementation**:
- Prompts composed from sections in `src/tunacode/core/prompting/sections/`
- `SectionLoader` and `compose_prompt` for assembly
- Different agents use different prompt combinations
**Files**:
- `<repo_root>/src/tunacode/core/prompting/`
- `<repo_root>/src/tunacode/prompts/sections/`
### Decision 3: Agent as Iterator
**Rationale**: Provide clean, pull-based model for consuming agent execution steps
**Implementation**:
- Agent execution exposed as async iterator
- UI consumes steps as they arrive
- Avoids callback hell
**Files**:
- `<repo_root>/src/tunacode/core/agents/main.py`
### Decision 4: Centralized State Management
**Rationale**: Single source of truth, explicit state changes, easier debugging
**Implementation**:
- `SessionState` dataclass holds all session data
- `StateManager` provides access and updates
- State passed to key components
**Files**:
- `<repo_root>/src/tunacode/core/state.py`
---
## 7. Separation of Concerns
### UI Layer (`src/tunacode/ui/`)
**Responsibility**: Presentation and user interaction
**Key Files**:
- `app.py` - Main Textual application
- `screens/` - Setup, picker, and confirmation screens
- `renderers/` - Tool output rendering
- `components/` - Reusable UI components
- `widgets/` - Custom widgets
**Does NOT**:
- Execute tool logic
- Make agent decisions
- Manage business logic
### Core Layer (`src/tunacode/core/`)
**Responsibility**: Agent orchestration and business logic
**Key Files**:
- `agents/main.py` - Request processing entry point
- `agents/agent_config.py` - Agent factory
- `agents/agent_components/node_processor.py` - Response processing
- `state.py` - State management
- `prompting/` - Prompt composition
**Does NOT**:
- Render UI
- Execute file operations directly
- Handle Textual framework details
### Tools Layer (`src/tunacode/tools/`)
**Responsibility**: Agent capabilities and system interactions
**Key Files**:
- `bash.py` - Shell command execution
- `grep.py` - Code search
- `read_file.py` - File reading
**Does NOT**:
- Orchestrate agent flow
- Render output
- Manage UI state
### Configuration Layer (`src/tunacode/configuration/`)
**Responsibility**: Settings and model data
**Key Files**:
- `models.py` - Model registry
- `defaults.py` - Default configuration
- `settings.py` - User settings
**Does NOT**:
- Execute logic
- Manage state
- Interact with UI
---
## 8. Anti-Patterns and Technical Debt
### Circular Dependency: Core ↔ Tools
**Issue**:
- `core` imports tool functions
- Some tools import from `core`
**Impact**:
- Difficult to test in isolation
- Complex initialization order
- Harder to reason about
**Potential Solutions**:
1. Extract shared interfaces to a separate module
2. Use dependency injection
3. Move shared functionality to a utilities module
### Current Locations
**Core → Tools**:
- `<repo_root>/src/tunacode/core/agents/agent_components/agent_config.py`
**Tools → Core**:
- `<repo_root>/src/tunacode/tools/todo.py`
---
## 9. Extension Points
### Adding a New Tool
1. Create tool function in `<repo_root>/src/tunacode/tools/`
2. Decorate with `@file_tool` or `@base_tool`
3. Add to tools list in `agent_config.py`
4. Optionally create custom renderer in `ui/renderers/tools/`
### Adding a New Agent Type
1. Create prompt sections in `<repo_root>/src/tunacode/prompts/sections/`
2. Compose prompt using `compose_prompt()`
3. Configure tools for agent type
4. Add agent factory logic
### Adding a New UI Screen
1. Create screen class in `<repo_root>/src/tunacode/ui/screens/`
2. Integrate with `TextualReplApp`
3. Add navigation logic
---
## 10. Testing Strategy
### Current Test Coverage
Located in `<repo_root>/tests/`:
- Tool decorator tests
- Tool conformance tests
- Compaction tests
- Tool retry logic tests
### Testing Challenges
1. **Circular Dependency**: Makes unit testing difficult
2. **Async Code**: Requires pytest-asyncio
3. **UI Code**: Textual framework requires specialized testing
### Recommended Testing Approach
1. **Unit Tests**: Test tools in isolation
2. **Integration Tests**: Test core orchestration with mock tools
3. **E2E Tests**: Test full request flow with real tools
4. **UI Tests**: Use textual-dev for screen testing
---
## 11. Performance Considerations
### Token Management
- Context window managed via `prune_old_tool_outputs()`
- Token usage tracked per request and session
- Cost calculation based on model pricing
### Caching
- Agent instances cached per model
- Code index cached
- Model registry lazy-loaded
### Async Operations
- All I/O operations are async
- Background tasks for long-running operations
- Non-blocking UI updates
---
## 13. Security Considerations
### Tool Safety
- Tool definitions enforce validation and limits
- Bash commands validated before execution
- File operations enforce size and path checks
### Input Validation
- Pydantic models for all inputs
- Path sanitization for file operations
- Shell command validation
### Dependency Security
- `defusedxml` for secure XML parsing
- Bandit security scanning configured
- Regular dependency updates
---
## Conclusion
Tunacode demonstrates a well-structured, modular architecture with clear separation of concerns. The primary architectural strength is the UI/Core decoupling, enabling multiple UI implementations and headless operation. Core-to-tools dependencies are now one-way, keeping the tool layer isolated from core internals.
The callback-based communication pattern, centralized state management, and composable system prompts all contribute to a maintainable and extensible codebase.
---
**Generated**: 2026-01-04
**Analysis Tool**: Gemini MCP (gemini-2.5-pro)
**Codebase Version**: 0.1.20
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.