Skip to content

Architecture

This section is for people who want to understand letscode — and especially for those who want to extend it. Each page commits to contracts that should drift less than the code.

  • Agent loop


    Event-driven async generator, parallel tool execution, cancellation, retries, the event sequence per turn.

  • Extension model


    The five extension types, the ten lifecycle hooks, the stable-surface contract, the versioning rule.

Layers

letscode is a three-layer Python application. Each layer is independently usable.

flowchart TD
    subgraph "Frontend layer"
        Basic[Basic TUI<br/>prompt_toolkit + rich]
        Future[Plugin frontends<br/>Textual / web / RPC]
    end
    subgraph "Agent loop layer"
        Loop[agent_loop<br/>AsyncIterator&lt;Event&gt;]
        State[AgentState<br/>messages, tools, queues]
        Hooks[Lifecycle hooks<br/>before/after_tool_call<br/>transform_context<br/>convert_to_llm<br/>render_custom_message<br/>system_prompt<br/>on_event]
    end
    subgraph "LLM client layer"
        Client[LLMClient<br/>openai.AsyncOpenAI wrapper]
    end
    Basic --> Loop
    Future -.->|same contract| Loop
    Loop --> Hooks
    Loop --> State
    Loop --> Client
    Client --> Provider[(OpenAI Chat Completions<br/>endpoint)]
    Plugins[(Pluggy + entry points<br/>tools · commands · skills · frontends · hooks)] -.extend.-> Loop
    Plugins -.extend.-> Basic

The frontend can be the basic terminal one or any plugin-supplied alternative. The agent loop is provider-agnostic by design — the LLM client is the only layer that knows about OpenAI's wire format.

Functional core / imperative shell

Side effects (LLM I/O, file I/O, subprocess) live at the edges. The agent loop is pure-ish: it consumes messages and tools, produces events. State mutation happens in controlled places (the loop's own structure, the steering / follow-up queues).

This shape makes the loop testable end-to-end without ever calling a real LLM — see tests/_helpers.py for the FakeLLMClient that drives the same event stream from canned chunks.

Where the contracts live

  • Type signatures for messages, events, tools, hooks → src/letscode/agent/*.py. 02-design.md is the design rationale.
  • Stable surface for pluginsextension model §4.
  • CLI flags and env varsconfiguration.
  • Session JSONL formatsessions.

If a contract here turns out wrong, update the doc and the code in the same change. That's the rule.