CodeDocs Vault

6. Agents, Skills, Slash Commands, MCP

6.1 Agents (subagents)

The shape — tools/AgentTool/loadAgentsDir.ts:162

type AgentDefinition = BuiltInAgentDefinition | CustomAgentDefinition | PluginAgentDefinition

All three have:

Built-in agents — tools/AgentTool/built-in/*.ts

Six hardcoded modules:

Custom and plugin agents

Custom agents are markdown files with YAML frontmatter loaded from ~/.claude/agents/, .claude/agents/, or /etc/claude-code/agents/. The frontmatter schema (AgentJsonSchema, loadAgentsDir.ts:73-99) validates:

name: my-agent
description: when to use it
tools: [Bash, Read, Edit]         # or ['*']
disallowedTools: [Write]
mcpServers: [slack, {my: {command: python, args: ['-m', 'x']}}]
requiredMcpServers: [slack]       # skip agent if not available
hooks: { PreToolUse: ... }
model: sonnet | haiku | opus
effort: low | high | <number>

getAgentDefinitionsWithOverrides() (loadAgentsDir.ts:296-349) loads all three sources concurrently, prioritizes (built-ins first, then plugins, users, projects, flags, managed), filters by requiredMcpServers via filterAgentsByMcpRequirements(), and returns { activeAgents, allAgents }.

Agent dispatch — tools/AgentTool/AgentTool.tsx + runAgent.ts

When the model calls AgentTool({subagent_type, prompt, ...}):

  1. AgentTool.call validates the schema, resolves the AgentDefinition.
  2. resolveAgentTools() (agentToolUtils.ts:122-170) computes the effective tool pool (wildcard expansion, denylist application, context filtering).
  3. If isolation: 'worktree', createAgentWorktree() creates a temporary git worktree; cwd is switched for the child; on completion the worktree is cleaned up if no changes, otherwise path + branch are returned (AgentTool.tsx:592-600).
  4. If isolation: 'remote' (ant-only), registerRemoteAgentTask() schedules a CCR sandbox run.
  5. Otherwise runAgent() (runAgent.ts:1-84) constructs the subagent context — forked readFileState, independent abortController — and calls query() with the agent's system prompt and tools.
  6. The child's streaming messages are bridged to the parent via a LocalAgentTask (for background), written to a sidechain transcript, and surfaced to the user through the footer/teammates panel.
  7. On completion, cleanup: worktree removal, MCP server teardown.

Fork subagents — tools/AgentTool/forkSubagent.ts

When isForkSubagentEnabled() and the caller omits subagent_type:

Forks are cheap because of cache sharing; fresh subagents (subagent_type specified) are expensive but start with clean context.

Verification agent — the adversarial-verifier contract

Gated by tengu_hive_evidence. When enabled, constants/prompts.ts:390-395 adds a hard rule to the session-specific guidance: non-trivial implementations (3+ file edits, backend/API changes, infra changes) require an independent verifier run before reporting completion, and the verifier can only return PASS / FAIL / PARTIAL — you cannot self-assign PARTIAL. On PASS you spot-check 2-3 commands from its report.

6.2 Skills

What is a skill?

A markdown file with YAML frontmatter + body, loaded into a PromptCommand (types/command.ts:24-56):

type PromptCommand = CommandBase & {
  type: 'prompt'
  progressMessage: string
  contentLength: number
  getPromptForCommand(args: string, context: ToolUseContext): Promise<ContentBlockParam[]>
  context?: 'inline' | 'fork'
  agent?: string             // agent type when forked
}

Four discovery sources

  1. Bundled — registered at build time via registerBundledSkill() (skills/bundledSkills.ts:53-99). Files extracted on first invocation.
  2. Disk-based~/.claude/skills/ (user), .claude/skills/ (project), /etc/claude-code/skills/ (managed), loaded by loadMarkdownFilesForSubdir('skills', cwd).
  3. Plugin skillsgetPluginSkills() from installed plugins.
  4. MCP skills — MCP prompts converted to PromptCommand with source: 'mcp'.

Skill frontmatter

name: commit
description: Create a git commit with staged changes
whenToUse: "Use when the user wants to commit changes"
model: sonnet
effort: low
hooks: { PreToolUse: ... }
paths: ['*.py', 'src/**']   # glob patterns for auto-discovery

Invocation

SkillTool.call resolves the skill and dispatches based on context:

Presentation

Skills are listed inside SkillTool's prompt in the default mode. Under feature('EXPERIMENTAL_SKILL_SEARCH'), they are injected as separate <system-reminder> attachments ("Skills relevant to your task: …") per turn, with DiscoverSkillsTool as the search gateway — same cache-preservation idea as ToolSearch.

The user-invocable form is simply /skill-name — e.g. /commit, /review-pr, /simplify. The guidance at constants/prompts.ts:382-384 tells the model:

/<skill-name> (e.g., /commit) is shorthand for users to invoke a user-invocable skill. When executed, the skill gets expanded to a full prompt. Use the SkillTool tool to execute them. IMPORTANT: Only use SkillTool for skills listed in its user-invocable skills section - do not guess or use built-in CLI commands.

6.3 Slash commands

Three kinds — types/command.ts:175-206

type Command = CommandBase & (PromptCommand | LocalCommand | LocalJSXCommand)
Type Behavior
PromptCommand Injects a rendered prompt into the conversation. Model handles it. Skills are this kind.
LocalCommand Runs client-side without the model. E.g. /clear, /cost, /exit.
LocalJSXCommand Like LocalCommand but renders an Ink UI overlay. E.g. /status, /model picker.

CommandBase provides: name, description, aliases, whenToUse, isEnabled(), isHidden, userInvocable, loadedFrom ('commands_DEPRECATED' | 'skills' | 'plugin' | 'managed' | 'bundled' | 'mcp'), allowedTools.

Registry — commands.ts

A hardcoded set plus lazy-loaded feature-gated ones:

All are merged into one Command[] array consumed by the model and the slash-command parser.

User-defined slash commands

There is no separate mechanism — a skill / name is the entry form. Write ~/.claude/skills/mycmd.md and /mycmd args works automatically.

6.4 Memory system

The four memory types — memdir/memoryTypes.ts:14

const MEMORY_TYPES = ['user', 'feedback', 'project', 'reference'] as const

These are the only types (legacy untyped files degrade gracefully). Each type has a defined when_to_save, how_to_use, body_structure, and multiple examples in the prompt (TYPES_SECTION_INDIVIDUAL and TYPES_SECTION_COMBINED). Reproduced in doc 07.

Storage layout

~/.claude/projects/<project-slug>/memory/
├── MEMORY.md                    # index, loaded every turn, capped at 200 lines / 25 KB
├── <topic>.md                   # one file per memory, with frontmatter
└── auto/<topic>.md              # auto-extracted by session memory

MEMORY.md is a hand-curated index of one-line pointers (- [Title](file.md) — one-line hook). The recall-side flow (memdir/findRelevantMemories.ts) reads MEMORY.md, scores topic files for relevance to the current user message, then loads the matches on demand.

Directory existence is guaranteed by ensureMemoryDirExists() (memdir/memdir.ts:129). The guidance bullet at memdir/memdir.ts:116-117 is:

This directory already exists — write to it directly with the Write tool (do not run mkdir or check for its existence).

Added because Claude was burning turns on ls / mkdir -p before writing.

Write path (auto-memory extraction)

services/extractMemories/ runs as a Stop hook after the model completes a turn. It:

  1. Checks whether extraction is due (token threshold, tool-call count since last extraction).
  2. Constructs an extraction prompt via buildExtractCombinedPrompt() / buildExtractAutoOnlyPrompt().
  3. Spawns a perfect fork via runForkedAgent() (sharing parent cache).
  4. The fork uses FileWrite / FileRead / FileEdit to update MEMORY.md and topic files.
  5. Logs extraction metrics.

Session memory

services/SessionMemory/ is a parallel track — a background-updated transcript of session highlights. Updated by a forked subagent when shouldExtractMemory() fires (based on token delta + tool-call count since last extraction).

CLAUDE.md — utils/claudemd.ts

Project-level instructions loaded in priority order:

  1. /etc/claude-code/CLAUDE.md (managed, global).
  2. ~/.claude/CLAUDE.md (user, private).
  3. CLAUDE.md, .claude/CLAUDE.md, .claude/rules/*.md (project, checked in).
  4. CLAUDE.local.md (per-project private).

Supports @include file directives. Files are applied so the closest/latest wins.

Read-only agents (Explore, Plan) pass omitClaudeMd: true to skip this block — they don't need lint/commit/PR guidance, and dropping it is a noticeable token savings.

6.5 MCP integration

Client — services/mcp/client.ts

Claude Code is an MCP client. It supports stdio, SSE, HTTP, WebSocket, and in-process transports (services/mcp/InProcessTransport.ts, SdkControlTransport.ts). For each connected server:

Output handling

MCP tool output can be large. mcpContentNeedsTruncation() + truncateMcpContentIfNeeded() cap oversized results; binary outputs can be persisted to disk with a preview returned.

Elicitation — services/mcp/elicitationHandler.ts

When an MCP server needs user interaction (e.g. OAuth completion), it sends an ElicitRequest (form or URL). Handler flow:

  1. executeElicitationHooks() — hooks may auto-respond.
  2. Otherwise UI prompt.
  3. Server emits ElicitationCompleteNotification when done.

Channel permissions (experimental)

Gated by tengu_harbor_permissions. Instead of UI prompts, permission requests are sent to integrated channels (Telegram, iMessage, Discord) via MCP notifications. Users reply with a structured format (y|yes|n|no <5-letter-id>, channelPermissions.ts:75). The channel server emits the parsed response as a notification — Claude Code never regex-parses free text. The 5-letter ID is drawn from a set that avoids profanity (ID_AVOID_SUBSTRINGS).

Approval flows

Custom agents' mcpServers frontmatter is subject to strictPluginOnlyCustomization lock (runAgent.ts:118-127): user-authored custom agents can be blocked from spawning new MCP servers to prevent breakout, while plugin / policy / built-in agents still can.

6.6 How these layers compose

A user typing /commit-push-pr triggers:

  1. The slash parser resolves it to a skill (commands.ts → skill registry).
  2. SkillTool is invoked with that skill's name.
  3. Skill frontmatter says context: 'inline'getPromptForCommand() returns the rendered prompt (which contains a full git status / git diff / git log plan and a HEREDOC example for the commit message).
  4. The model then uses BashTool (with Bash permissions allowing git *) to execute the steps.
  5. If the skill is context: 'fork', the whole thing runs in a fork sharing cache — output returned as a single message.

A user typing "search the codebase for all callers of X" might trigger:

  1. Model calls AgentTool({ description: 'find callers', subagent_type: 'Explore', prompt: '…' }).
  2. runAgent spawns a fork with the Explore agent's system prompt and a restricted tool pool.
  3. The Explore agent makes many Grep / Read calls.
  4. Completion returns one summary message to the parent; raw tool output is kept out of the parent's context.