Claude Code Guide

July 16, 2026 · View on GitHub

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Always work in a worktree, never directly in the main checkout

For any non-trivial change (anything beyond a single-line typo fix), create a git worktree first and do the work there. This repo has many concurrent feature branches and stashed WIPs; editing the main checkout has repeatedly led to:

  • branch switches reverting in-flight edits before they can be committed
  • mixed-author dirty trees getting accidentally committed together
  • losing several minutes of work to a git reset --hard from a parallel session

The convention here is .worktrees/<slug>/ on a fresh branch off origin/main. Examples already on disk include .worktrees/imap-hardening, .worktrees/issue73-preset-confirmation, .worktrees/release-0.9.7. Use the same pattern:

cd ~/Documents/GitHub/lingtai-kernel
git fetch origin main
git worktree add -b <branch-slug> .worktrees/<slug> origin/main
cd .worktrees/<slug>
# ... edit, smoke test, pytest, commit, push ...

When the work is merged (or abandoned), clean up:

git worktree remove .worktrees/<slug>
git branch -d <branch-slug>     # or -D if abandoned

Hard rule: if you find yourself about to make more than ~10 lines of edits in the main checkout (the one at ~/Documents/GitHub/lingtai-kernel/), stop and move the work to a worktree first. The 30 seconds spent setting one up are recouped many times over the first time a parallel session resets the branch out from under you.

Single-line fixes, doc tweaks, or commits that are already staged from prior work can stay in the main checkout — the rule is about multi-step editing sessions.

What is 灵台

灵台 (Língtái) is a generic agent framework — an "agent operating system" providing the minimal kernel for AI agents: thinking (LLM), perceiving (vision, search), acting (file I/O), and communicating (inter-agent email). Domain tools, coordination, and orchestration are plugged in from outside via MCP-compatible interfaces.

Named after 灵台方寸山 — where 孙悟空 learned his 72 transformations. Each agent (器灵) can spawn avatars (分身) that venture into 三千世界 and return with experiences. The self-growing network of avatars IS the agent itself — memory becomes infinite through multiplication.

Two-Package Architecture

This repo contains both packages, published as a single lingtai PyPI package:

  • lingtai.kernel (src/lingtai/kernel/) — minimal agent runtime. Contains BaseAgent, intrinsics, LLM protocol (ABCs + service), mail/logging services, and core utilities. Zero hard dependencies. Can be used standalone.
  • lingtai (src/lingtai/) — batteries-included layer. Depends on lingtai.kernel. Provides Agent (capabilities layer), 19 capabilities, LLM adapter implementations, FileIO/Vision/Search services, MCP, CLI, and addons. Re-exports kernel's public API so from lingtai import BaseAgent works.

The kernel must never import from lingtai — the dependency is strictly one-directional.

Anatomy navigation

This repo follows a per-folder ANATOMY.md convention. Use anatomy as your navigator instead of greping for structural questions.

  • The convention itself lives at src/lingtai/intrinsic_skills/lingtai-kernel-anatomy/SKILL.md — what an ANATOMY.md is, the 6-section template, the writing checklist, the maintenance discipline, the citation-rot rule. Read it once before writing or auditing anatomy.
  • The kernel anatomy tree is rooted at src/lingtai/kernel/ANATOMY.md — that file is itself just-an-anatomy of src/lingtai/kernel/, following the same 6-section template as every other anatomy. Its Composition section enumerates the direct children. Descend from there.

The two-entrance framing of earlier versions is gone: there is the convention (the skill) and there is the tree (rooted at the kernel-root anatomy). The kernel-root anatomy is the top of the tree, not a doorway.

How to use it as a coding agent:

  1. Structural question ("where does X live, what shape is this part of the kernel, what does Y connect to") → descend the anatomy tree, top-down from the kernel-root anatomy. Three reads will usually take you deeper than fifty grep hits.
  2. Enumeration question ("every callsite of this function, every file matching this pattern") → grep is still right.
  3. If anatomy disagrees with the code: the code is almost always correct. Update the anatomy to match before you leave the file. Reading and maintaining anatomy are the same act.
  4. If anatomy is missing for a folder you just understood: write it per the convention skill's writing checklist. ~80 lines cap; less is better.
  5. If you refactor code across files: every ANATOMY.md that cited the moved file may have rotted citations. grep -rn "<old-file>:" src/lingtai/kernel/**/ANATOMY.md src/lingtai/kernel/ANATOMY.md and verify each match — citation rot is the single most common drift mode. The convention skill spells out the rule.

The convention exists because grep is full-text-search, and full-text-search is the wrong primitive for understanding architecture. Agents read whole files cheaply; the navigation pattern that pays off for agents is depth-first traversal of structural maps, not breadth-first symbol search. Reach for grep when you've already located the right region and need to enumerate within it.

The much older monolithic prose in this CLAUDE.md (the Architecture / Key Modules / Intrinsics sections below) predates this convention. Treat it as a fallback when anatomy is incomplete; over time it will shrink as content migrates into per-folder ANATOMY.md files.

Build & Test

# Activate the venv (required — lingtai is installed in editable mode)
source venv/bin/activate

# Install in editable mode
pip install -e .

# Run all tests
python -m pytest tests/

# Run a single test file
python -m pytest tests/test_agent.py

# Run a single test
python -m pytest tests/test_agent.py::test_agent_starts_and_stops -v

# Smoke-test after editing a module
python -c "import lingtai"

All dependencies (LLM provider SDKs, MCP, search) are required — no optional extras. Never add [project.optional-dependencies] to pyproject.toml.

Architecture

Three-Layer Agent Hierarchy

BaseAgent              — kernel (intrinsics, sealed tool surface)
    |
Agent(BaseAgent)   — kernel + capabilities + domain tools
    |
CustomAgent(Agent) — host's wrapper (subclass with domain logic)
  • BaseAgent (lingtai.kernel.base_agent) — kernel coordinator for lifecycle state, the message loop, tool dispatch, public APIs, and subclass hooks. Direct BaseAgent callers and subclasses inject all six required Core-owned Ports; Agent and the CLI compose adapters. See the complete six-Port constructor and adapter recipe in the lingtai.kernel.base_agent Key Modules entry below. Five mandatory intrinsics are wired by BaseAgent.
  • Agent (src/lingtai/agent.py) — accepts capabilities= (list or dict) at construction. get_capability(name) for manager access. Also provides connect_mcp() for MCP server integration and auto-creates LocalFileIOService if none provided.
  • Custom agents — subclass Agent, add domain tools via add_tool() or _setup_capability() in __init__.

Four Services (all optional)

ServiceWhat it backsFirst implementation
LLMServiceCore agent loop (thinking)Adapter registry (kernel) + adapters (lingtai)
FileIOServicefile capabilities (read, edit, write, glob, grep)LocalFileIOService (lingtai)
MailServicemail (disk-backed mailbox with inbox, send, check, read, search, delete, self-send)FilesystemMailService (kernel)
LoggingServicestructured JSONL event logging (auto-created in working dir)JSONLLoggingService (kernel)

LLMService lives in the kernel with an adapter registry; adapter implementations live in lingtai and register on import. FileIOService auto-creates LocalFileIOService in Agent (not BaseAgent). LoggingService auto-creates JSONLLoggingService at {working_dir}/logs/events.jsonl if not passed. VisionService and SearchService are capability-level — passed via capabilities={"vision": {"vision_service": svc}}.

Three-Tier Tool Model

TierWhatHow added
IntrinsicsKernel services (mail, system, psyche, soul). Mail provides a disk-backed mailbox: send, check, read, search, delete. Self-send (to own address) creates persistent notes that survive context compaction. System provides runtime inspection (show), synchronization (nap — timed pause), lifecycle (refresh — reload MCP, reset session), self-sleep (sleep), and karma/nirvana-gated actions (lull, interrupt, suspend, cpr, nirvana). Psyche provides pad (edit/load on system/pad.md), context management (molt for self-compaction with briefing), and naming (name/set — set true name once, nickname — mutable). context_forget is internal only (system-forced wipe/recovery). Covenant is a protected prompt section (no tool access). Capabilities can upgrade intrinsics via override_intrinsic().Built-in, always present
CapabilitiesComposable capabilities (file [read/write/edit/glob/grep], knowledge, skills, shell, avatar, daemon, mcp, vision, web_search)Declared at construction via capabilities= on Agent
MCP toolsDomain tools from external MCP serversConnected via Agent.connect_mcp() using MCPClient from services/mcp.py, or add_tool() in subclass constructors

Key Modules

  • lingtai.kernel.base_agentBaseAgent class (kernel coordinator). Constructor: BaseAgent(service: LLMService, *, agent_name: str | None = None, working_dir: str | Path, workdir_lease: WorkdirLeasePort, notification_store: NotificationStorePort, agent_presence: AgentPresenceStorePort, lifecycle_clock: LifecycleClockPort, snapshot_port: SnapshotPort, source_revision_port: SourceRevisionPort, ...). Caller provides the full working_dir path; WorkingDir creates it on disk. The constructor takes six required Core-owned Portsworkdir_lease (lingtai.kernel.workdir_lease.WorkdirLeasePort), notification_store, agent_presence, lifecycle_clock (lingtai.kernel.lifecycle_clock.LifecycleClockPort), snapshot_port, and source_revision_port. Each is required with no unlocked/no-op/default fallback, so omitting any one fails loudly at construction; Core neither imports nor constructs the concrete adapters. The lifecycle_clock supplies the two lifecycle time readings (wall_seconds() / monotonic_seconds()) BaseAgent uses for state/progress/heartbeat/status timestamps and for uptime/idle/AED/snapshot pacing — see the normative src/lingtai/kernel/lifecycle_clock/CONTRACT.md and the docs/references/lifecycle-clock.md manual rather than duplicating the mapping here. The wrapper Agent and the CLI are the composition roots: they select/construct and inject the production adapters (e.g. the POSIX flock lease via lingtai.adapters.workdir_lease.select_workdir_lease, fail-loud on unsupported platforms, and the portable SystemLifecycleClockAdapter), and an explicitly injected Port wins over the default the composition root would otherwise construct. agent_name is an optional true name (真名) — set once via set_name(name) or at construction, never changed. Psyche intrinsic provides name/set action for self-naming. 5-state lifecycle (ACTIVE/IDLE/STUCK/ASLEEP/SUSPENDED), message loop, 2-layer tool dispatch routing (intrinsics + tools), mail notification pipeline via MailService (messages persisted to disk by MailService, agent notified via [system] notification with configurable _mailbox_name/_mailbox_tool). Messages queued during active work are concatenated into one LLM turn via _concat_queued_messages. Public API (add_tool, remove_tool, override_intrinsic, set_name, send, mail). send() is fire-and-forget only — all agents are async peers. Subclass hooks (_pre_request, _post_request, _on_tool_result_hook). Delegates to WorkingDir, SessionManager, ToolExecutor. Tool surface sealed after start().
  • lingtai.kernel.workdirWorkingDir class. Agent working directory management: receives full path from caller, creates it via mkdir(parents=True). Git init with opt-in tracking, manifest read/write, diff() (read-only) and diff_and_commit(). No reference to BaseAgent — pure filesystem/subprocess operations. The exclusive working-directory lock is no longer here — it moved to the Core-owned lingtai.kernel.workdir_lease.WorkdirLeasePort and its POSIX flock adapter; WorkdirLayout.agent_lock still names the .agent.lock path.
  • lingtai.kernel.sessionSessionManager class. LLM session lifecycle: ensure_session(), send() (single attempt, raises on failure — AED retry is in the message loop), _rebuild_session() (create new session with current config, preserving conversation history), context compaction, token tracking, session persistence. No reference to BaseAgent — receives callbacks (build_system_prompt_fn, build_tool_schemas_fn) at construction.
  • lingtai.kernel.tool_executorToolExecutor class. Sequential and parallel tool call execution with timing, error handling, guard checks, and intercept hooks. No reference to BaseAgent — receives dispatch_fn and make_tool_result_fn callbacks.
  • src/lingtai/agent.pyAgent(BaseAgent). Accepts capabilities= at construction. Tracks _capabilities for avatar replay. get_capability(name) returns manager instances.
  • src/lingtai/state.pyAgentState enum (ACTIVE, IDLE, STUCK, ASLEEP, SUSPENDED).
  • src/lingtai/message.pyMessage dataclass (type, sender, content, id, reply_to, timestamp), _make_message (auto-prepends UTC timestamp to string content), MSG_REQUEST, MSG_USER_INPUT. No synchronous reply mechanism — all communication is async.
  • src/lingtai/services/ — lingtai services: file_io.py (ABC + LocalFileIOService), vision.py, search.py, mcp.py. Kernel services (mail.py, logging.py) live in lingtai.kernel.services.
  • lingtai.kernel.llm.interfaceChatInterface, the canonical provider-agnostic conversation history. Single source of truth — adapters rebuild provider formats from this. Content blocks: TextBlock, ToolCallBlock, ToolResultBlock, ThinkingBlock, ImageBlock.
  • lingtai.kernel.llm.baseLLMAdapter (ABC), ChatSession (ABC), LLMResponse, ToolCall, FunctionSchema. All agent code depends on these, never on provider SDKs directly.
  • lingtai.kernel.llm.serviceLLMService. Adapter registry + factory, session registry, one-shot generation gateway, context compaction orchestration. Adapters register via LLMService.register_adapter(). Decoupled from config files — uses injected key_resolver and provider_defaults.
  • src/lingtai/llm/interface_converters.py — Bidirectional converters between ChatInterface and provider-specific formats (Anthropic, OpenAI, Gemini).
  • tools (the five mandatory intrinsics) — Each file exports get_schema(lang), get_description(lang), and handle(agent, args). All five mandatory intrinsics (email, system, psyche, soul, notification) — now consolidated into the top-level tools/ package and injected via BaseAgent(intrinsics=lingtai.tools.registry.INTRINSICS) — have self-contained handler logic — they receive the agent as an explicit parameter. Mail intrinsic provides a disk-backed mailbox with 5 actions: send (fire-and-forget with optional delay in seconds — all sends go through outbox → mailman thread → sent pipeline), check (list inbox with unread flags), read (by ID, non-destructive), search (regex), delete. Every send writes to mailbox/outbox/, spawns a daemon _mailman thread that sleeps for the delay, dispatches (filesystem write or self-send), then moves to mailbox/sent/ with sent_at and status. Returns {"status": "sent", "to": addr, "delay": N} — the agent doesn't know dispatch outcome. outbox/ (transient) and sent/ (audit trail) are not exposed to the agent. Messages persist in mailbox/inbox/{uuid}/message.json — delivery is a filesystem write to the recipient's inbox directory. System intrinsic provides runtime inspection (show), synchronization (nap — timed pause, wakes on mail), lifecycle (refresh — reload MCP, reset session), self-sleep (sleep), and karma/nirvana-gated actions: lull (put other to sleep), interrupt (cancel other's turn), suspend (force other to SUSPENDED), cpr (resuscitate SUSPENDED agent), nirvana (permanently destroy). Psyche intrinsic provides pad (edit/load on system/pad.md), context management (molt for self-compaction with briefing), and naming (name/set — set true name once, nickname — mutable). context_forget is internal only (called by system-forced recovery). Covenant is injected at construction as a protected prompt section (no tool access).
  • src/lingtai/tools/ — Built-in capability modules export setup(agent, **kwargs). The core default floor is read, write, edit, glob, grep (file I/O — also available as the "file" group), knowledge, skills, shell, avatar, daemon, and mcp; optional registry entries include vision and web_search. Durable private knowledge lives in the knowledge capability; the skill catalog lives in skills. Avatar (分身) spawns Agent peers with name and type; type="deep" copies character, pad, and knowledge, while the default shallow spawn starts from init.json only. Reasoning is sent as the first message.
  • src/lingtai/tools/daemon/__init__.py — Daemon (神識) subagent capability. DaemonManager dispatches ephemeral ChatSession tool loops in threads via ThreadPoolExecutor. Each emanation gets a curated tool surface (parent capability handlers plus task-scoped MCP registrations, minus blacklist). Results persist in per-run daemon folders and terminal completion/failure is surfaced as compact system notifications. Actions: emanate (分), list (观), ask (问), check (察), reclaim (收). Configurable: max_emanations, max_turns, timeout.
  • src/lingtai/cli.py — CLI entrypoint (lingtai-agent run <working_dir>). Reads init.json manifest, creates LLMService + Agent, starts the agent loop. Also lingtai cpr <dir> for resuscitating suspended agents.
  • src/lingtai/network.pyAgentNetwork class. Three-layer topology discovery: avatar edges (from delegates/ledger.jsonl), contact edges (from mailbox/contacts.json), mail edges (from mailbox/inbox/ + mailbox/sent/). Returns AgentNode and edge objects.
  • src/lingtai/init_schema.pyvalidate_init() for init.json manifest validation.
  • src/lingtai/addons/ — Optional integrations registered at Agent construction via addons= kwarg. imap/ (IMAP email polling — IMAPManager, IMAPService, IMAPAccount), telegram/ (Telegram bot — TelegramManager, TelegramService, TelegramAccount).
  • lingtai.kernel.configAgentConfig dataclass. Key fields: max_turns, provider, model, api_key, base_url, retry_timeout, aed_timeout (max seconds in STUCK before ASLEEP, default 360), max_aed_attempts (default 3), thinking_budget, data_dir, soul_delay, language, context_limit. Molt thresholds (molt_notice/molt_pressure/molt_urgency) are kernel-fixed runtime constants (MOLT_*_THRESHOLD), not agent-configurable; the context.molt message is hardcoded in meta_block.build_molt_context. Legacy init.json values (molt_notice/molt_pressure/molt_urgency/molt_prompt) are tolerated but ignored. Host app injects resolved values; no file-based config inside lingtai.
  • lingtai.kernel.prompt — Builds system prompt from base template + SystemPromptManager sections + MCP tool descriptions.
  • lingtai.kernel.loop_guardLoopGuard class. Duplicate tool call tracking, total call limit enforcement, invalid tool detection, escalating warnings (dup_free_passes → warnings → hard block).
  • lingtai.kernel.handshakeis_agent(path), is_alive(path), is_human(path), manifest(path) — agent discovery and health checks via filesystem.

LLM Provider Adapters

5 adapter directories under src/lingtai/llm/, each lazy-imported and registered with LLMService.register_adapter() on import lingtai.llm: Gemini (google-genai), OpenAI, Anthropic, MiniMax, Custom. The Custom adapter handles additional providers (DeepSeek, Grok, Qwen, GLM, Kimi) via api_compat routing. Each adapter subdirectory has adapter.py (implementation) and defaults.py (model defaults). LLM protocol ABCs live in lingtai.kernel.llm; adapter implementations live in lingtai.llm.

Built-in Capabilities (19)

CapabilityUsageWhat it adds
filecapabilities=["file"]Group sugar — expands to read, write, edit, glob, grep
readcapabilities=["read"]Read text file contents via FileIOService
writecapabilities=["write"]Create or overwrite files via FileIOService
editcapabilities=["edit"]Exact string replacement in files via FileIOService
globcapabilities=["glob"]Find files by glob pattern via FileIOService
grepcapabilities=["grep"]Search file contents by regex via FileIOService
psycheintrinsicIdentity, pad, context molt, and naming.
knowledgecapabilities=["knowledge"]Private durable knowledge across molts. Former durable-memory names library and codex are removed.
shellcapabilities={"shell": {"policy_file": "p.json"}} or {"shell": {"yolo": True}}Shell command execution with the active dialect
avatarcapabilities=["avatar"]Spawn avatar (分身) as fully independent detached process. Two params: name (required, true name) and type ('shallow' default — copies init.json only, 投胎; 'deep' — copies character/pad/knowledge too, 二重身). Each avatar gets its own working dir + lingtai-agent run process. Survives parent death. Reasoning = starting prompt in init.json.
emailcapabilities=["email"]Upgrades mail intrinsic with reply/reply_all, CC/BCC, contacts, sent/archive folders, archive (inbox→archive), delete (inbox/archive), delayed send (delay), private mode, and scheduled recurring sends (schedule sub-object with create/cancel/list). Routes dispatch through outbox → _mailman(skip_sent=True). Sets _mailbox_name="email box", _mailbox_tool="email". Delegates inbox ops to mail intrinsic helpers.
visioncapabilities=["vision"] or {"vision": {"vision_service": svc}}Image understanding (LLM multimodal or dedicated VisionService)
web_searchcapabilities=["web_search"] or {"web_search": {"search_service": svc}}Web search (LLM grounding or dedicated SearchService)
web_readcapabilities=["web_read"]Read and extract content from web pages
talkcapabilities=["talk"]Text-to-speech via MiniMax MCP
composecapabilities=["compose"]Music generation via MiniMax MCP
drawcapabilities=["draw"]Text-to-image via MiniMax MCP
videocapabilities=["video"] or {"video": {"provider": "minimax"}}Video generation via MiniMax MCP. Text-to-video and image-to-video (via first_frame_image). Director models support camera movement instructions in prompts. Output: MP4 saved to media/videos/.
listencapabilities=["listen"]Speech transcription + music analysis
daemoncapabilities=["daemon"] or {"daemon": {"max_emanations": 100}}Subagent system (分神). Dispatch ephemeral LLM sessions as parallel workers in the same working dir. Actions: emanate (分, dispatch batch), list (观, status), ask (问, follow-up), reclaim (收, kill all). Results return as [daemon:em-N] notifications. MCP registrations supplied per task via mcp (serialized for all backends; loaded as task-scoped tools by LingTai backend). Blacklist: daemon, avatar, psyche, skills, knowledge.

Extension Pattern

# Layer 2: Agent with capabilities
agent = Agent(
    service=svc, agent_name="alice", working_dir="/agents/alice",
    capabilities=["file", "vision", "web_search", "shell"],  # "file" expands to read/write/edit/glob/grep
)
agent = Agent(
    service=svc, agent_name="bob", working_dir="/agents/bob",
    capabilities={"shell": {"policy_file": "p.json"}},   # dict form (with kwargs)
)

# Layer 3: Custom agent subclass
class ResearchAgent(Agent):
    def __init__(self, **kwargs):
        super().__init__(capabilities=["file", "vision", "web_search"], **kwargs)
        self._setup_capability("bash", policy_file="research.json")
        self.add_tool("query_db", schema={...}, handler=db_handler)

# Low-level API (on BaseAgent, sealed after start)
agent.add_tool(name, schema=schema, handler=handler)     # register tool
agent.remove_tool(name)                                   # unregister tool
agent.override_intrinsic(name)                            # remove intrinsic, return handler
agent.update_system_prompt(section, content)              # inject prompt section (open at any time)

Note: capabilities= accepts list[str] (no kwargs) or dict[str, dict] (with kwargs per capability). Group names like "file" expand to individual capabilities. add_tool(), remove_tool(), and override_intrinsic() raise RuntimeError after start().

System Prompt Structure

Base prompt (minimal — identity and general guidance only) → Sections (injected by host/capabilities via update_system_prompt) → MCP tool descriptions (auto-generated). Protected sections are modified only by host/capability code paths, not by free-form model text.

Do not put tool pipelines or tool-specific instructions in the system prompt. Pipelines (e.g., "mail admin first, then sleep") belong in tool schema descriptions where the LLM sees them in context. The system prompt should stay minimal.

Conventions

  • Python 3.11+, from __future__ import annotations used throughout.
  • Dataclasses preferred over dicts for structured data.
  • No file-based config inside lingtai — all config injected via constructor args.
  • All services optional — missing service auto-disables backed intrinsics.
  • Provider SDKs lazy-imported — only active provider needs installation.
  • Tests use unittest.mock.MagicMock for LLM service mocking. Test functions follow test_<what_is_tested> naming.
  • Migrations should be complete and clean — remove old code entirely. No backward-compatibility shims, no deprecated wrappers, no legacy aliases unless the user explicitly asks for them.
  • Kernel must never import from lingtai — dependency is strictly one-directional.
  • All imports in lingtai use from lingtai.kernel.xxx import ... for kernel types.
  • Repo housekeeping: docs/, reports/, and tmp/ are ignored by default because agent sessions often create private scratch there. PR explainers are local artifacts by default and should not be uploaded; put them under ignored reports/, tmp/, or another local artifact path. Only long-lived docs, release materials, or reports with an explicit durable purpose may be force-added with rationale. Historical design material should now live in issues, PR descriptions, commit messages, or durable docs rather than root scratch trees; do not remove such records unless the owner explicitly approves that archival decision.