Skip to content

Architecture

Neo is a small Go coding agent. The core agent loop is policy-free: it owns message state, calls an LLM provider, emits events, and runs injected tools. Product behavior is layered around that loop.

Path Responsibility
cmd/neo/ Process lifecycle, stream-injected command dispatch, and chat session startup.
internal/agent/ Core agent loop, transcript state, event model, tool-use continuation.
internal/approval/ Literal matcher for optional interactive tool confirmations.
internal/atomicfile/ Atomic file replacement used by session and credential storage.
internal/auth/ OpenAI ChatGPT/Codex device-code login, token refresh, and stored subscription credentials.
internal/compact/ Compaction interface, summarizing compactor, and safe split helpers.
internal/config/ Config discovery, defaults, and feature flags.
internal/subagent/ Subagent runtime for the chat agent tool: supervisor budgets and node bookkeeping. The TUI renders agent calls as ordinary tool receipts and does not consume the supervisor event stream.
internal/llm/ Provider-neutral request/response types and system prompt blocks.
internal/llm/anthropic/ Anthropic provider adapter.
internal/llm/openai/ OpenAI provider adapters for API-key Responses API calls and ChatGPT/Codex subscription calls.
internal/llm/chatcompletions/ Reusable OpenAI-compatible Chat Completions adapter.
internal/llm/openrouter/ OpenRouter provider setup and defaults.
internal/llm/google/ Google Gemini adapter.
internal/logx/ Optional structured debug logging.
internal/projectctx/ AGENTS.md discovery and prompt augmentation.
internal/session/ File-backed session metadata and transcripts.
internal/skills/ Built-in and discovered skills, catalog rendering, $name or /name expansion, and turn labels.
internal/tools/ Built-in tools exposed to the model.
internal/tui/ Bubble Tea terminal UI and transcript rendering.
internal/workflow/ In-memory checklist state and the chat workflow tool.
internal/workspace/ Workspace helpers shared by project-context features.
  1. cmd/neo resolves the session store. For a resumed chat, it loads the session and restores its saved working directory before entering the shared chat path.
  2. It loads config and selects Anthropic, OpenAI, OpenRouter, or Google Gemini, preferring saved provider and model metadata when resuming. OpenAI defaults to API-key auth; openai_auth: subscription builds the Codex subscription provider from stored device-code credentials.
  3. It resolves the project root, constructs the workflow and subagent capabilities, and builds the complete tool registry.
  4. For a new chat, it creates the session in internal/session.
  5. Skills (built-in plus discovered) and AGENTS.md are loaded, then chatSystem builds both flattened and segmented system prompts.
  6. agent.New receives the provider, tools, optional interactive approval matcher, system prompt, and restored messages and usage.
  7. tui.Run owns user interaction and saves the transcript after each send.

Neo assumes its VM or sandbox controls filesystem, process, network, credential, and external-service access. Neo does not classify commands or enforce a security policy. Interactive tool_approvals add optional confirmation before selected coordinator calls; they are not passed to headless or child agents.

Inspect children are constrained by capability selection: their registry contains only read_file, grep, and glob.

The agent appends one user text message per Send call. Before each provider turn, the compactor may make its own provider call and returns both the rewritten transcript and that call’s usage. The agent aggregates compaction and normal response usage exactly once. Each normal provider response becomes an assistant message. If the assistant requests tools, Neo runs them, caps oversized output before it enters user tool_result blocks, and continues the provider loop until the assistant ends the turn or max turns is reached. A provider refusal ends the turn with its refusal text unless accepted steering requests a continuation, while pause_turn explicitly replays the assistant response and continues. Unknown stop reasons fail the turn instead of repeating provider calls. Anthropic’s model_context_window_exceeded ends the turn with a typed truncation error and preserves any partial response text.

Provider HTTP clients set no total timeout: a fixed deadline caps how long one generation may take, and the previous five-minute limit cut off long reasoning turns and then retried them. Instead retry.NewHTTPClient bounds the wait for response headers (10 minutes, generous because a non-streaming API sends them only when generation finishes) and every body read is guarded by retry.IdleBody, which cancels the request when the provider sends nothing for 3 minutes. The retry helper repeats transient transport errors and retryable statuses only; timeouts, context deadlines, and errors marked with retry.Permanent (a missing login, a malformed stream) fail after one attempt. The caller’s context remains the overall bound (Ctrl-C in chat, --timeout in headless runs).

Provider HTTP failures surface as llm.HTTPError. Every adapter, and the retry helper when it gives up, parses the shared {"error":{...}} body shape into one line such as anthropic 401 authentication_error: API key is invalid. (check ANTHROPIC_API_KEY); 401 and 403 responses carry a hint naming the credential to check, and an unparseable body is trimmed to a short excerpt. The raw body stays on the error for debug logs but is never printed. The agent both emits an EventError and returns the same error from Send; the TUI renders it once, from the returned error, and treats cancellation as a notice rather than an error.