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 --hardfrom 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 onlingtai.kernel. Provides Agent (capabilities layer), 19 capabilities, LLM adapter implementations, FileIO/Vision/Search services, MCP, CLI, and addons. Re-exports kernel's public API sofrom lingtai import BaseAgentworks.
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 anANATOMY.mdis, 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 ofsrc/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:
- 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.
- Enumeration question ("every callsite of this function, every file matching this pattern") → grep is still right.
- 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.
- 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.
- If you refactor code across files: every
ANATOMY.mdthat cited the moved file may have rotted citations.grep -rn "<old-file>:" src/lingtai/kernel/**/ANATOMY.md src/lingtai/kernel/ANATOMY.mdand 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. DirectBaseAgentcallers and subclasses inject all six required Core-owned Ports;Agentand the CLI compose adapters. See the complete six-Port constructor and adapter recipe in thelingtai.kernel.base_agentKey Modules entry below. Five mandatory intrinsics are wired byBaseAgent. - Agent (
src/lingtai/agent.py) — acceptscapabilities=(list or dict) at construction.get_capability(name)for manager access. Also providesconnect_mcp()for MCP server integration and auto-createsLocalFileIOServiceif none provided. - Custom agents — subclass Agent, add domain tools via
add_tool()or_setup_capability()in__init__.
Four Services (all optional)
| Service | What it backs | First implementation |
|---|---|---|
LLMService | Core agent loop (thinking) | Adapter registry (kernel) + adapters (lingtai) |
FileIOService | file capabilities (read, edit, write, glob, grep) | LocalFileIOService (lingtai) |
MailService | mail (disk-backed mailbox with inbox, send, check, read, search, delete, self-send) | FilesystemMailService (kernel) |
LoggingService | structured 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
| Tier | What | How added |
|---|---|---|
| Intrinsics | Kernel 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 |
| Capabilities | Composable capabilities (file [read/write/edit/glob/grep], knowledge, skills, shell, avatar, daemon, mcp, vision, web_search) | Declared at construction via capabilities= on Agent |
| MCP tools | Domain tools from external MCP servers | Connected via Agent.connect_mcp() using MCPClient from services/mcp.py, or add_tool() in subclass constructors |
Key Modules
lingtai.kernel.base_agent—BaseAgentclass (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 fullworking_dirpath;WorkingDircreates it on disk. The constructor takes six required Core-owned Ports —workdir_lease(lingtai.kernel.workdir_lease.WorkdirLeasePort),notification_store,agent_presence,lifecycle_clock(lingtai.kernel.lifecycle_clock.LifecycleClockPort),snapshot_port, andsource_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. Thelifecycle_clocksupplies 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 normativesrc/lingtai/kernel/lifecycle_clock/CONTRACT.mdand thedocs/references/lifecycle-clock.mdmanual rather than duplicating the mapping here. The wrapperAgentand the CLI are the composition roots: they select/construct and inject the production adapters (e.g. the POSIXflocklease vialingtai.adapters.workdir_lease.select_workdir_lease, fail-loud on unsupported platforms, and the portableSystemLifecycleClockAdapter), and an explicitly injected Port wins over the default the composition root would otherwise construct.agent_nameis an optional true name (真名) — set once viaset_name(name)or at construction, never changed. Psyche intrinsic providesname/setaction 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 toWorkingDir,SessionManager,ToolExecutor. Tool surface sealed afterstart().lingtai.kernel.workdir—WorkingDirclass. Agent working directory management: receives full path from caller, creates it viamkdir(parents=True). Git init with opt-in tracking, manifest read/write,diff()(read-only) anddiff_and_commit(). No reference to BaseAgent — pure filesystem/subprocess operations. The exclusive working-directory lock is no longer here — it moved to the Core-ownedlingtai.kernel.workdir_lease.WorkdirLeasePortand its POSIXflockadapter;WorkdirLayout.agent_lockstill names the.agent.lockpath.lingtai.kernel.session—SessionManagerclass. 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_executor—ToolExecutorclass. Sequential and parallel tool call execution with timing, error handling, guard checks, and intercept hooks. No reference to BaseAgent — receivesdispatch_fnandmake_tool_result_fncallbacks.src/lingtai/agent.py—Agent(BaseAgent). Acceptscapabilities=at construction. Tracks_capabilitiesfor avatar replay.get_capability(name)returns manager instances.src/lingtai/state.py—AgentStateenum (ACTIVE, IDLE, STUCK, ASLEEP, SUSPENDED).src/lingtai/message.py—Messagedataclass (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 inlingtai.kernel.services.lingtai.kernel.llm.interface—ChatInterface, 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.base—LLMAdapter(ABC),ChatSession(ABC),LLMResponse,ToolCall,FunctionSchema. All agent code depends on these, never on provider SDKs directly.lingtai.kernel.llm.service—LLMService. Adapter registry + factory, session registry, one-shot generation gateway, context compaction orchestration. Adapters register viaLLMService.register_adapter(). Decoupled from config files — uses injectedkey_resolverandprovider_defaults.src/lingtai/llm/interface_converters.py— Bidirectional converters betweenChatInterfaceand provider-specific formats (Anthropic, OpenAI, Gemini).tools(the five mandatory intrinsics) — Each file exportsget_schema(lang),get_description(lang), andhandle(agent, args). All five mandatory intrinsics (email, system, psyche, soul, notification) — now consolidated into the top-leveltools/package and injected viaBaseAgent(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 optionaldelayin 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 tomailbox/outbox/, spawns a daemon_mailmanthread that sleeps for the delay, dispatches (filesystem write or self-send), then moves tomailbox/sent/withsent_atandstatus. Returns{"status": "sent", "to": addr, "delay": N}— the agent doesn't know dispatch outcome.outbox/(transient) andsent/(audit trail) are not exposed to the agent. Messages persist inmailbox/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/loadonsystem/pad.md), context management (moltfor self-compaction with briefing), and naming (name/set— set true name once,nickname— mutable).context_forgetis 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 exportsetup(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 theknowledgecapability; the skill catalog lives inskills. Avatar (分身) spawnsAgentpeers withnameandtype;type="deep"copies character, pad, and knowledge, while the default shallow spawn starts frominit.jsononly. Reasoning is sent as the first message.src/lingtai/tools/daemon/__init__.py— Daemon (神識) subagent capability.DaemonManagerdispatches ephemeralChatSessiontool loops in threads viaThreadPoolExecutor. 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>). Readsinit.jsonmanifest, creates LLMService + Agent, starts the agent loop. Alsolingtai cpr <dir>for resuscitating suspended agents.src/lingtai/network.py—AgentNetworkclass. Three-layer topology discovery: avatar edges (fromdelegates/ledger.jsonl), contact edges (frommailbox/contacts.json), mail edges (frommailbox/inbox/+mailbox/sent/). ReturnsAgentNodeand edge objects.src/lingtai/init_schema.py—validate_init()for init.json manifest validation.src/lingtai/addons/— Optional integrations registered at Agent construction viaaddons=kwarg.imap/(IMAP email polling —IMAPManager,IMAPService,IMAPAccount),telegram/(Telegram bot —TelegramManager,TelegramService,TelegramAccount).lingtai.kernel.config—AgentConfigdataclass. 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; thecontext.moltmessage is hardcoded inmeta_block.build_molt_context. Legacyinit.jsonvalues (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 +SystemPromptManagersections + MCP tool descriptions.lingtai.kernel.loop_guard—LoopGuardclass. Duplicate tool call tracking, total call limit enforcement, invalid tool detection, escalating warnings (dup_free_passes → warnings → hard block).lingtai.kernel.handshake—is_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)
| Capability | Usage | What it adds |
|---|---|---|
file | capabilities=["file"] | Group sugar — expands to read, write, edit, glob, grep |
read | capabilities=["read"] | Read text file contents via FileIOService |
write | capabilities=["write"] | Create or overwrite files via FileIOService |
edit | capabilities=["edit"] | Exact string replacement in files via FileIOService |
glob | capabilities=["glob"] | Find files by glob pattern via FileIOService |
grep | capabilities=["grep"] | Search file contents by regex via FileIOService |
psyche | intrinsic | Identity, pad, context molt, and naming. |
knowledge | capabilities=["knowledge"] | Private durable knowledge across molts. Former durable-memory names library and codex are removed. |
shell | capabilities={"shell": {"policy_file": "p.json"}} or {"shell": {"yolo": True}} | Shell command execution with the active dialect |
avatar | capabilities=["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. |
email | capabilities=["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. |
vision | capabilities=["vision"] or {"vision": {"vision_service": svc}} | Image understanding (LLM multimodal or dedicated VisionService) |
web_search | capabilities=["web_search"] or {"web_search": {"search_service": svc}} | Web search (LLM grounding or dedicated SearchService) |
web_read | capabilities=["web_read"] | Read and extract content from web pages |
talk | capabilities=["talk"] | Text-to-speech via MiniMax MCP |
compose | capabilities=["compose"] | Music generation via MiniMax MCP |
draw | capabilities=["draw"] | Text-to-image via MiniMax MCP |
video | capabilities=["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/. |
listen | capabilities=["listen"] | Speech transcription + music analysis |
daemon | capabilities=["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 annotationsused 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.MagicMockfor LLM service mocking. Test functions followtest_<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/, andtmp/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 ignoredreports/,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.