Skip to content

Architecture

Skep is a composition of standard primitives: tree-sitter indexing, SQLite FTS5, Unix sockets, tmux panes, and MCP. Each piece is well-established on its own; Skep’s job is to wire them together into a per-repo task orchestrator for Claude Code.

Three pieces. You only really need to understand one at a time.

Every repo has a SQLite index built by tree-sitter. It records every function, type, class, method, and doc comment in the repo, along with their file locations and doc signatures. MCP tools query this index directly — search_symbols for fuzzy lookup, get_file_context for a file’s symbol table, get_call_graph for callers and callees. The index persists on disk and updates incrementally as files change.

The daemon also computes a PageRank score over the symbol/file graph at startup and after large index updates. The score is stored on each symbol row and used to rank ties in search_symbols results — so a symbol called from many places sorts above an equally-named unused stub. See internal/graph/rank.go for the implementation.

Every task goes through the same states:

created → classified → pending → approved → queued →
executing → done ─or─ interrupted ─or─ failed ─or─ rejected

Classification picks a scope (small / large / ambiguous) and a plan. Small tasks auto-approve (when you opt in). Large tasks wait for a human. Every task runs on its own git branch and records how many tokens it cost you.

Cross-repo work uses four MCP tools that the sender’s Claude session calls against the peer’s Skep daemon. (You may see this flow called “the waggle” elsewhere in the docs — a bee-metaphor nickname for exactly this MCP handoff.)

ToolBlocks?Purpose
create_remote_taskyes, until peer classifieshand a task to a peer, get back its plan
approve_remote_tasknoapprove the peer’s plan so it executes
wait_remote_taskyes, until peer finishesblock until the peer hits a terminal state
get_remote_tasknonon-blocking check on a peer task

No polling loops. One call per step. The sender’s Claude session never drifts into the “I wonder if backend is done yet” ad-hoc behavior that breaks multi-step plans.

┌─────────────────────────────────────────────────────────┐
│ your workspace │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ backend │ │ mobile │ │ shared │ │
│ │ .skep │◄─►│ .skep │◄─►│ .skep │ │
│ │ ─ index │MCP│ ─ index │MCP│ ─ index │ │
│ │ ─ tasks │ │ ─ tasks │ │ ─ tasks │ │
│ │ ─ daemon │ │ ─ daemon │ │ ─ daemon │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ▲ ▲ ▲ │
│ └───────────────┼───────────────┘ │
│ │ │
│ skep workspace watch │
│ (your cockpit) │
└─────────────────────────────────────────────────────────┘
~/code/my-project/
├── .skep-workspace/
│ └── registry.json ← workspace registry (which repos, which LLMs)
├── backend/
│ └── .skep/
│ ├── index.db ← SQLite: files, symbols, edges, tasks, FTS5
│ ├── config.json ← llm, model, test-cmd, tmux-layout
│ ├── daemon.pid ← running daemon's PID
│ ├── agent.sock ← Unix socket for inter-agent + CLI control
│ └── branches/ ← per-branch indexes for diffing
└── frontend/.skep/ ← same layout

Each repo gets its own daemon process:

skep daemon (per repo)
├── fsnotify watcher ← re-indexes on file change
├── Unix socket listener ← ask / create_task / list_tasks / stop
├── HTTP API sidecar ← 127.0.0.1:<random>, bearer token auth
│ (same verbs, for VS Code / webhook / remote surfaces)
└── task executor goroutine ← serial queue, one task at a time

The HTTP sidecar listens on a random loopback port picked at daemon startup and writes the URL + bearer token to .skep/http.json (mode 0600). Clients that cannot speak the Unix-socket protocol — notably editor extensions and webhook receivers — hit the HTTP surface with the same JSON verbs as the socket listener. Everything stays on 127.0.0.1; there is no network exposure.

skep task create "..." (CLI, MCP, or peer daemon)
┌─────────────────────────────────────────────┐
│ dedup: FTS5 symbol match + in-flight scan │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ shell-out #1: classify + plan │
│ claude -p "$(cat current-prompt.md)" │
│ → stores classification + plan in tasks │
└─────────────────────────────────────────────┘
├── small + auto-execute → approved
└── large / ambiguous → pending (human gate)
┌─────────────────────────────────────────────┐
│ git checkout -b skep/task-{id}-{slug} │
│ materialize task_steps from plan_json │
│ shell-out per step (not per task): │
│ step 1 → LLM edits → commit → mark done │
│ step 2 → LLM edits → commit → mark done │
│ ... first-failure-stops-task, 1 retry/step│
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ post-execution │
│ re-index, diff branch vs main │
│ write .skep/task-{id}-result.md │
│ mark done / interrupted / failed │
└─────────────────────────────────────────────┘
PackageResponsibility
internal/agentdaemon lifecycle, socket listener, executor
internal/indexindexer, hasher, differ, SQLite store
internal/parsertree-sitter wrapping, symbol extraction
internal/graphcall graph + PageRank
internal/taskscreate, dedup, classify, approve, delete
internal/mcpstdio MCP server, cross-repo routing
internal/llmshell-out wrapper, prompt file management
internal/registryworkspace registry read/write
  • MCP (vertical): how the interactive LLM session queries this repo’s agent for symbols, call graphs, overviews, task status.
  • Unix socket JSON (horizontal): how agents talk to each other across repos — create_remote_task, wait_remote_task, approve_remote_task, list_tasks.

See Cross-repo delegation and MCP for the details of each layer.