Internals¶
This page covers Attocode's internal systems: the agent state machine, error hierarchy, undo system, rules loading, LSP integration, and shared state for multi-agent coordination.
Agent State Machine¶
The agent uses a formal finite state machine with 7 lifecycle states:
stateDiagram-v2
[*] --> IDLE
IDLE --> INITIALIZING
INITIALIZING --> RUNNING
INITIALIZING --> ERROR
RUNNING --> PAUSED
RUNNING --> COMPLETING
RUNNING --> ERROR
PAUSED --> RUNNING
PAUSED --> COMPLETING
PAUSED --> ERROR
COMPLETING --> IDLE
COMPLETING --> ERROR
ERROR --> IDLE
IDLE --> DISPOSED
INITIALIZING --> DISPOSED
RUNNING --> DISPOSED
PAUSED --> DISPOSED
COMPLETING --> DISPOSED
ERROR --> DISPOSED
States¶
| State | Description |
|---|---|
IDLE |
Agent created but not started, or completed a run |
INITIALIZING |
Loading config, tools, providers, features |
RUNNING |
Actively processing --- making LLM calls and executing tools |
PAUSED |
Temporarily suspended (e.g., waiting for approval) |
COMPLETING |
Wrapping up --- generating closure report |
ERROR |
An error occurred; can recover to IDLE |
DISPOSED |
Permanently shut down; no further transitions possible |
Valid Transitions¶
The state machine enforces 18 valid transitions. Invalid transitions raise InvalidTransitionError with from_state and to_state attributes.
Convenience Methods¶
machine.initialize() # IDLE → INITIALIZING
machine.start() # INITIALIZING → RUNNING
machine.pause() # RUNNING → PAUSED
machine.resume() # PAUSED → RUNNING
machine.complete() # RUNNING/PAUSED → COMPLETING
machine.finish() # COMPLETING → IDLE
machine.fail(error) # Any → ERROR
machine.dispose() # Any → DISPOSED
Transition Listeners¶
def on_state_change(from_state, to_state, metadata):
print(f"Agent: {from_state} → {to_state}")
machine.on_transition(on_state_change)
Listeners receive (from_state, to_state, metadata). Exceptions in listeners are caught silently --- listeners cannot prevent transitions.
The history property returns a list of (from_state, to_state) tuples for debugging.
Error Hierarchy¶
All agent errors extend from AgentError, which carries a category, retryable flag, and details dict:
AgentError (base)
├── LLMError — Empty response, malformed output (retryable)
├── ProviderError — Rate limit, network, auth (retryable)
│ Fields: provider, status_code
├── ToolError — Tool execution failure (not retryable)
│ ├── ToolNotFoundError
│ └── ToolTimeoutError
├── BudgetExhaustedError — Token/cost/time limit exceeded
├── CancellationError — User or system abort
├── PermissionDeniedError — Tool blocked by policy
│ Field: tool_name
└── ConfigurationError — Invalid/missing settings
Error Categories¶
The ErrorCategory enum enables filtering and specialized handling:
| Category | Typical Errors |
|---|---|
LLM |
Empty responses, malformed JSON output |
PROVIDER |
Rate limits, network errors, authentication failures |
TOOL |
File not found, command timeout, execution failure |
BUDGET |
Token/cost/duration limits exceeded |
CANCELLATION |
User pressed Ctrl+C, system abort |
PERMISSION |
Tool blocked by policy engine |
CONFIGURATION |
Missing API key, invalid config file |
INTERNAL |
Unexpected errors |
Retryable Errors¶
The retryable flag guides the execution loop:
| Error Type | Retryable | Action |
|---|---|---|
LLMError |
Yes | Retry the LLM call |
ProviderError |
Yes | Retry or fall back to next provider |
ToolError |
No | Report to agent, let it try a different approach |
BudgetExhaustedError |
No | Stop execution or request extension |
CancellationError |
No | Stop immediately |
PermissionDeniedError |
No | Report denial, agent may ask user |
ConfigurationError |
No | Stop and report config issue |
Undo System¶
The /undo and /diff commands are backed by FileChangeTracker, which records before/after snapshots of every file modification.
How It Works¶
Every time the agent writes or edits a file, the tracker stores:
- File path
- Content before the change (full snapshot)
- Content after the change
- Tool name, timestamp, iteration number
- Optional description
Undo Commands¶
/undo # Undo the most recent file change
/undo turn # Undo all changes in the current iteration
/undo file src/app.py # Undo the most recent change to a specific file
/diff # Show pending (non-undone) changes
Undo Semantics¶
| Change Type | Undo Action |
|---|---|
| File created | Delete the file from disk |
| File deleted | Restore from before_content |
| File modified | Write before_content back |
Each change can only be undone once --- setting undone=True prevents double-undo. History is capped at 100 entries to prevent unbounded memory growth.
Rules System¶
Rules are project-specific or user-specific instructions injected into the agent's system prompt.
File Locations¶
Rules are loaded from Markdown files with this priority (highest wins):
| Priority | Path | Scope |
|---|---|---|
| 3 (highest) | .attocode/rules.md |
Project-level |
| 2 | ~/.attocode/rules.md |
User-level |
| 1 (fallback) | .agent/rules.md |
Legacy project-level |
Format¶
Rules files are plain Markdown. The entire content is injected into the system prompt:
# Project Rules
- Always use `pytest` for testing, never `unittest`
- Prefer dataclasses over plain dicts for structured data
- All public functions must have type annotations
- Never commit directly to main --- use feature branches
Behavior¶
- Rules load lazily on first access
- Empty files are silently skipped
- Read errors are caught and ignored (never fails the agent)
- Multiple rules files are joined with
\n\n---\n\nseparator - UTF-8 with
errors='replace'for malformed files
LSP Integration¶
Attocode includes an async Language Server Protocol client for code navigation, completions, hover info, and diagnostics.
Built-in Server Configurations¶
| Language | Server | Extensions |
|---|---|---|
| TypeScript | typescript-language-server |
.ts, .tsx, .js, .jsx, .mjs, .cjs |
| Python | pyright-langserver |
.py, .pyi |
| Rust | rust-analyzer |
.rs |
| Go | gopls |
.go |
| JSON | vscode-json-language-server |
.json, .jsonc |
Auto-Detection¶
The LSP manager detects which servers to start based on project files:
| Project File | Server Started |
|---|---|
package.json or tsconfig.json |
TypeScript |
pyproject.toml or requirements.txt |
Python |
Cargo.toml |
Rust |
go.mod |
Go |
Capabilities¶
| Method | Description |
|---|---|
get_definition(file, line, col) |
Jump to symbol definition |
get_completions(file, line, col) |
Autocomplete suggestions |
get_hover(file, line, col) |
Type and documentation popup |
get_references(file, line, col) |
Find all usages of a symbol |
get_diagnostics(file) |
Cached errors and warnings |
The /lsp Command¶
Displays which language servers are running, their status, and capabilities.
Protocol Details¶
The client communicates with language servers over JSON-RPC 2.0 via stdio with Content-Length header framing. Each server runs as an async subprocess with a background read loop for notifications.
Shared State¶
In swarm mode, multiple worker agents need to coordinate. The shared/ module provides thread-safe state for cross-agent communication.
Shared Context State¶
Thread-safe pool for cross-worker failure tracking and reference sharing:
shared = SharedContextState(config=SharedContextConfig(
max_references=200,
max_failures_per_worker=50,
))
| Method | Description |
|---|---|
record_failure(worker_id, input, error) |
Track a worker's failure |
get_failure_context(max_failures) |
Formatted text for LLM injection |
get_failure_insights() |
Extracted actionable rules |
add_references(refs) |
Pool compaction references (deduped) |
search_references(query) |
Substring search on pooled refs |
Workers share failure context so the same mistakes aren't repeated across agents.
Shared Economics State¶
Cross-worker doom loop detection:
When the same tool+args fingerprint appears across multiple workers exceeding the global threshold (default 10), it's flagged as a global doom loop. This catches systemic issues like an API endpoint being down that affects all workers.
Worker Budget Tracker¶
Per-worker budget enforcement that reports to shared state:
tracker = WorkerBudgetTracker(config=WorkerBudgetConfig(
max_tokens=300_000,
max_iterations=50,
doom_loop_threshold=3,
))
Uses MD5 fingerprinting of {"tool": name, "args": args} for doom loop detection. Tracks the last 20 fingerprints with a local threshold of 3 consecutive identical calls.
Shared Context Engine¶
Assembles system prompts for swarm workers using shared state:
engine = SharedContextEngine(shared_context, config=SharedContextEngineConfig(
max_failure_context=10,
include_insights=True,
include_references=True,
))
prompt = engine.build_worker_system_prompt(task)
The prompt includes a shared prefix (for KV-cache alignment across workers), failure guidance from cross-worker failures, and a goal recitation block with task metadata.
Persistence¶
Shared state supports checkpointing via two persistence adapters:
| Adapter | Storage | Use Case |
|---|---|---|
JSONFilePersistenceAdapter |
.json files in a directory |
Development, debugging |
SQLitePersistenceAdapter |
SQLite WAL-mode database | Production, concurrent access |
Both implement the same PersistenceAdapter protocol with save(), load(), list_keys(), delete(), and exists() methods.
Related Pages¶
- Architecture --- Overall system design
- Budget System --- Token budgets and economics
- Advanced Features --- Permission modes, plan mode
- Extending Attocode --- Adding custom tools and providers