A standalone multi-agent research system built on the Claude Agent SDK. A lead agent decomposes a topic into subtopics, dispatches researcher subagents in parallel, and then synthesizes their findings via a report-writer subagent.
⚠️ Scope & safety — please read before runningThis project is a basic learning harness for the Claude Agent SDK, intended for experimentation and for understanding the fundamentals of subagents, hooks, skills, and session tracking. It is not production-ready. In particular:
- Not sandboxed, not containerized. Agents run with your shell user's permissions and write directly to your filesystem.
- No special safeguards against prompt injection, so be selective and thoughtful about what sites you are searching with the harness in present state.
permission_mode="bypassPermissions"is the default, which auto-approves every tool call (web searches, file writes, etc.). Fine for local exploration on trusted directories; inadvisable elsewhere.- No monitoring, metrics, or alerting. Logs are plain files under
logs/. There is no cost tracking, rate limiting, dashboard, or telemetry export.- No retry or failure-recovery logic beyond what the SDK itself provides.
- Single user, single session. No multi-tenant safeguards, no authentication, no queueing.
- No secret management beyond reading
ANTHROPIC_API_KEYfrom a local.env.Use it to learn the SDK, fork it, and add the hardening your environment needs. For more featureful or differently-shaped starting points, see Anthropic's official reference implementations —
anthropics/claude-agent-sdk-demos(the demos repo this template was originally extracted from),anthropics/claude-quickstarts(customer-support agent, financial-data-analyst, computer-use, etc.), andanthropics/claude-cookbooks(recipes for tool use, RAG, multimodal, etc.). If you specifically want a Docker-based harness with sandboxing and observability layered on top of the SDK, see the companioncasdk-harnessproject.
- Multi-agent coordination — Lead agent orchestrates researcher and report-writer subagents
- Parallel execution — Multiple researchers investigate distinct subtopics simultaneously
- Hook-based tracking —
PreToolUse/PostToolUsehooks capture every tool call and attribute it to the right subagent viaparent_tool_use_id - Skills — Subagents can opt into project-local skills (the bundled
joplin-research+joplin-formattingpair is a worked example of a routing skill that delegates to a formatting skill) - Structured logs — Per-session transcripts (
transcript.txt) plus JSONL tool-call records (tool_calls.jsonl) - Test suite — pytest-based unit and smoke tests in
tests/
💡 Starting a new project? Click Use this template on GitHub to fork without inheriting history, or
gh repo create my-project --template andisab/casdk-template.
- Python ≥ 3.10
- uv package manager
- Claude Code CLI on
$PATH— install withnpm install -g @anthropic-ai/claude-codeif missing - Anthropic API key (get one here)
-
Run the setup script:
./scripts/setup.sh
-
Add your API key to
.env:ANTHROPIC_API_KEY=your_api_key_here
-
Run the agent:
./scripts/run.sh # or uv run agents/agent.py
You: Research recent developments in quantum computing
Agent: Researching 4 areas: hardware/qubits, algorithms/applications,
industry players/investments, and challenges/timeline.
Spawning researchers.
[🚀 Spawning RESEARCHER-1: Quantum hardware and qubit technology]
[🚀 Spawning RESEARCHER-2: Quantum algorithms and applications]
[🚀 Spawning RESEARCHER-3: Industry players and investments]
[🚀 Spawning RESEARCHER-4: Challenges and timeline]
[Researchers complete their work...]
[🚀 Spawning REPORT-WRITER-1: Synthesize research into final report]
Agent: Complete. Report: workspace/results/quantum_computing_summary_YYYYMMDD.txt
┌─────────────────┐
│ Lead Agent │ Coordinator (uses only the Agent tool)
└────────┬────────┘
│
├─────────────────────┬─────────────────────┬─────────────────────┐
│ │ │ │
┌────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│RESEARCHER-1│ │RESEARCHER-2 │ │RESEARCHER-3 │ │RESEARCHER-4 │
│ Subtopic A │ │ Subtopic B │ │ Subtopic C │ │ Subtopic D │
└────┬───────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │ │
│ WebSearch, Write │ WebSearch, Write │ WebSearch, Write │ WebSearch, Write
│ │ │ │
└──────────────────────┴─────────────────────┴─────────────────────┘
│
┌────▼────────────┐
│ REPORT-WRITER-1 │
│ Reads, synthesizes,│
│ creates report │
└─────────────────┘
Glob, Read, Write, Skill
- Role: Orchestrates the entire research process
- Tools:
Agent(only) — the subagent-spawning tool. NamedTaskon Claude Code builds before v2.1.63; the project allows both names for compatibility. - Model: Haiku
- Responsibilities:
- Breaks research requests into 2–4 subtopics
- Spawns researcher subagents in parallel
- Waits for all research to complete
- Spawns report-writer to synthesize findings
- Role: Gathers information on specific subtopics
- Tools:
WebSearch,Write - Model: Haiku
- Responsibilities:
- Conducts 3–7 web searches per subtopic
- Extracts key findings from authoritative sources
- Saves concise research notes to
workspace/research-notes/
- Role: Synthesizes research into professional reports
- Tools:
Glob,Read,Write,Skill - Skills:
joplin-research+joplin-formatting(both declared on theAgentDefinition; subagents do not inherit project skills automatically, andjoplin-researchdelegates markdown formatting tojoplin-formatting, so both must be reachable) - Model: Haiku
- Responsibilities:
- Reads all research notes from
workspace/research-notes/ - Applies formatting guidelines from the loaded skill
- Creates the final report in
workspace/results/
- Reads all research notes from
Joplin is a free, open-source, cross-platform note-taking app (desktop on Windows / macOS / Linux, mobile on iOS / Android, plus a terminal client). It stores notes as Markdown with optional end-to-end-encrypted sync over WebDAV, Nextcloud, Dropbox, OneDrive, S3, or Joplin Cloud. Source and downloads: https://github.com/laurent22/joplin.
The bundled joplin-research and joplin-formatting skills produce reports tuned for Joplin's renderer — heading hierarchy, link styling, callouts, code blocks, and tables follow the conventions Joplin's CSS expects. The output is plain Markdown, so it also renders fine in any other Markdown viewer; the formatting is just optimized for Joplin. To use a generated report, copy the contents of workspace/results/<report>.md into a new Joplin note (or use the Joplin import feature for a .md file).
The Joplin skills are an example of a "routing skill that delegates to a formatting skill." If you don't use Joplin, you can swap in your own formatting skill (e.g. for Notion, Obsidian, Confluence) following the same pattern — see
.claude/skills/README.md.
casdk-template/
├── .env.example # Environment variables template
├── .gitignore
├── pyproject.toml # Project + dev dependency groups + pytest config
├── README.md # This file
├── CLAUDE.md # Project context for Claude Code
│
├── agents/ # Application package
│ ├── agent.py # Entry point (ClaudeSDKClient + AgentDefinitions + hooks)
│ ├── prompts/ # Agent system prompts
│ │ ├── lead_agent.txt
│ │ ├── researcher.txt
│ │ └── report_writer.txt
│ └── utils/
│ ├── message_handler.py # Block dispatch, subagent spawn detection
│ ├── subagent_tracker.py # Hook callbacks, JSONL log, parent_tool_use_id attribution
│ └── transcript.py # Session directory + dual stdout/file writer
│
├── .claude/ # Claude Code project settings
│ └── skills/
│ ├── README.md # Skills documentation
│ ├── joplin-research/ # Bundled skill: Joplin-formatted research output
│ │ ├── SKILL.md # Routes by request type (rundown, survey, summary, ...)
│ │ └── resources/
│ │ ├── citation-guide.md
│ │ ├── image-policy.md
│ │ └── templates/ # article-summary, book-summary, research-notes,
│ │ # technical-rundown, technical-survey, whitepaper-summary
│ └── joplin-formatting/ # Markdown formatting rules; invoked by joplin-research
│ ├── SKILL.md
│ └── resources/
│ ├── css-reference.md
│ ├── element-catalog.md
│ └── validate_joplin_md.py
│
├── scripts/ # Development utilities
│ ├── setup.sh # Initial setup (uv sync, .env scaffolding)
│ ├── run.sh # Quick run wrapper
│ ├── test.sh # Run the test suite (forwards args to pytest)
│ ├── clean_logs.sh # Clean old session logs
│ └── analyze_logs.py # Parse session JSONL logs
│
├── tests/ # Unit + smoke tests
│ ├── conftest.py # Shared fixtures
│ ├── test_agent_setup.py # Module-import smoke + regression guards
│ ├── test_message_handler.py # Block dispatch, Agent / Task spawn detection
│ ├── test_subagent_tracker.py # Hook returns, JSONL output, attribution
│ └── test_transcript.py # setup_session, TranscriptWriter
│
├── workspace/ # Generated by agents (gitignored)
│ ├── research-notes/
│ └── results/
│
└── logs/ # Session logs (gitignored)
└── session_YYYYMMDD_HHMMSS/
├── transcript.txt # Human-readable log
└── tool_calls.jsonl # Structured tool data
-
Create a new prompt file in
agents/prompts/. -
Define the agent in
agents/agent.py:agents = { "your-agent": AgentDefinition( description="What this agent does and when to use it", tools=["Tool1", "Tool2"], prompt=load_prompt("your_agent.txt"), model="haiku", # Optional: declare project skills the subagent can load skills=["your-skill"], # Optional: cap turns to prevent runaway loops maxTurns=10, ) }
-
Update the lead agent prompt to know when to use the new agent.
Subagents cannot spawn their own subagents. Don't include
Agentin a subagent'stoolslist.
To give an agent additional tools, edit its tools list in the AgentDefinition:
tools=["WebSearch", "Write", "Bash", "Read"]Common tools: WebSearch, WebFetch, Write, Read, Edit, Glob, Grep, Bash, Agent, Skill. See the SDK docs for the full list.
Skills are optional, modular knowledge packages that agents can load on demand. To create one:
mkdir -p .claude/skills/your-skill-name
# Create SKILL.md (and an optional resources/ subdir)Then declare the skill on each subagent that should be able to load it:
AgentDefinition(
...,
skills=["your-skill-name"],
)
⚠️ Subagents do not inherit project skills automatically — even withsetting_sources=["project"]. Each subagent that needs a skill must list it explicitly. See.claude/skills/README.mdfor detailed examples and the bundledjoplin-researchskill for a working reference.
# Most recent session
python scripts/analyze_logs.py
# A specific session
python scripts/analyze_logs.py session_YYYYMMDD_HHMMSSThe test suite covers the utility modules, hook callbacks, and key SDK regressions (e.g. the Task → Agent tool rename, canonical hook return shape, and the skills=[…] declaration on the report-writer).
# Run the full suite
./scripts/test.sh
# Or directly via uv (installs dev deps on first run)
uv run --group dev pytest
# Filter by name
./scripts/test.sh -k message_handler -vvStyle and import order are enforced with ruff. CI runs ruff check and ruff format --check on every push.
# Check
./scripts/lint.sh
# Auto-fix what can be fixed
uv run --group dev ruff check --fix
uv run --group dev ruff format# Keep last 7 days (default)
./scripts/clean_logs.sh
# Keep last 30 days
./scripts/clean_logs.sh 30The system registers PreToolUse and PostToolUse hooks that fire for every tool call, regardless of which agent issues it:
hooks = {
"PreToolUse": [HookMatcher(matcher=None, hooks=[tracker.pre_tool_use_hook])],
"PostToolUse": [HookMatcher(matcher=None, hooks=[tracker.post_tool_use_hook])],
}Key features:
- Tracks which agent (
RESEARCHER-1,RESEARCHER-2,REPORT-WRITER-1, …) issued which tool calls. - Logs input parameters and outputs.
- Associates each call with the parent Agent invocation via
parent_tool_use_id. - Writes both human-readable transcripts and structured JSONL records.
- Hooks return
{}(canonical SDK shape — non-blocking, no decision).
- User sends a research request.
- Lead agent analyzes it and breaks it into subtopics.
- Lead agent spawns researchers in parallel using the
Agenttool. - Each researcher runs in its own context; messages from inside that subagent carry a
parent_tool_use_idlinking back to the spawning tool call. Hooks use that ID to attribute every nested tool call to the correct researcher. - Researchers complete and save findings to
workspace/research-notes/. - Lead agent spawns the report-writer.
- Report-writer reads the notes, optionally loads the
joplin-researchskill, and writes the final report toworkspace/results/.
ANTHROPIC_API_KEY— Anthropic API key (required)
Subagent definitions and SDK options are constructed by two factory functions in agents/agent.py:
build_agents(*, researcher_prompt, report_writer_prompt)— returns thedict[str, AgentDefinition]of subagents. Edit this to change models, tools, skill declarations, or to add new subagents. Calling it from a test (with dummy prompt strings) lets you assert on the constructedAgentDefinitions without spinning up a session — seetests/test_agent_setup.py.build_options(*, system_prompt, agents, hooks=None)— returns theClaudeAgentOptions. Edit this to changepermission_mode,model,allowed_tools,setting_sources, or to passcwd=....
Common knobs:
- Models:
model="haiku"→"sonnet"or"opus"for stronger reasoning at higher cost."inherit"is also valid on subagents (use the parent's model). - Permission mode: replace
"bypassPermissions"with one of the documented values:"default"(prompt for each tool),"acceptEdits","plan","dontAsk", or"auto". See the SDK docs for the current semantics of each. - Allowed tools: tighten or loosen the per-agent
toolslists inbuild_agents(). - Working directory: pass
cwd=...onClaudeAgentOptionsto redirect file output.
Run ./scripts/setup.sh and edit .env with your API key.
Install uv:
curl -LsSf https://astral.sh/uv/install.sh | shThe Claude Agent SDK requires the Claude Code CLI on $PATH. Install it:
npm install -g @anthropic-ai/claude-code
# verify
which claudeIf the agent runs but no files appear in workspace/, check:
- File output is relative to the directory where you invoked the agent.
- The
workspace/directory is created automatically — check write permissions. - Inspect session logs:
cat logs/session_*/transcript.txtandlogs/session_*/tool_calls.jsonl.
This is a personal learning template for the Claude Agent SDK; issues and pull requests are not actively triaged. The expected use pattern is to fork it (or click Use this template) and adapt it to your own project. The architecture, prompts, and skills are all intended to be edited.
- Rate limiting and budget caps on top of the per-session cost reporting
- Optional Agent-as-a-file (
.claude/agents/*.md) loading alongside programmaticAgentDefinitions - More worked-example skills (citations, structured-output formatting, domain-specific research types)
Based on the Claude Agent SDK demo by Anthropic. Modified for standalone development.