main a9c71002d2ca cached
156 files
1.2 MB
336.2k tokens
556 symbols
2 requests
Download .txt
Showing preview only (1,240K chars total). Download the full file or copy to clipboard to get everything.
Repository: shareAI-lab/learn-claude-code
Branch: main
Commit: a9c71002d2ca
Files: 156
Total size: 1.2 MB

Directory structure:
gitextract_p2krhmdp/

├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── test.yml
├── .gitignore
├── LICENSE
├── README-ja.md
├── README-zh.md
├── README.md
├── agents/
│   ├── __init__.py
│   ├── s01_agent_loop.py
│   ├── s02_tool_use.py
│   ├── s03_todo_write.py
│   ├── s04_subagent.py
│   ├── s05_skill_loading.py
│   ├── s06_context_compact.py
│   ├── s07_task_system.py
│   ├── s08_background_tasks.py
│   ├── s09_agent_teams.py
│   ├── s10_team_protocols.py
│   ├── s11_autonomous_agents.py
│   ├── s12_worktree_task_isolation.py
│   └── s_full.py
├── docs/
│   ├── en/
│   │   ├── s01-the-agent-loop.md
│   │   ├── s02-tool-use.md
│   │   ├── s03-todo-write.md
│   │   ├── s04-subagent.md
│   │   ├── s05-skill-loading.md
│   │   ├── s06-context-compact.md
│   │   ├── s07-task-system.md
│   │   ├── s08-background-tasks.md
│   │   ├── s09-agent-teams.md
│   │   ├── s10-team-protocols.md
│   │   ├── s11-autonomous-agents.md
│   │   └── s12-worktree-task-isolation.md
│   ├── ja/
│   │   ├── s01-the-agent-loop.md
│   │   ├── s02-tool-use.md
│   │   ├── s03-todo-write.md
│   │   ├── s04-subagent.md
│   │   ├── s05-skill-loading.md
│   │   ├── s06-context-compact.md
│   │   ├── s07-task-system.md
│   │   ├── s08-background-tasks.md
│   │   ├── s09-agent-teams.md
│   │   ├── s10-team-protocols.md
│   │   ├── s11-autonomous-agents.md
│   │   └── s12-worktree-task-isolation.md
│   └── zh/
│       ├── s01-the-agent-loop.md
│       ├── s02-tool-use.md
│       ├── s03-todo-write.md
│       ├── s04-subagent.md
│       ├── s05-skill-loading.md
│       ├── s06-context-compact.md
│       ├── s07-task-system.md
│       ├── s08-background-tasks.md
│       ├── s09-agent-teams.md
│       ├── s10-team-protocols.md
│       ├── s11-autonomous-agents.md
│       └── s12-worktree-task-isolation.md
├── requirements.txt
├── skills/
│   ├── agent-builder/
│   │   ├── SKILL.md
│   │   ├── references/
│   │   │   ├── agent-philosophy.md
│   │   │   ├── minimal-agent.py
│   │   │   ├── subagent-pattern.py
│   │   │   └── tool-templates.py
│   │   └── scripts/
│   │       └── init_agent.py
│   ├── code-review/
│   │   └── SKILL.md
│   ├── mcp-builder/
│   │   └── SKILL.md
│   └── pdf/
│       └── SKILL.md
└── web/
    ├── .gitignore
    ├── README.md
    ├── next.config.ts
    ├── package.json
    ├── postcss.config.mjs
    ├── scripts/
    │   └── extract-content.ts
    ├── src/
    │   ├── app/
    │   │   ├── [locale]/
    │   │   │   ├── (learn)/
    │   │   │   │   ├── [version]/
    │   │   │   │   │   ├── client.tsx
    │   │   │   │   │   ├── diff/
    │   │   │   │   │   │   ├── diff-content.tsx
    │   │   │   │   │   │   └── page.tsx
    │   │   │   │   │   └── page.tsx
    │   │   │   │   ├── compare/
    │   │   │   │   │   └── page.tsx
    │   │   │   │   ├── layers/
    │   │   │   │   │   └── page.tsx
    │   │   │   │   ├── layout.tsx
    │   │   │   │   └── timeline/
    │   │   │   │       └── page.tsx
    │   │   │   ├── layout.tsx
    │   │   │   └── page.tsx
    │   │   ├── globals.css
    │   │   └── page.tsx
    │   ├── components/
    │   │   ├── architecture/
    │   │   │   ├── arch-diagram.tsx
    │   │   │   ├── design-decisions.tsx
    │   │   │   ├── execution-flow.tsx
    │   │   │   └── message-flow.tsx
    │   │   ├── code/
    │   │   │   └── source-viewer.tsx
    │   │   ├── diff/
    │   │   │   ├── code-diff.tsx
    │   │   │   └── whats-new.tsx
    │   │   ├── docs/
    │   │   │   └── doc-renderer.tsx
    │   │   ├── layout/
    │   │   │   ├── header.tsx
    │   │   │   └── sidebar.tsx
    │   │   ├── simulator/
    │   │   │   ├── agent-loop-simulator.tsx
    │   │   │   ├── simulator-controls.tsx
    │   │   │   └── simulator-message.tsx
    │   │   ├── timeline/
    │   │   │   └── timeline.tsx
    │   │   ├── ui/
    │   │   │   ├── badge.tsx
    │   │   │   ├── card.tsx
    │   │   │   └── tabs.tsx
    │   │   └── visualizations/
    │   │       ├── index.tsx
    │   │       ├── s01-agent-loop.tsx
    │   │       ├── s02-tool-dispatch.tsx
    │   │       ├── s03-todo-write.tsx
    │   │       ├── s04-subagent.tsx
    │   │       ├── s05-skill-loading.tsx
    │   │       ├── s06-context-compact.tsx
    │   │       ├── s07-task-system.tsx
    │   │       ├── s08-background-tasks.tsx
    │   │       ├── s09-agent-teams.tsx
    │   │       ├── s10-team-protocols.tsx
    │   │       ├── s11-autonomous-agents.tsx
    │   │       ├── s12-worktree-task-isolation.tsx
    │   │       └── shared/
    │   │           └── step-controls.tsx
    │   ├── data/
    │   │   ├── annotations/
    │   │   │   ├── s01.json
    │   │   │   ├── s02.json
    │   │   │   ├── s03.json
    │   │   │   ├── s04.json
    │   │   │   ├── s05.json
    │   │   │   ├── s06.json
    │   │   │   ├── s07.json
    │   │   │   ├── s08.json
    │   │   │   ├── s09.json
    │   │   │   ├── s10.json
    │   │   │   ├── s11.json
    │   │   │   └── s12.json
    │   │   ├── execution-flows.ts
    │   │   ├── generated/
    │   │   │   ├── docs.json
    │   │   │   └── versions.json
    │   │   └── scenarios/
    │   │       ├── s01.json
    │   │       ├── s02.json
    │   │       ├── s03.json
    │   │       ├── s04.json
    │   │       ├── s05.json
    │   │       ├── s06.json
    │   │       ├── s07.json
    │   │       ├── s08.json
    │   │       ├── s09.json
    │   │       ├── s10.json
    │   │       ├── s11.json
    │   │       └── s12.json
    │   ├── hooks/
    │   │   ├── useDarkMode.ts
    │   │   ├── useSimulator.ts
    │   │   └── useSteppedVisualization.ts
    │   ├── i18n/
    │   │   └── messages/
    │   │       ├── en.json
    │   │       ├── ja.json
    │   │       └── zh.json
    │   ├── lib/
    │   │   ├── constants.ts
    │   │   ├── i18n-server.ts
    │   │   ├── i18n.tsx
    │   │   └── utils.ts
    │   └── types/
    │       └── agent-data.ts
    ├── tsconfig.json
    └── vercel.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: web

    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: web/package-lock.json

      - name: Install dependencies
        run: npm ci

      - name: Type check
        run: npx tsc --noEmit

      - name: Build
        run: npm run build


================================================
FILE: .github/workflows/test.yml
================================================
name: Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  unit-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: pip install anthropic python-dotenv

      - name: Run unit tests
        run: python tests/test_unit.py

  session-test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        session: [v0, v1, v2, v3, v4, v5, v6, v7, v8a, v8b, v8c, v9]
    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: pip install anthropic python-dotenv

      - name: Run ${{ matrix.session }} tests
        env:
          TEST_API_KEY: ${{ secrets.TEST_API_KEY }}
          TEST_BASE_URL: ${{ secrets.TEST_BASE_URL }}
          TEST_MODEL: ${{ secrets.TEST_MODEL }}
        run: python tests/test_${{ matrix.session }}.py

  web-build:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: web
    steps:
      - uses: actions/checkout@v6

      - name: Set up Node.js
        uses: actions/setup-node@v6
        with:
          node-version: "20"
          cache: "npm"
          cache-dependency-path: web/package-lock.json

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
/lib/
/lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# UV
#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#uv.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#   pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
#   https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/

# pixi
#   Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
#   Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
#   in the .venv directory. It is recommended not to include this directory in version control.
.pixi

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/

# Visual Studio Code
#  Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 
#  that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
#  and can be added to the global gitignore or merged into this file. However, if you prefer, 
#  you could uncomment the following to ignore the entire vscode folder
# .vscode/

# Transcripts (generated by compression agent)
.transcripts/

# Runtime artifacts (generated by agent tests)
.task_outputs/
.tasks/
.teams/

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc

# Cursor
#  Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
#  exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
#  refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore

# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/

# Web app
web/node_modules/
web/.next/
web/out/
.vercel
.env*.local
test_providers.py

# Internal analysis artifacts (not learning material)
analysis/
analysis_progress.md


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 shareAI Lab

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README-ja.md
================================================
# Learn Claude Code -- 真の Agent のための Harness Engineering

[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)

## モデルこそが Agent である

コードの話をする前に、一つだけ明確にしておく。

**Agent とはモデルのことだ。フレームワークではない。プロンプトチェーンではない。ドラッグ&ドロップのワークフローではない。**

### Agent とは何か

Agent とはニューラルネットワークである -- Transformer、RNN、学習された関数 -- 数十億回の勾配更新を経て、行動系列データの上で環境を知覚し、目標を推論し、行動を起こすことを学んだもの。AI における "Agent" という言葉は、始まりからずっとこの意味だった。常に。

人間も Agent だ。数百万年の進化的訓練によって形作られた生物的ニューラルネットワーク。感覚で世界を知覚し、脳で推論し、身体で行動する。DeepMind、OpenAI、Anthropic が "Agent" と言うとき、それはこの分野が誕生以来ずっと意味してきたものと同じだ:**行動することを学んだモデル。**

歴史がその証拠を刻んでいる:

- **2013 -- DeepMind DQN が Atari をプレイ。** 単一のニューラルネットワークが、生のピクセルとスコアだけを受け取り、7 つの Atari 2600 ゲームを学習 -- すべての先行アルゴリズムを超え、3 つで人間の専門家を打ち負かした。2015 年には同じアーキテクチャが [49 ゲームに拡張され、プロのテスターに匹敵](https://www.nature.com/articles/nature14236)、*Nature* に掲載。ゲーム固有のルールなし。決定木なし。一つのモデルが経験から学んだ。そのモデルが Agent だった。

- **2019 -- OpenAI Five が Dota 2 を制覇。** 5 つのニューラルネットワークが 10 ヶ月間で [45,000 年分の Dota 2](https://openai.com/index/openai-five-defeats-dota-2-world-champions/) を自己対戦し、サンフランシスコのライブストリームで **OG** -- TI8 世界王者 -- を 2-0 で撃破。その後の公開アリーナでは 42,729 試合で勝率 99.4%。スクリプト化された戦略なし。メタプログラムされたチーム連携なし。モデルが完全に自己対戦を通じてチームワーク、戦術、リアルタイム適応を学んだ。

- **2019 -- DeepMind AlphaStar が StarCraft II をマスター。** AlphaStar は非公開戦で[プロ選手を 10-1 で撃破](https://deepmind.google/blog/alphastar-mastering-the-real-time-strategy-game-starcraft-ii/)、その後ヨーロッパサーバーで[グランドマスター到達](https://www.nature.com/articles/d41586-019-03298-6) -- 90,000 人中の上位 0.15%。不完全情報、リアルタイム判断、チェスや囲碁を遥かに凌駕する組合せ的行動空間を持つゲーム。Agent とは? モデルだ。訓練されたもの。スクリプトではない。

- **2019 -- Tencent 絶悟が王者栄耀を支配。** Tencent AI Lab の「絶悟」は 2019 年 8 月 2 日、世界チャンピオンカップで [KPL プロ選手を 5v5 で撃破](https://www.jiemian.com/article/3371171.html)。1v1 モードではプロが [15 戦中 1 勝のみ、8 分以上生存不可](https://developer.aliyun.com/article/851058)。訓練強度:1 日 = 人間の 440 年。2021 年までに全ヒーロープールで KPL プロを全面的に上回った。手書きのヒーロー相性表なし。スクリプト化されたチーム編成なし。自己対戦でゲーム全体をゼロから学んだモデル。

- **2024-2025 -- LLM Agent がソフトウェアエンジニアリングを再構築。** Claude、GPT、Gemini -- 人類のコードと推論の全幅で訓練された大規模言語モデル -- がコーディング Agent として展開される。コードベースを読み、実装を書き、障害をデバッグし、チームで協調する。アーキテクチャは先行するすべての Agent と同一:訓練されたモデルが環境に配置され、知覚と行動のツールを与えられる。唯一の違いは、学んだものの規模と解くタスクの汎用性。

すべてのマイルストーンが同じ真理を共有している:**"Agent" は決して周囲のコードではない。Agent は常にモデルそのものだ。**

### Agent ではないもの

"Agent" という言葉は、プロンプト配管工の産業全体に乗っ取られてしまった。

ドラッグ&ドロップのワークフロービルダー。ノーコード "AI Agent" プラットフォーム。プロンプトチェーン・オーケストレーションライブラリ。すべて同じ幻想を共有している:LLM API 呼び出しを if-else 分岐、ノードグラフ、ハードコードされたルーティングロジックで繋ぎ合わせることが "Agent の構築" だと。

違う。彼らが作ったものはルーブ・ゴールドバーグ・マシンだ -- 過剰に設計された脆い手続き的ルールのパイプライン。LLM は美化されたテキスト補完ノードとして押し込まれているだけ。それは Agent ではない。壮大な妄想を持つシェルスクリプトだ。

**プロンプト配管工式 "Agent" は、モデルを訓練しないプログラマーの妄想だ。** 手続き的ロジックを積み重ねて知能を力技で再現しようとする -- 巨大なルールツリー、ノードグラフ、チェーン・プロンプトの滝 -- そして十分なグルーコードがいつか自律的振る舞いを創発すると祈る。しない。工学的手段で Agency をコーディングすることはできない。Agency は学習されるものであって、プログラムされるものではない。

あのシステムたちは生まれた瞬間から死んでいる:脆弱で、スケールせず、汎化が根本的に不可能。GOFAI(Good Old-Fashioned AI、古典的記号 AI)の現代版だ -- 何十年も前に学術界が放棄した記号ルールシステムが、LLM のペンキを塗り直して再登場した。パッケージが違うだけで、同じ袋小路。

### マインドシフト:「Agent を開発する」から Harness を開発する へ

「Agent を開発しています」と言うとき、意味できるのは二つだけだ:

**1. モデルを訓練する。** 強化学習、ファインチューニング、RLHF、その他の勾配ベースの手法で重みを調整する。タスクプロセスデータ -- 実ドメインにおける知覚・推論・行動の実際の系列 -- を収集し、モデルの振る舞いを形成する。DeepMind、OpenAI、Tencent AI Lab、Anthropic が行っていること。これが最も本来的な Agent 開発。

**2. Harness を構築する。** モデルに動作環境を提供するコードを書く。私たちの大半が行っていることであり、このリポジトリの核心。

Harness とは、Agent が特定のドメインで機能するために必要なすべて:

```
Harness = Tools + Knowledge + Observation + Action Interfaces + Permissions

    Tools:          ファイル I/O、シェル、ネットワーク、データベース、ブラウザ
    Knowledge:      製品ドキュメント、ドメイン資料、API 仕様、スタイルガイド
    Observation:    git diff、エラーログ、ブラウザ状態、センサーデータ
    Action:         CLI コマンド、API 呼び出し、UI インタラクション
    Permissions:    サンドボックス、承認ワークフロー、信頼境界
```

モデルが決断する。Harness が実行する。モデルが推論する。Harness がコンテキストを提供する。モデルはドライバー。Harness は車両。

**コーディング Agent の Harness は IDE、ターミナル、ファイルシステム。** 農業 Agent の Harness はセンサーアレイ、灌漑制御、気象データフィード。ホテル Agent の Harness は予約システム、ゲストコミュニケーションチャネル、施設管理 API。Agent -- 知性、意思決定者 -- は常にモデル。Harness はドメインごとに変わる。Agent はドメインを超えて汎化する。

このリポジトリは車両の作り方を教える。コーディング用の車両だ。だが設計パターンはあらゆるドメインに汎化する:農場管理、ホテル運営、工場製造、物流、医療、教育、科学研究。タスクが知覚され、推論され、実行される必要がある場所ならどこでも -- Agent には Harness が要る。

### Harness エンジニアの仕事

このリポジトリを読んでいるなら、あなたはおそらく Harness エンジニアだ -- それは強力なアイデンティティ。以下があなたの本当の仕事:

- **ツールの実装。** Agent に手を与える。ファイル読み書き、シェル実行、API 呼び出し、ブラウザ制御、データベースクエリ。各ツールは Agent が環境内で取れる行動。原子的で、組み合わせ可能で、記述が明確であるように設計する。

- **知識のキュレーション。** Agent にドメイン専門性を与える。製品ドキュメント、アーキテクチャ決定記録、スタイルガイド、規制要件。オンデマンドで読み込み(s05)、前もって詰め込まない。Agent は何が利用可能か知った上で、必要なものを自ら取得すべき。

- **コンテキストの管理。** Agent にクリーンな記憶を与える。サブ Agent 隔離(s04)がノイズの漏洩を防ぐ。コンテキスト圧縮(s06)が履歴の氾濫を防ぐ。タスクシステム(s07)が目標を単一の会話を超えて永続化する。

- **権限の制御。** Agent に境界を与える。ファイルアクセスのサンドボックス化。破壊的操作への承認要求。Agent と外部システム間の信頼境界の実施。安全工学と Harness 工学の交差点。

- **タスクプロセスデータの収集。** Agent があなたの Harness 内で実行するすべての行動系列は訓練シグナル。実デプロイメントの知覚-推論-行動トレースは、次世代 Agent モデルをファインチューニングする原材料。あなたの Harness は Agent に仕えるだけでなく -- Agent を進化させる助けにもなる。

あなたは知性を書いているのではない。知性が住まう世界を構築している。その世界の品質 -- Agent がどれだけ明瞭に知覚でき、どれだけ正確に行動でき、利用可能な知識がどれだけ豊かか -- が、知性がどれだけ効果的に自らを表現できるかを直接決定する。

**優れた Harness を作れ。Agent が残りをやる。**

### なぜ Claude Code か -- Harness Engineering の大師範

なぜこのリポジトリは特に Claude Code を解剖するのか?

Claude Code は私たちが見てきた中で最もエレガントで完成度の高い Agent Harness だからだ。単一の巧妙なトリックのためではなく、それが *しないこと* のために:Agent そのものになろうとしない。硬直的なワークフローを押し付けない。精緻な決定木でモデルを二度推しない。ツール、知識、コンテキスト管理、権限境界をモデルに提供し -- そして道を譲る。

Claude Code の本質を剥き出しにすると:

```
Claude Code = 一つの agent loop
            + ツール (bash, read, write, edit, glob, grep, browser...)
            + オンデマンド skill ロード
            + コンテキスト圧縮
            + サブ Agent スポーン
            + 依存グラフ付きタスクシステム
            + 非同期メールボックスによるチーム協調
            + worktree 分離による並列実行
            + 権限ガバナンス
```

これがすべてだ。これが全アーキテクチャ。すべてのコンポーネントは Harness メカニズム -- Agent が住む世界の一部。Agent そのものは? Claude だ。モデル。Anthropic が人類の推論とコードの全幅で訓練した。Harness が Claude を賢くしたのではない。Claude は元々賢い。Harness が Claude に手と目とワークスペースを与えた。

これが Claude Code が理想的な教材である理由だ:**モデルを信頼し、工学的努力を Harness に集中させるとどうなるかを示している。** このリポジトリの各セッション(s01-s12)は Claude Code アーキテクチャから一つの Harness メカニズムをリバースエンジニアリングする。終了時には、Claude Code の仕組みだけでなく、あらゆるドメインのあらゆる Agent に適用される Harness 工学の普遍的原則を理解している。

教訓は「Claude Code をコピーせよ」ではない。教訓は:**最高の Agent プロダクトは、自分の仕事が Harness であって Intelligence ではないと理解しているエンジニアが作る。**

---

## ビジョン:宇宙を本物の Agent で満たす

これはコーディング Agent だけの話ではない。

人間が複雑で多段階の判断集約的な仕事をしているすべてのドメインは、Agent が稼働できるドメインだ -- 正しい Harness さえあれば。このリポジトリのパターンは普遍的だ:

```
不動産管理 Agent  = モデル + 物件センサー + メンテナンスツール + テナント通信
農業 Agent        = モデル + 土壌/気象データ + 灌漑制御 + 作物知識
ホテル運営 Agent  = モデル + 予約システム + ゲストチャネル + 施設 API
医学研究 Agent    = モデル + 文献検索 + 実験機器 + プロトコル文書
製造 Agent        = モデル + 生産ラインセンサー + 品質管理 + 物流
教育 Agent        = モデル + カリキュラム知識 + 学生進捗 + 評価ツール
```

ループは常に同じ。ツールが変わる。知識が変わる。権限が変わる。Agent -- モデル -- がすべてを汎化する。

このリポジトリを読むすべての Harness エンジニアは、ソフトウェアエンジニアリングを遥かに超えたパターンを学んでいる。知的で自動化された未来のためのインフラストラクチャを構築することを学んでいる。実ドメインにデプロイされた優れた Harness の一つ一つが、Agent が知覚し、推論し、行動できる新たな拠点。

まずワークショップを満たす。次に農場、病院、工場。次に都市。次に惑星。

**Bash is all you need. Real agents are all the universe needs.**

---

```
                    THE AGENT PATTERN
                    =================

    User --> messages[] --> LLM --> response
                                      |
                            stop_reason == "tool_use"?
                           /                          \
                         yes                           no
                          |                             |
                    execute tools                    return text
                    append results
                    loop back -----------------> messages[]


    最小ループ。すべての AI Agent にこのループが必要だ。
    モデルがツール呼び出しと停止を決める。
    コードはモデルの要求を実行するだけ。
    このリポジトリはこのループを囲むすべて --
    Agent を特定ドメインで効果的にする Harness -- の作り方を教える。
```

**12 の段階的セッション、シンプルなループから分離された自律実行まで。**
**各セッションは 1 つの Harness メカニズムを追加する。各メカニズムには 1 つのモットーがある。**

> **s01**   *"One loop & Bash is all you need"* — 1つのツール + 1つのループ = エージェント
>
> **s02**   *"ツールを足すなら、ハンドラーを1つ足すだけ"* — ループは変わらない。新ツールは dispatch map に登録するだけ
>
> **s03**   *"計画のないエージェントは行き当たりばったり"* — まずステップを書き出し、それから実行
>
> **s04**   *"大きなタスクを分割し、各サブタスクにクリーンなコンテキストを"* — サブエージェントは独立した messages[] を使い、メイン会話を汚さない
>
> **s05**   *"必要な知識を、必要な時に読み込む"* — system prompt ではなく tool_result で注入
>
> **s06**   *"コンテキストはいつか溢れる、空ける手段が要る"* — 3層圧縮で無限セッションを実現
>
> **s07**   *"大きな目標を小タスクに分解し、順序付けし、ディスクに記録する"* — ファイルベースのタスクグラフ、マルチエージェント協調の基盤
>
> **s08**   *"遅い操作はバックグラウンドへ、エージェントは次を考え続ける"* — デーモンスレッドがコマンド実行、完了後に通知を注入
>
> **s09**   *"一人で終わらないなら、チームメイトに任せる"* — 永続チームメイト + 非同期メールボックス
>
> **s10**   *"チームメイト間には統一の通信ルールが必要"* — 1つの request-response パターンが全交渉を駆動
>
> **s11**   *"チームメイトが自らボードを見て、仕事を取る"* — リーダーが逐一割り振る必要はない
>
> **s12**   *"各自のディレクトリで作業し、互いに干渉しない"* — タスクは目標を管理、worktree はディレクトリを管理、IDで紐付け

---

## コアパターン

```python
def agent_loop(messages):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM,
            messages=messages, tools=TOOLS,
        )
        messages.append({"role": "assistant",
                         "content": response.content})

        if response.stop_reason != "tool_use":
            return

        results = []
        for block in response.content:
            if block.type == "tool_use":
                output = TOOL_HANDLERS[block.name](**block.input)
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output,
                })
        messages.append({"role": "user", "content": results})
```

各セッションはこのループの上に 1 つの Harness メカニズムを重ねる -- ループ自体は変わらない。ループは Agent のもの。メカニズムは Harness のもの。

## スコープ (重要)

このリポジトリは Harness 工学の 0->1 学習プロジェクト -- Agent モデルを囲む環境の構築を学ぶ。
学習を優先するため、以下の本番メカニズムは意図的に簡略化または省略している:

- 完全なイベント / Hook バス (例: PreToolUse, SessionStart/End, ConfigChange)。
  s12 では教材用に最小の追記型ライフサイクルイベントのみ実装。
- ルールベースの権限ガバナンスと信頼フロー
- セッションライフサイクル制御 (resume/fork) と高度な worktree ライフサイクル制御
- MCP ランタイムの詳細 (transport/OAuth/リソース購読/ポーリング)

このリポジトリの JSONL メールボックス方式は教材用の実装であり、特定の本番内部実装を主張するものではない。

## クイックスタート

```sh
git clone https://github.com/shareAI-lab/learn-claude-code
cd learn-claude-code
pip install -r requirements.txt
cp .env.example .env   # .env を編集して ANTHROPIC_API_KEY を入力

python agents/s01_agent_loop.py       # ここから開始
python agents/s12_worktree_task_isolation.py  # 全セッションの到達点
python agents/s_full.py               # 総括: 全メカニズム統合
```

### Web プラットフォーム

インタラクティブな可視化、ステップスルーアニメーション、ソースビューア、各セッションのドキュメント。

```sh
cd web && npm install && npm run dev   # http://localhost:3000
```

## 学習パス

```
フェーズ1: ループ                     フェーズ2: 計画と知識
==================                   ==============================
s01  エージェントループ      [1]     s03  TodoWrite               [5]
     while + stop_reason                  TodoManager + nag リマインダー
     |                                    |
     +-> s02  Tool Use            [4]     s04  サブエージェント      [5]
              dispatch map: name->handler     子ごとに新しい messages[]
                                              |
                                         s05  Skills               [5]
                                              SKILL.md を tool_result で注入
                                              |
                                         s06  Context Compact      [5]
                                              3層コンテキスト圧縮

フェーズ3: 永続化                     フェーズ4: チーム
==================                   =====================
s07  タスクシステム           [8]     s09  エージェントチーム      [9]
     ファイルベース CRUD + 依存グラフ      チームメイト + JSONL メールボックス
     |                                    |
s08  バックグラウンドタスク   [6]     s10  チームプロトコル        [12]
     デーモンスレッド + 通知キュー         シャットダウン + プラン承認 FSM
                                          |
                                     s11  自律エージェント        [14]
                                          アイドルサイクル + 自動クレーム
                                     |
                                     s12  Worktree 分離           [16]
                                          タスク調整 + 必要時の分離実行レーン

                                     [N] = ツール数
```

## プロジェクト構成

```
learn-claude-code/
|
|-- agents/                        # Python リファレンス実装 (s01-s12 + s_full 総括)
|-- docs/{en,zh,ja}/               # メンタルモデル優先のドキュメント (3言語)
|-- web/                           # インタラクティブ学習プラットフォーム (Next.js)
|-- skills/                        # s05 の Skill ファイル
+-- .github/workflows/ci.yml      # CI: 型チェック + ビルド
```

## ドキュメント

メンタルモデル優先: 問題、解決策、ASCII図、最小限のコード。
[English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/)

| セッション | トピック | モットー |
|-----------|---------|---------|
| [s01](./docs/ja/s01-the-agent-loop.md) | エージェントループ | *One loop & Bash is all you need* |
| [s02](./docs/ja/s02-tool-use.md) | Tool Use | *ツールを足すなら、ハンドラーを1つ足すだけ* |
| [s03](./docs/ja/s03-todo-write.md) | TodoWrite | *計画のないエージェントは行き当たりばったり* |
| [s04](./docs/ja/s04-subagent.md) | サブエージェント | *大きなタスクを分割し、各サブタスクにクリーンなコンテキストを* |
| [s05](./docs/ja/s05-skill-loading.md) | Skills | *必要な知識を、必要な時に読み込む* |
| [s06](./docs/ja/s06-context-compact.md) | Context Compact | *コンテキストはいつか溢れる、空ける手段が要る* |
| [s07](./docs/ja/s07-task-system.md) | タスクシステム | *大きな目標を小タスクに分解し、順序付けし、ディスクに記録する* |
| [s08](./docs/ja/s08-background-tasks.md) | バックグラウンドタスク | *遅い操作はバックグラウンドへ、エージェントは次を考え続ける* |
| [s09](./docs/ja/s09-agent-teams.md) | エージェントチーム | *一人で終わらないなら、チームメイトに任せる* |
| [s10](./docs/ja/s10-team-protocols.md) | チームプロトコル | *チームメイト間には統一の通信ルールが必要* |
| [s11](./docs/ja/s11-autonomous-agents.md) | 自律エージェント | *チームメイトが自らボードを見て、仕事を取る* |
| [s12](./docs/ja/s12-worktree-task-isolation.md) | Worktree + タスク分離 | *各自のディレクトリで作業し、互いに干渉しない* |

## 次のステップ -- 理解から出荷へ

12 セッションを終えれば、Harness 工学の内部構造を完全に理解している。その知識を活かす 2 つの方法:

### Kode Agent CLI -- オープンソース Coding Agent CLI

> `npm i -g @shareai-lab/kode`

Skill & LSP 対応、Windows 対応、GLM / MiniMax / DeepSeek 等のオープンモデルに接続可能。インストールしてすぐ使える。

GitHub: **[shareAI-lab/Kode-cli](https://github.com/shareAI-lab/Kode-cli)**

### Kode Agent SDK -- アプリにエージェント機能を埋め込む

公式 Claude Code Agent SDK は内部で完全な CLI プロセスと通信する -- 同時ユーザーごとに独立のターミナルプロセスが必要。Kode SDK は独立ライブラリでユーザーごとのプロセスオーバーヘッドがなく、バックエンド、ブラウザ拡張、組み込みデバイス等に埋め込み可能。

GitHub: **[shareAI-lab/Kode-agent-sdk](https://github.com/shareAI-lab/Kode-agent-sdk)**

---

## 姉妹教材: *オンデマンドセッション*から*常時稼働アシスタント*へ

本リポジトリが教える Harness は **使い捨て型** -- ターミナルを開き、Agent にタスクを与え、終わったら閉じる。次のセッションは白紙から始まる。Claude Code のモデル。

[OpenClaw](https://github.com/openclaw/openclaw) は別の可能性を証明した: 同じ agent core の上に 2 つの Harness メカニズムを追加するだけで、Agent は「突かないと動かない」から「30 秒ごとに自分で起きて仕事を探す」に変わる:

- **ハートビート** -- 30 秒ごとに Harness が Agent にメッセージを送り、やることがあるか確認させる。なければスリープ続行、あれば即座に行動。
- **Cron** -- Agent が自ら未来のタスクをスケジュールし、時間が来たら自動実行。

さらにマルチチャネル IM ルーティング (WhatsApp / Telegram / Slack / Discord 等 13+ プラットフォーム)、永続コンテキストメモリ、Soul パーソナリティシステムを加えると、Agent は使い捨てツールから常時稼働のパーソナル AI アシスタントへ変貌する。

**[claw0](https://github.com/shareAI-lab/claw0)** はこれらの Harness メカニズムをゼロから分解する姉妹教材リポジトリ:

```
claw agent = agent core + heartbeat + cron + IM chat + memory + soul
```

```
learn-claude-code                   claw0
(agent harness コア:                 (能動的な常時稼働 harness:
 ループ、ツール、計画、                ハートビート、cron、IM チャネル、
 チーム、worktree 分離)                メモリ、Soul パーソナリティ)
```

## ライセンス

MIT

---

**モデルが Agent だ。コードは Harness だ。優れた Harness を作れ。Agent が残りをやる。**

**Bash is all you need. Real agents are all the universe needs.**


================================================
FILE: README-zh.md
================================================
# Learn Claude Code -- 真正的 Agent Harness 工程

[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)

## 模型就是 Agent

在讨论代码之前,先把一件事彻底说清楚。

**Agent 是模型。不是框架。不是提示词链。不是拖拽式工作流。**

### Agent 到底是什么

Agent 是一个神经网络 -- Transformer、RNN、一个被训练出来的函数 -- 经过数十亿次梯度更新,在行动序列数据上学会了感知环境、推理目标、采取行动。"Agent" 这个词在 AI 领域从诞生之日起就是这个意思。从来都是。

人类就是 agent。一个由数百万年进化训练出来的生物神经网络,通过感官感知世界,通过大脑推理,通过身体行动。当 DeepMind、OpenAI 或 Anthropic 说 "agent" 时,他们说的和这个领域自诞生以来就一直在说的完全一样:**一个学会了行动的模型。**

历史已经写好了铁证:

- **2013 -- DeepMind DQN 玩 Atari。** 一个神经网络,只接收原始像素和游戏分数,学会了 7 款 Atari 2600 游戏 -- 超越所有先前算法,在其中 3 款上击败人类专家。到 2015 年,同一架构扩展到 [49 款游戏,达到职业人类测试员水平](https://www.nature.com/articles/nature14236),论文发表在 *Nature*。没有游戏专属规则。没有决策树。一个模型,从经验中学习。那个模型就是 agent。

- **2019 -- OpenAI Five 征服 Dota 2。** 五个神经网络,在 10 个月内与自己对战了 [45,000 年的 Dota 2](https://openai.com/index/openai-five-defeats-dota-2-world-champions/),在旧金山直播赛上 2-0 击败了 **OG** -- TI8 世界冠军。随后的公开竞技场中,AI 在 42,729 场比赛中胜率 99.4%。没有脚本化的策略。没有元编程的团队协调逻辑。模型完全通过自我对弈学会了团队协作、战术和实时适应。

- **2019 -- DeepMind AlphaStar 制霸星际争霸 II。** AlphaStar 在闭门赛中 [10-1 击败职业选手](https://deepmind.google/blog/alphastar-mastering-the-real-time-strategy-game-starcraft-ii/),随后在欧洲服务器上达到[宗师段位](https://www.nature.com/articles/d41586-019-03298-6) -- 90,000 名玩家中的前 0.15%。一个信息不完全、实时决策、组合动作空间远超国际象棋和围棋的游戏。Agent 是什么?是模型。训练出来的。不是编出来的。

- **2019 -- 腾讯绝悟统治王者荣耀。** 腾讯 AI Lab 的 "绝悟" 于 2019 年 8 月 2 日世冠杯半决赛上[以 5v5 击败 KPL 职业选手](https://www.jiemian.com/article/3371171.html)。在 1v1 模式下,职业选手 [15 场只赢 1 场,最多坚持不到 8 分钟](https://developer.aliyun.com/article/851058)。训练强度:一天等于人类 440 年。到 2021 年,绝悟在全英雄池 BO5 上全面超越 KPL 职业选手水准。没有手工编写的英雄克制表。没有脚本化的阵容编排。一个从零开始通过自我对弈学习整个游戏的模型。

- **2024-2025 -- LLM Agent 重塑软件工程。** Claude、GPT、Gemini -- 在人类全部代码和推理上训练的大语言模型 -- 被部署为编程 agent。它们阅读代码库,编写实现,调试故障,团队协作。架构与之前每一个 agent 完全相同:一个训练好的模型,放入一个环境,给予感知和行动的工具。唯一的不同是它们学到的东西的规模和解决任务的通用性。

每一个里程碑都共享同一个真理:**"Agent" 从来都不是外面那层代码。Agent 永远是模型本身。**

### Agent 不是什么

"Agent" 这个词已经被一整个提示词水管工产业劫持了。

拖拽式工作流构建器。无代码 "AI Agent" 平台。提示词链编排库。它们共享同一个幻觉:把 LLM API 调用用 if-else 分支、节点图、硬编码路由逻辑串在一起就算是 "构建 Agent" 了。

不是的。它们做出来的东西是鲁布·戈德堡机械 -- 一个过度工程化的、脆弱的过程式规则流水线,LLM 被楔在里面当一个美化了的文本补全节点。那不是 Agent。那是一个有着宏大妄想的 shell 脚本。

**提示词水管工式 "Agent" 是不做模型的程序员的意淫。** 他们试图通过堆叠过程式逻辑来暴力模拟智能 -- 庞大的规则树、节点图、链式提示词瀑布流 -- 然后祈祷足够多的胶水代码能涌现出自主行为。不会的。你不可能通过工程手段编码出 agency。Agency 是学出来的,不是编出来的。

那些系统从诞生之日起就已经死了:脆弱、不可扩展、根本不具备泛化能力。它们是 GOFAI(Good Old-Fashioned AI,经典符号 AI)的现代还魂 -- 几十年前就被学界抛弃的符号规则系统,现在喷了一层 LLM 的漆又登场了。换了个包装,同一条死路。

### 心智转换:从 "开发 Agent" 到开发 Harness

当一个人说 "我在开发 Agent" 时,他只可能是两个意思之一:

**1. 训练模型。** 通过强化学习、微调、RLHF 或其他基于梯度的方法调整权重。收集任务过程数据 -- 真实领域中感知、推理、行动的实际序列 -- 用它们来塑造模型的行为。这是 DeepMind、OpenAI、腾讯 AI Lab、Anthropic 在做的事。这是最本义的 Agent 开发。

**2. 构建 Harness。** 编写代码,为模型提供一个可操作的环境。这是我们大多数人在做的事,也是本仓库的核心。

Harness 是 agent 在特定领域工作所需要的一切:

```
Harness = Tools + Knowledge + Observation + Action Interfaces + Permissions

    Tools:          文件读写、Shell、网络、数据库、浏览器
    Knowledge:      产品文档、领域资料、API 规范、风格指南
    Observation:    git diff、错误日志、浏览器状态、传感器数据
    Action:         CLI 命令、API 调用、UI 交互
    Permissions:    沙箱隔离、审批流程、信任边界
```

模型做决策。Harness 执行。模型做推理。Harness 提供上下文。模型是驾驶者。Harness 是载具。

**编程 agent 的 harness 是它的 IDE、终端和文件系统。** 农业 agent 的 harness 是传感器阵列、灌溉控制和气象数据。酒店 agent 的 harness 是预订系统、客户沟通渠道和设施管理 API。Agent -- 那个智能、那个决策者 -- 永远是模型。Harness 因领域而变。Agent 跨领域泛化。

这个仓库教你造载具。编程用的载具。但设计模式可以泛化到任何领域:庄园管理、农田运营、酒店运作、工厂制造、物流调度、医疗保健、教育培训、科学研究。只要有一个任务需要被感知、推理和执行 -- agent 就需要一个 harness。

### Harness 工程师到底在做什么

如果你在读这个仓库,你很可能是一名 harness 工程师 -- 这是一个强大的身份。以下是你真正的工作:

- **实现工具。** 给 agent 一双手。文件读写、Shell 执行、API 调用、浏览器控制、数据库查询。每个工具都是 agent 在环境中可以采取的一个行动。设计它们时要原子化、可组合、描述清晰。

- **策划知识。** 给 agent 领域专长。产品文档、架构决策记录、风格指南、合规要求。按需加载(s05),不要前置塞入。Agent 应该知道有什么可用,然后自己拉取所需。

- **管理上下文。** 给 agent 干净的记忆。子 agent 隔离(s04)防止噪声泄露。上下文压缩(s06)防止历史淹没。任务系统(s07)让目标持久化到单次对话之外。

- **控制权限。** 给 agent 边界。沙箱化文件访问。对破坏性操作要求审批。在 agent 和外部系统之间实施信任边界。这是安全工程与 harness 工程的交汇点。

- **收集任务过程数据。** Agent 在你的 harness 中执行的每一条行动序列都是训练信号。真实部署中的感知-推理-行动轨迹是微调下一代 agent 模型的原材料。你的 harness 不仅服务于 agent -- 它还可以帮助进化 agent。

你不是在编写智能。你是在构建智能栖居的世界。这个世界的质量 -- agent 能看得多清楚、行动得多精准、可用知识有多丰富 -- 直接决定了智能能多有效地表达自己。

**造好 Harness。Agent 会完成剩下的。**

### 为什么是 Claude Code -- Harness 工程的大师课

为什么这个仓库专门拆解 Claude Code?

因为 Claude Code 是我们所见过的最优雅、最完整的 agent harness 实现。不是因为某个巧妙的技巧,而是因为它 *没做* 的事:它没有试图成为 agent 本身。它没有强加僵化的工作流。它没有用精心设计的决策树去替模型做判断。它给模型提供了工具、知识、上下文管理和权限边界 -- 然后让开了。

把 Claude Code 剥到本质来看:

```
Claude Code = 一个 agent loop
            + 工具 (bash, read, write, edit, glob, grep, browser...)
            + 按需 skill 加载
            + 上下文压缩
            + 子 agent 派生
            + 带依赖图的任务系统
            + 异步邮箱的团队协调
            + worktree 隔离的并行执行
            + 权限治理
```

就这些。这就是全部架构。每一个组件都是 harness 机制 -- 为 agent 构建的栖居世界的一部分。Agent 本身呢?是 Claude。一个模型。由 Anthropic 在人类推理和代码的全部广度上训练而成。Harness 没有让 Claude 变聪明。Claude 本来就聪明。Harness 给了 Claude 双手、双眼和一个工作空间。

这就是 Claude Code 作为教学标本的意义:**它展示了当你信任模型、把工程精力集中在 harness 上时会发生什么。** 本仓库的每一个课程(s01-s12)都在逆向工程 Claude Code 架构中的一个 harness 机制。学完之后,你理解的不只是 Claude Code 怎么工作,而是适用于任何领域、任何 agent 的 harness 工程通用原则。

启示不是 "复制 Claude Code"。启示是:**最好的 agent 产品,出自那些明白自己的工作是 harness 而非 intelligence 的工程师之手。**

---

## 愿景:用真正的 Agent 铺满宇宙

这不只关乎编程 agent。

每一个人类从事复杂、多步骤、需要判断力的工作的领域,都是 agent 可以运作的领域 -- 只要有对的 harness。本仓库中的模式是通用的:

```
庄园管理 agent  = 模型 + 物业传感器 + 维护工具 + 租户通信
农业 agent      = 模型 + 土壤/气象数据 + 灌溉控制 + 作物知识
酒店运营 agent  = 模型 + 预订系统 + 客户渠道 + 设施 API
医学研究 agent  = 模型 + 文献检索 + 实验仪器 + 协议文档
制造业 agent    = 模型 + 产线传感器 + 质量控制 + 物流系统
教育 agent      = 模型 + 课程知识 + 学生进度 + 评估工具
```

循环永远不变。工具在变。知识在变。权限在变。Agent -- 那个模型 -- 泛化一切。

每一个读这个仓库的 harness 工程师都在学习远超软件工程的模式。你在学习为一个智能的、自动化的未来构建基础设施。每一个部署在真实领域的好 harness,都是 agent 能够感知、推理、行动的又一个阵地。

先铺满工作室。然后是农田、医院、工厂。然后是城市。然后是星球。

**Bash is all you need. Real agents are all the universe needs.**

---

```
                    THE AGENT PATTERN
                    =================

    User --> messages[] --> LLM --> response
                                      |
                            stop_reason == "tool_use"?
                           /                          \
                         yes                           no
                          |                             |
                    execute tools                    return text
                    append results
                    loop back -----------------> messages[]


    这是最小循环。每个 AI Agent 都需要这个循环。
    模型决定何时调用工具、何时停止。
    代码只是执行模型的要求。
    本仓库教你构建围绕这个循环的一切 --
    让 agent 在特定领域高效工作的 harness。
```

**12 个递进式课程, 从简单循环到隔离化的自治执行。**
**每个课程添加一个 harness 机制。每个机制有一句格言。**

> **s01**   *"One loop & Bash is all you need"* — 一个工具 + 一个循环 = 一个智能体
>
> **s02**   *"加一个工具, 只加一个 handler"* — 循环不用动, 新工具注册进 dispatch map 就行
>
> **s03**   *"没有计划的 agent 走哪算哪"* — 先列步骤再动手, 完成率翻倍
>
> **s04**   *"大任务拆小, 每个小任务干净的上下文"* — 子智能体用独立 messages[], 不污染主对话
>
> **s05**   *"用到什么知识, 临时加载什么知识"* — 通过 tool_result 注入, 不塞 system prompt
>
> **s06**   *"上下文总会满, 要有办法腾地方"* — 三层压缩策略, 换来无限会话
>
> **s07**   *"大目标要拆成小任务, 排好序, 记在磁盘上"* — 文件持久化的任务图, 为多 agent 协作打基础
>
> **s08**   *"慢操作丢后台, agent 继续想下一步"* — 后台线程跑命令, 完成后注入通知
>
> **s09**   *"任务太大一个人干不完, 要能分给队友"* — 持久化队友 + 异步邮箱
>
> **s10**   *"队友之间要有统一的沟通规矩"* — 一个 request-response 模式驱动所有协商
>
> **s11**   *"队友自己看看板, 有活就认领"* — 不需要领导逐个分配, 自组织
>
> **s12**   *"各干各的目录, 互不干扰"* — 任务管目标, worktree 管目录, 按 ID 绑定

---

## 核心模式

```python
def agent_loop(messages):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM,
            messages=messages, tools=TOOLS,
        )
        messages.append({"role": "assistant",
                         "content": response.content})

        if response.stop_reason != "tool_use":
            return

        results = []
        for block in response.content:
            if block.type == "tool_use":
                output = TOOL_HANDLERS[block.name](**block.input)
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output,
                })
        messages.append({"role": "user", "content": results})
```

每个课程在这个循环之上叠加一个 harness 机制 -- 循环本身始终不变。循环属于 agent。机制属于 harness。

## 范围说明 (重要)

本仓库是一个 0->1 的 harness 工程学习项目 -- 构建围绕 agent 模型的工作环境。
为保证学习路径清晰,仓库有意简化或省略了部分生产机制:

- 完整事件 / Hook 总线 (例如 PreToolUse、SessionStart/End、ConfigChange)。
  s12 仅提供教学用途的最小 append-only 生命周期事件流。
- 基于规则的权限治理与信任流程
- 会话生命周期控制 (resume/fork) 与更完整的 worktree 生命周期控制
- 完整 MCP 运行时细节 (transport/OAuth/资源订阅/轮询)

仓库中的团队 JSONL 邮箱协议是教学实现,不是对任何特定生产内部实现的声明。

## 快速开始

```sh
git clone https://github.com/shareAI-lab/learn-claude-code
cd learn-claude-code
pip install -r requirements.txt
cp .env.example .env   # 编辑 .env 填入你的 ANTHROPIC_API_KEY

python agents/s01_agent_loop.py       # 从这里开始
python agents/s12_worktree_task_isolation.py  # 完整递进终点
python agents/s_full.py               # 总纲: 全部机制合一
```

### Web 平台

交互式可视化、分步动画、源码查看器, 以及每个课程的文档。

```sh
cd web && npm install && npm run dev   # http://localhost:3000
```

## 学习路径

```
第一阶段: 循环                       第二阶段: 规划与知识
==================                   ==============================
s01  Agent 循环              [1]     s03  TodoWrite               [5]
     while + stop_reason                  TodoManager + nag 提醒
     |                                    |
     +-> s02  Tool Use            [4]     s04  子智能体             [5]
              dispatch map: name->handler     每个子智能体独立 messages[]
                                              |
                                         s05  Skills               [5]
                                              SKILL.md 通过 tool_result 注入
                                              |
                                         s06  Context Compact      [5]
                                              三层上下文压缩

第三阶段: 持久化                     第四阶段: 团队
==================                   =====================
s07  任务系统                [8]     s09  智能体团队             [9]
     文件持久化 CRUD + 依赖图             队友 + JSONL 邮箱
     |                                    |
s08  后台任务                [6]     s10  团队协议               [12]
     守护线程 + 通知队列                  关机 + 计划审批 FSM
                                          |
                                     s11  自治智能体             [14]
                                          空闲轮询 + 自动认领
                                     |
                                     s12  Worktree 隔离          [16]
                                          任务协调 + 按需隔离执行通道

                                     [N] = 工具数量
```

## 项目结构

```
learn-claude-code/
|
|-- agents/                        # Python 参考实现 (s01-s12 + s_full 总纲)
|-- docs/{en,zh,ja}/               # 心智模型优先的文档 (3 种语言)
|-- web/                           # 交互式学习平台 (Next.js)
|-- skills/                        # s05 的 Skill 文件
+-- .github/workflows/ci.yml      # CI: 类型检查 + 构建
```

## 文档

心智模型优先: 问题、方案、ASCII 图、最小化代码。
[English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/)

| 课程 | 主题 | 格言 |
|------|------|------|
| [s01](./docs/zh/s01-the-agent-loop.md) | Agent 循环 | *One loop & Bash is all you need* |
| [s02](./docs/zh/s02-tool-use.md) | Tool Use | *加一个工具, 只加一个 handler* |
| [s03](./docs/zh/s03-todo-write.md) | TodoWrite | *没有计划的 agent 走哪算哪* |
| [s04](./docs/zh/s04-subagent.md) | 子智能体 | *大任务拆小, 每个小任务干净的上下文* |
| [s05](./docs/zh/s05-skill-loading.md) | Skills | *用到什么知识, 临时加载什么知识* |
| [s06](./docs/zh/s06-context-compact.md) | Context Compact | *上下文总会满, 要有办法腾地方* |
| [s07](./docs/zh/s07-task-system.md) | 任务系统 | *大目标要拆成小任务, 排好序, 记在磁盘上* |
| [s08](./docs/zh/s08-background-tasks.md) | 后台任务 | *慢操作丢后台, agent 继续想下一步* |
| [s09](./docs/zh/s09-agent-teams.md) | 智能体团队 | *任务太大一个人干不完, 要能分给队友* |
| [s10](./docs/zh/s10-team-protocols.md) | 团队协议 | *队友之间要有统一的沟通规矩* |
| [s11](./docs/zh/s11-autonomous-agents.md) | 自治智能体 | *队友自己看看板, 有活就认领* |
| [s12](./docs/zh/s12-worktree-task-isolation.md) | Worktree + 任务隔离 | *各干各的目录, 互不干扰* |

## 学完之后 -- 从理解到落地

12 个课程走完, 你已经从内到外理解了 harness 工程的运作原理。两种方式把知识变成产品:

### Kode Agent CLI -- 开源 Coding Agent CLI

> `npm i -g @shareai-lab/kode`

支持 Skill & LSP, 适配 Windows, 可接 GLM / MiniMax / DeepSeek 等开放模型。装完即用。

GitHub: **[shareAI-lab/Kode-cli](https://github.com/shareAI-lab/Kode-cli)**

### Kode Agent SDK -- 把 Agent 能力嵌入你的应用

官方 Claude Code Agent SDK 底层与完整 CLI 进程通信 -- 每个并发用户 = 一个终端进程。Kode SDK 是独立库, 无 per-user 进程开销, 可嵌入后端、浏览器插件、嵌入式设备等任意运行时。

GitHub: **[shareAI-lab/Kode-agent-sdk](https://github.com/shareAI-lab/Kode-agent-sdk)**

---

## 姊妹教程: 从*被动临时会话*到*主动常驻助手*

本仓库教的 harness 属于 **用完即走** 型 -- 开终端、给 agent 任务、做完关掉, 下次重开是全新会话。Claude Code 就是这种模式。

但 [OpenClaw](https://github.com/openclaw/openclaw) 证明了另一种可能: 在同样的 agent core 之上, 加两个 harness 机制就能让 agent 从 "踹一下动一下" 变成 "自己隔 30 秒醒一次找活干":

- **心跳 (Heartbeat)** -- 每 30 秒 harness 给 agent 发一条消息, 让它检查有没有事可做。没事就继续睡, 有事立刻行动。
- **定时任务 (Cron)** -- agent 可以给自己安排未来要做的事, 到点自动执行。

再加上 IM 多通道路由 (WhatsApp/Telegram/Slack/Discord 等 13+ 平台)、不清空的上下文记忆、Soul 人格系统, agent 就从一个临时工具变成了始终在线的个人 AI 助手。

**[claw0](https://github.com/shareAI-lab/claw0)** 是我们的姊妹教学仓库, 从零拆解这些 harness 机制:

```
claw agent = agent core + heartbeat + cron + IM chat + memory + soul
```

```
learn-claude-code                   claw0
(agent harness 内核:                 (主动式常驻 harness:
 循环、工具、规划、                    心跳、定时任务、IM 通道、
 团队、worktree 隔离)                  记忆、Soul 人格)
```

## 许可证

MIT

---

**模型就是 Agent。代码是 Harness。造好 Harness,Agent 会完成剩下的。**

**Bash is all you need. Real agents are all the universe needs.**


================================================
FILE: README.md
================================================
[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)
# Learn Claude Code -- Harness Engineering for Real Agents

## The Model IS the Agent

Before we talk about code, let's get one thing absolutely straight.

**An agent is a model. Not a framework. Not a prompt chain. Not a drag-and-drop workflow.**

### What an Agent IS

An agent is a neural network -- a Transformer, an RNN, a learned function -- that has been trained, through billions of gradient updates on action-sequence data, to perceive an environment, reason about goals, and take actions to achieve them. The word "agent" in AI has always meant this. Always.

A human is an agent. A biological neural network, shaped by millions of years of evolutionary training, perceiving the world through senses, reasoning through a brain, acting through a body. When DeepMind, OpenAI, or Anthropic say "agent," they mean the same thing the field has meant since its inception: **a model that has learned to act.**

The proof is written in history:

- **2013 -- DeepMind DQN plays Atari.** A single neural network, receiving only raw pixels and game scores, learned to play 7 Atari 2600 games -- surpassing all prior algorithms and beating human experts on 3 of them. By 2015, the same architecture scaled to [49 games and matched professional human testers](https://www.nature.com/articles/nature14236), published in *Nature*. No game-specific rules. No decision trees. One model, learning from experience. That model was the agent.

- **2019 -- OpenAI Five conquers Dota 2.** Five neural networks, having played [45,000 years of Dota 2](https://openai.com/index/openai-five-defeats-dota-2-world-champions/) against themselves in 10 months, defeated **OG** -- the reigning TI8 world champions -- 2-0 on a San Francisco livestream. In a subsequent public arena, the AI won 99.4% of 42,729 games against all comers. No scripted strategies. No meta-programmed team coordination. The models learned teamwork, tactics, and real-time adaptation entirely through self-play.

- **2019 -- DeepMind AlphaStar masters StarCraft II.** AlphaStar [beat professional players 10-1](https://deepmind.google/blog/alphastar-mastering-the-real-time-strategy-game-starcraft-ii/) in a closed-door match, and later achieved [Grandmaster status](https://www.nature.com/articles/d41586-019-03298-6) on European servers -- top 0.15% of 90,000 players. A game with imperfect information, real-time decisions, and a combinatorial action space that dwarfs chess and Go. The agent? A model. Trained. Not scripted.

- **2019 -- Tencent Jueyu dominates Honor of Kings.** Tencent AI Lab's "Jueyu" [defeated KPL professional players](https://www.jiemian.com/article/3371171.html) in a full 5v5 match at the World Champion Cup. In 1v1 mode, pros won only [1 out of 15 games and never survived past 8 minutes](https://developer.aliyun.com/article/851058). Training intensity: one day equaled 440 human years. By 2021, Jueyu surpassed KPL pros across the full hero pool. No handcrafted matchup tables. No scripted compositions. A model that learned the entire game from scratch through self-play.

- **2024-2025 -- LLM agents reshape software engineering.** Claude, GPT, Gemini -- large language models trained on the entirety of human code and reasoning -- are deployed as coding agents. They read codebases, write implementations, debug failures, coordinate in teams. The architecture is identical to every agent before them: a trained model, placed in an environment, given tools to perceive and act. The only difference is the scale of what they've learned and the generality of the tasks they solve.

Every one of these milestones shares the same truth: **the "agent" is never the surrounding code. The agent is always the model.**

### What an Agent Is NOT

The word "agent" has been hijacked by an entire cottage industry of prompt plumbing.

Drag-and-drop workflow builders. No-code "AI agent" platforms. Prompt-chain orchestration libraries. They all share the same delusion: that wiring together LLM API calls with if-else branches, node graphs, and hardcoded routing logic constitutes "building an agent."

It doesn't. What they build is a Rube Goldberg machine -- an over-engineered, brittle pipeline of procedural rules, with an LLM wedged in as a glorified text-completion node. That is not an agent. That is a shell script with delusions of grandeur.

**Prompt plumbing "agents" are the fantasy of programmers who don't train models.** They attempt to brute-force intelligence by stacking procedural logic -- massive rule trees, node graphs, chain-of-prompt waterfalls -- and praying that enough glue code will somehow emergently produce autonomous behavior. It won't. You cannot engineer your way to agency. Agency is learned, not programmed.

Those systems are dead on arrival: fragile, unscalable, fundamentally incapable of generalization. They are the modern resurrection of GOFAI (Good Old-Fashioned AI) -- the symbolic rule systems the field abandoned decades ago, now spray-painted with an LLM veneer. Different packaging, same dead end.

### The Mind Shift: From "Developing Agents" to Developing Harness

When someone says "I'm developing an agent," they can only mean one of two things:

**1. Training the model.** Adjusting weights through reinforcement learning, fine-tuning, RLHF, or other gradient-based methods. Collecting task-process data -- the actual sequences of perception, reasoning, and action in real domains -- and using it to shape the model's behavior. This is what DeepMind, OpenAI, Tencent AI Lab, and Anthropic do. This is agent development in the truest sense.

**2. Building the harness.** Writing the code that gives the model an environment to operate in. This is what most of us do, and it is the focus of this repository.

A harness is everything the agent needs to function in a specific domain:

```
Harness = Tools + Knowledge + Observation + Action Interfaces + Permissions

    Tools:          file I/O, shell, network, database, browser
    Knowledge:      product docs, domain references, API specs, style guides
    Observation:    git diff, error logs, browser state, sensor data
    Action:         CLI commands, API calls, UI interactions
    Permissions:    sandboxing, approval workflows, trust boundaries
```

The model decides. The harness executes. The model reasons. The harness provides context. The model is the driver. The harness is the vehicle.

**A coding agent's harness is its IDE, terminal, and filesystem access.** A farm agent's harness is its sensor array, irrigation controls, and weather data feeds. A hotel agent's harness is its booking system, guest communication channels, and facility management APIs. The agent -- the intelligence, the decision-maker -- is always the model. The harness changes per domain. The agent generalizes across them.

This repo teaches you to build vehicles. Vehicles for coding. But the design patterns generalize to any domain: farm management, hotel operations, manufacturing, logistics, healthcare, education, scientific research. Anywhere a task needs to be perceived, reasoned about, and acted upon -- an agent needs a harness.

### What Harness Engineers Actually Do

If you are reading this repository, you are likely a harness engineer -- and that is a powerful thing to be. Here is your real job:

- **Implement tools.** Give the agent hands. File read/write, shell execution, API calls, browser control, database queries. Each tool is an action the agent can take in its environment. Design them to be atomic, composable, and well-described.

- **Curate knowledge.** Give the agent domain expertise. Product documentation, architectural decision records, style guides, regulatory requirements. Load them on-demand (s05), not upfront. The agent should know what's available and pull what it needs.

- **Manage context.** Give the agent clean memory. Subagent isolation (s04) prevents noise from leaking. Context compression (s06) prevents history from overwhelming. Task systems (s07) persist goals beyond any single conversation.

- **Control permissions.** Give the agent boundaries. Sandbox file access. Require approval for destructive operations. Enforce trust boundaries between the agent and external systems. This is where safety engineering meets harness engineering.

- **Collect task-process data.** Every action sequence the agent executes in your harness is training signal. The perception-reasoning-action traces from real deployments are the raw material for fine-tuning the next generation of agent models. Your harness doesn't just serve the agent -- it can help improve the agent.

You are not writing the intelligence. You are building the world the intelligence inhabits. The quality of that world -- how clearly the agent can perceive, how precisely it can act, how rich its available knowledge is -- directly determines how effectively the intelligence can express itself.

**Build great harnesses. The agent will do the rest.**

### Why Claude Code -- A Masterclass in Harness Engineering

Why does this repository dissect Claude Code specifically?

Because Claude Code is the most elegant and fully-realized agent harness we have seen. Not because of any single clever trick, but because of what it *doesn't* do: it doesn't try to be the agent. It doesn't impose rigid workflows. It doesn't second-guess the model with elaborate decision trees. It provides the model with tools, knowledge, context management, and permission boundaries -- then gets out of the way.

Look at what Claude Code actually is, stripped to its essence:

```
Claude Code = one agent loop
            + tools (bash, read, write, edit, glob, grep, browser...)
            + on-demand skill loading
            + context compression
            + subagent spawning
            + task system with dependency graph
            + team coordination with async mailboxes
            + worktree isolation for parallel execution
            + permission governance
```

That's it. That's the entire architecture. Every component is a harness mechanism -- a piece of the world built for the agent to inhabit. The agent itself? It's Claude. A model. Trained by Anthropic on the full breadth of human reasoning and code. The harness doesn't make Claude smart. Claude is already smart. The harness gives Claude hands, eyes, and a workspace.

This is why Claude Code is the ideal teaching subject: **it demonstrates what happens when you trust the model and focus your engineering on the harness.** Every session in this repository (s01-s12) reverse-engineers one harness mechanism from Claude Code's architecture. By the end, you understand not just how Claude Code works, but the universal principles of harness engineering that apply to any agent in any domain.

The lesson is not "copy Claude Code." The lesson is: **the best agent products are built by engineers who understand that their job is harness, not intelligence.**

---

## The Vision: Fill the Universe with Real Agents

This is not just about coding agents.

Every domain where humans perform complex, multi-step, judgment-intensive work is a domain where agents can operate -- given the right harness. The patterns in this repository are universal:

```
Estate management agent    = model + property sensors + maintenance tools + tenant comms
Agricultural agent         = model + soil/weather data + irrigation controls + crop knowledge
Hotel operations agent     = model + booking system + guest channels + facility APIs
Medical research agent     = model + literature search + lab instruments + protocol docs
Manufacturing agent        = model + production line sensors + quality controls + logistics
Education agent            = model + curriculum knowledge + student progress + assessment tools
```

The loop is always the same. The tools change. The knowledge changes. The permissions change. The agent -- the model -- generalizes.

Every harness engineer reading this repository is learning patterns that apply far beyond software engineering. You are learning to build the infrastructure for an intelligent, automated future. Every well-designed harness deployed in a real domain is one more place where an agent can perceive, reason, and act.

First we fill the workshops. Then the farms, the hospitals, the factories. Then the cities. Then the planet.

**Bash is all you need. Real agents are all the universe needs.**

---

```
                    THE AGENT PATTERN
                    =================

    User --> messages[] --> LLM --> response
                                      |
                            stop_reason == "tool_use"?
                           /                          \
                         yes                           no
                          |                             |
                    execute tools                    return text
                    append results
                    loop back -----------------> messages[]


    That's the minimal loop. Every AI agent needs this loop.
    The MODEL decides when to call tools and when to stop.
    The CODE just executes what the model asks for.
    This repo teaches you to build what surrounds this loop --
    the harness that makes the agent effective in a specific domain.
```

**12 progressive sessions, from a simple loop to isolated autonomous execution.**
**Each session adds one harness mechanism. Each mechanism has one motto.**

> **s01**   *"One loop & Bash is all you need"* — one tool + one loop = an agent
>
> **s02**   *"Adding a tool means adding one handler"* — the loop stays the same; new tools register into the dispatch map
>
> **s03**   *"An agent without a plan drifts"* — list the steps first, then execute; completion doubles
>
> **s04**   *"Break big tasks down; each subtask gets a clean context"* — subagents use independent messages[], keeping the main conversation clean
>
> **s05**   *"Load knowledge when you need it, not upfront"* — inject via tool_result, not the system prompt
>
> **s06**   *"Context will fill up; you need a way to make room"* — three-layer compression strategy for infinite sessions
>
> **s07**   *"Break big goals into small tasks, order them, persist to disk"* — a file-based task graph with dependencies, laying the foundation for multi-agent collaboration
>
> **s08**   *"Run slow operations in the background; the agent keeps thinking"* — daemon threads run commands, inject notifications on completion
>
> **s09**   *"When the task is too big for one, delegate to teammates"* — persistent teammates + async mailboxes
>
> **s10**   *"Teammates need shared communication rules"* — one request-response pattern drives all negotiation
>
> **s11**   *"Teammates scan the board and claim tasks themselves"* — no need for the lead to assign each one
>
> **s12**   *"Each works in its own directory, no interference"* — tasks manage goals, worktrees manage directories, bound by ID

---

## The Core Pattern

```python
def agent_loop(messages):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM,
            messages=messages, tools=TOOLS,
        )
        messages.append({"role": "assistant",
                         "content": response.content})

        if response.stop_reason != "tool_use":
            return

        results = []
        for block in response.content:
            if block.type == "tool_use":
                output = TOOL_HANDLERS[block.name](**block.input)
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output,
                })
        messages.append({"role": "user", "content": results})
```

Every session layers one harness mechanism on top of this loop -- without changing the loop itself. The loop belongs to the agent. The mechanisms belong to the harness.

## Scope (Important)

This repository is a 0->1 learning project for harness engineering -- building the environment that surrounds an agent model.
It intentionally simplifies or omits several production mechanisms:

- Full event/hook buses (for example PreToolUse, SessionStart/End, ConfigChange).
  s12 includes only a minimal append-only lifecycle event stream for teaching.
- Rule-based permission governance and trust workflows
- Session lifecycle controls (resume/fork) and advanced worktree lifecycle controls
- Full MCP runtime details (transport/OAuth/resource subscribe/polling)

Treat the team JSONL mailbox protocol in this repo as a teaching implementation, not a claim about any specific production internals.

## Quick Start

```sh
git clone https://github.com/shareAI-lab/learn-claude-code
cd learn-claude-code
pip install -r requirements.txt
cp .env.example .env   # Edit .env with your ANTHROPIC_API_KEY

python agents/s01_agent_loop.py       # Start here
python agents/s12_worktree_task_isolation.py  # Full progression endpoint
python agents/s_full.py               # Capstone: all mechanisms combined
```

### Web Platform

Interactive visualizations, step-through diagrams, source viewer, and documentation.

```sh
cd web && npm install && npm run dev   # http://localhost:3000
```

## Learning Path

```
Phase 1: THE LOOP                    Phase 2: PLANNING & KNOWLEDGE
==================                   ==============================
s01  The Agent Loop          [1]     s03  TodoWrite               [5]
     while + stop_reason                  TodoManager + nag reminder
     |                                    |
     +-> s02  Tool Use            [4]     s04  Subagents            [5]
              dispatch map: name->handler     fresh messages[] per child
                                              |
                                         s05  Skills               [5]
                                              SKILL.md via tool_result
                                              |
                                         s06  Context Compact      [5]
                                              3-layer compression

Phase 3: PERSISTENCE                 Phase 4: TEAMS
==================                   =====================
s07  Tasks                   [8]     s09  Agent Teams             [9]
     file-based CRUD + deps graph         teammates + JSONL mailboxes
     |                                    |
s08  Background Tasks        [6]     s10  Team Protocols          [12]
     daemon threads + notify queue        shutdown + plan approval FSM
                                          |
                                     s11  Autonomous Agents       [14]
                                          idle cycle + auto-claim
                                     |
                                     s12  Worktree Isolation      [16]
                                          task coordination + optional isolated execution lanes

                                     [N] = number of tools
```

## Architecture

```
learn-claude-code/
|
|-- agents/                        # Python reference implementations (s01-s12 + s_full capstone)
|-- docs/{en,zh,ja}/               # Mental-model-first documentation (3 languages)
|-- web/                           # Interactive learning platform (Next.js)
|-- skills/                        # Skill files for s05
+-- .github/workflows/ci.yml      # CI: typecheck + build
```

## Documentation

Mental-model-first: problem, solution, ASCII diagram, minimal code.
Available in [English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/).

| Session | Topic | Motto |
|---------|-------|-------|
| [s01](./docs/en/s01-the-agent-loop.md) | The Agent Loop | *One loop & Bash is all you need* |
| [s02](./docs/en/s02-tool-use.md) | Tool Use | *Adding a tool means adding one handler* |
| [s03](./docs/en/s03-todo-write.md) | TodoWrite | *An agent without a plan drifts* |
| [s04](./docs/en/s04-subagent.md) | Subagents | *Break big tasks down; each subtask gets a clean context* |
| [s05](./docs/en/s05-skill-loading.md) | Skills | *Load knowledge when you need it, not upfront* |
| [s06](./docs/en/s06-context-compact.md) | Context Compact | *Context will fill up; you need a way to make room* |
| [s07](./docs/en/s07-task-system.md) | Tasks | *Break big goals into small tasks, order them, persist to disk* |
| [s08](./docs/en/s08-background-tasks.md) | Background Tasks | *Run slow operations in the background; the agent keeps thinking* |
| [s09](./docs/en/s09-agent-teams.md) | Agent Teams | *When the task is too big for one, delegate to teammates* |
| [s10](./docs/en/s10-team-protocols.md) | Team Protocols | *Teammates need shared communication rules* |
| [s11](./docs/en/s11-autonomous-agents.md) | Autonomous Agents | *Teammates scan the board and claim tasks themselves* |
| [s12](./docs/en/s12-worktree-task-isolation.md) | Worktree + Task Isolation | *Each works in its own directory, no interference* |

## What's Next -- from understanding to shipping

After the 12 sessions you understand how harness engineering works inside out. Two ways to put that knowledge to work:

### Kode Agent CLI -- Open-Source Coding Agent CLI

> `npm i -g @shareai-lab/kode`

Skill & LSP support, Windows-ready, pluggable with GLM / MiniMax / DeepSeek and other open models. Install and go.

GitHub: **[shareAI-lab/Kode-cli](https://github.com/shareAI-lab/Kode-cli)**

### Kode Agent SDK -- Embed Agent Capabilities in Your App

The official Claude Code Agent SDK communicates with a full CLI process under the hood -- each concurrent user means a separate terminal process. Kode SDK is a standalone library with no per-user process overhead, embeddable in backends, browser extensions, embedded devices, or any runtime.

GitHub: **[shareAI-lab/Kode-agent-sdk](https://github.com/shareAI-lab/Kode-agent-sdk)**

---

## Sister Repo: from *on-demand sessions* to *always-on assistant*

The harness this repo teaches is **use-and-discard** -- open a terminal, give the agent a task, close when done, next session starts blank. That is the Claude Code model.

[OpenClaw](https://github.com/openclaw/openclaw) proved another possibility: on top of the same agent core, two harness mechanisms turn the agent from "poke it to make it move" into "it wakes up every 30 seconds to look for work":

- **Heartbeat** -- every 30s the harness sends the agent a message to check if there is anything to do. Nothing? Go back to sleep. Something? Act immediately.
- **Cron** -- the agent can schedule its own future tasks, executed automatically when the time comes.

Add multi-channel IM routing (WhatsApp / Telegram / Slack / Discord, 13+ platforms), persistent context memory, and a Soul personality system, and the agent goes from a disposable tool to an always-on personal AI assistant.

**[claw0](https://github.com/shareAI-lab/claw0)** is our companion teaching repo that deconstructs these harness mechanisms from scratch:

```
claw agent = agent core + heartbeat + cron + IM chat + memory + soul
```

```
learn-claude-code                   claw0
(agent harness core:                (proactive always-on harness:
 loop, tools, planning,              heartbeat, cron, IM channels,
 teams, worktree isolation)          memory, soul personality)
```

## About
<img width="260" src="https://github.com/user-attachments/assets/fe8b852b-97da-4061-a467-9694906b5edf" /><br>

Scan with Wechat to follow us,
or follow on X: [shareAI-Lab](https://x.com/baicai003)

## License

MIT

---

**The model is the agent. The code is the harness. Build great harnesses. The agent will do the rest.**

**Bash is all you need. Real agents are all the universe needs.**


================================================
FILE: agents/__init__.py
================================================
# agents/ - Harness implementations (s01-s12) + full reference (s_full)
# Each file is self-contained and runnable: python agents/s01_agent_loop.py
# The model is the agent. These files are the harness.


================================================
FILE: agents/s01_agent_loop.py
================================================
#!/usr/bin/env python3
# Harness: the loop -- the model's first connection to the real world.
"""
s01_agent_loop.py - The Agent Loop

The entire secret of an AI coding agent in one pattern:

    while stop_reason == "tool_use":
        response = LLM(messages, tools)
        execute tools
        append results

    +----------+      +-------+      +---------+
    |   User   | ---> |  LLM  | ---> |  Tool   |
    |  prompt  |      |       |      | execute |
    +----------+      +---+---+      +----+----+
                          ^               |
                          |   tool_result |
                          +---------------+
                          (loop continues)

This is the core loop: feed tool results back to the model
until the model decides to stop. Production agents layer
policy, hooks, and lifecycle controls on top.
"""

import os
import subprocess

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)

if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]

SYSTEM = f"You are a coding agent at {os.getcwd()}. Use bash to solve tasks. Act, don't explain."

TOOLS = [{
    "name": "bash",
    "description": "Run a shell command.",
    "input_schema": {
        "type": "object",
        "properties": {"command": {"type": "string"}},
        "required": ["command"],
    },
}]


def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(command, shell=True, cwd=os.getcwd(),
                           capture_output=True, text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"


# -- The core pattern: a while loop that calls tools until the model stops --
def agent_loop(messages: list):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        # Append assistant turn
        messages.append({"role": "assistant", "content": response.content})
        # If the model didn't call a tool, we're done
        if response.stop_reason != "tool_use":
            return
        # Execute each tool call, collect results
        results = []
        for block in response.content:
            if block.type == "tool_use":
                print(f"\033[33m$ {block.input['command']}\033[0m")
                output = run_bash(block.input["command"])
                print(output[:200])
                results.append({"type": "tool_result", "tool_use_id": block.id,
                                "content": output})
        messages.append({"role": "user", "content": results})


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms01 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s02_tool_use.py
================================================
#!/usr/bin/env python3
# Harness: tool dispatch -- expanding what the model can reach.
"""
s02_tool_use.py - Tools

The agent loop from s01 didn't change. We just added tools to the array
and a dispatch map to route calls.

    +----------+      +-------+      +------------------+
    |   User   | ---> |  LLM  | ---> | Tool Dispatch    |
    |  prompt  |      |       |      | {                |
    +----------+      +---+---+      |   bash: run_bash |
                          ^          |   read: run_read |
                          |          |   write: run_wr  |
                          +----------+   edit: run_edit |
                          tool_result| }                |
                                     +------------------+

Key insight: "The loop didn't change at all. I just added tools."
"""

import os
import subprocess
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)

if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]

SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks. Act, don't explain."


def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path


def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(command, shell=True, cwd=WORKDIR,
                           capture_output=True, text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"


def run_read(path: str, limit: int = None) -> str:
    try:
        text = safe_path(path).read_text()
        lines = text.splitlines()
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more lines)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"


def run_write(path: str, content: str) -> str:
    try:
        fp = safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"Wrote {len(content)} bytes to {path}"
    except Exception as e:
        return f"Error: {e}"


def run_edit(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = safe_path(path)
        content = fp.read_text()
        if old_text not in content:
            return f"Error: Text not found in {path}"
        fp.write_text(content.replace(old_text, new_text, 1))
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"


# -- The dispatch map: {tool_name: handler} --
TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
}

TOOLS = [
    {"name": "bash", "description": "Run a shell command.",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "read_file", "description": "Read file contents.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
    {"name": "write_file", "description": "Write content to file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
    {"name": "edit_file", "description": "Replace exact text in file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
]


def agent_loop(messages: list):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                print(f"> {block.name}: {output[:200]}")
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
        messages.append({"role": "user", "content": results})


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms02 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s03_todo_write.py
================================================
#!/usr/bin/env python3
# Harness: planning -- keeping the model on course without scripting the route.
"""
s03_todo_write.py - TodoWrite

The model tracks its own progress via a TodoManager. A nag reminder
forces it to keep updating when it forgets.

    +----------+      +-------+      +---------+
    |   User   | ---> |  LLM  | ---> | Tools   |
    |  prompt  |      |       |      | + todo  |
    +----------+      +---+---+      +----+----+
                          ^               |
                          |   tool_result |
                          +---------------+
                                |
                    +-----------+-----------+
                    | TodoManager state     |
                    | [ ] task A            |
                    | [>] task B <- doing   |
                    | [x] task C            |
                    +-----------------------+
                                |
                    if rounds_since_todo >= 3:
                      inject <reminder>

Key insight: "The agent can track its own progress -- and I can see it."
"""

import os
import subprocess
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)

if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]

SYSTEM = f"""You are a coding agent at {WORKDIR}.
Use the todo tool to plan multi-step tasks. Mark in_progress before starting, completed when done.
Prefer tools over prose."""


# -- TodoManager: structured state the LLM writes to --
class TodoManager:
    def __init__(self):
        self.items = []

    def update(self, items: list) -> str:
        if len(items) > 20:
            raise ValueError("Max 20 todos allowed")
        validated = []
        in_progress_count = 0
        for i, item in enumerate(items):
            text = str(item.get("text", "")).strip()
            status = str(item.get("status", "pending")).lower()
            item_id = str(item.get("id", str(i + 1)))
            if not text:
                raise ValueError(f"Item {item_id}: text required")
            if status not in ("pending", "in_progress", "completed"):
                raise ValueError(f"Item {item_id}: invalid status '{status}'")
            if status == "in_progress":
                in_progress_count += 1
            validated.append({"id": item_id, "text": text, "status": status})
        if in_progress_count > 1:
            raise ValueError("Only one task can be in_progress at a time")
        self.items = validated
        return self.render()

    def render(self) -> str:
        if not self.items:
            return "No todos."
        lines = []
        for item in self.items:
            marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}[item["status"]]
            lines.append(f"{marker} #{item['id']}: {item['text']}")
        done = sum(1 for t in self.items if t["status"] == "completed")
        lines.append(f"\n({done}/{len(self.items)} completed)")
        return "\n".join(lines)


TODO = TodoManager()


# -- Tool implementations --
def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(command, shell=True, cwd=WORKDIR,
                           capture_output=True, text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"

def run_read(path: str, limit: int = None) -> str:
    try:
        lines = safe_path(path).read_text().splitlines()
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"

def run_write(path: str, content: str) -> str:
    try:
        fp = safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"Wrote {len(content)} bytes"
    except Exception as e:
        return f"Error: {e}"

def run_edit(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = safe_path(path)
        content = fp.read_text()
        if old_text not in content:
            return f"Error: Text not found in {path}"
        fp.write_text(content.replace(old_text, new_text, 1))
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"


TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
    "todo":       lambda **kw: TODO.update(kw["items"]),
}

TOOLS = [
    {"name": "bash", "description": "Run a shell command.",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "read_file", "description": "Read file contents.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
    {"name": "write_file", "description": "Write content to file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
    {"name": "edit_file", "description": "Replace exact text in file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
    {"name": "todo", "description": "Update task list. Track progress on multi-step tasks.",
     "input_schema": {"type": "object", "properties": {"items": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string"}, "text": {"type": "string"}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]}}, "required": ["id", "text", "status"]}}}, "required": ["items"]}},
]


# -- Agent loop with nag reminder injection --
def agent_loop(messages: list):
    rounds_since_todo = 0
    while True:
        # Nag reminder is injected below, alongside tool results
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        used_todo = False
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                try:
                    output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                except Exception as e:
                    output = f"Error: {e}"
                print(f"> {block.name}: {str(output)[:200]}")
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)})
                if block.name == "todo":
                    used_todo = True
        rounds_since_todo = 0 if used_todo else rounds_since_todo + 1
        if rounds_since_todo >= 3:
            results.insert(0, {"type": "text", "text": "<reminder>Update your todos.</reminder>"})
        messages.append({"role": "user", "content": results})


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms03 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s04_subagent.py
================================================
#!/usr/bin/env python3
# Harness: context isolation -- protecting the model's clarity of thought.
"""
s04_subagent.py - Subagents

Spawn a child agent with fresh messages=[]. The child works in its own
context, sharing the filesystem, then returns only a summary to the parent.

    Parent agent                     Subagent
    +------------------+             +------------------+
    | messages=[...]   |             | messages=[]      |  <-- fresh
    |                  |  dispatch   |                  |
    | tool: task       | ---------->| while tool_use:  |
    |   prompt="..."   |            |   call tools     |
    |   description="" |            |   append results |
    |                  |  summary   |                  |
    |   result = "..." | <--------- | return last text |
    +------------------+             +------------------+
              |
    Parent context stays clean.
    Subagent context is discarded.

Key insight: "Process isolation gives context isolation for free."
"""

import os
import subprocess
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)

if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]

SYSTEM = f"You are a coding agent at {WORKDIR}. Use the task tool to delegate exploration or subtasks."
SUBAGENT_SYSTEM = f"You are a coding subagent at {WORKDIR}. Complete the given task, then summarize your findings."


# -- Tool implementations shared by parent and child --
def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(command, shell=True, cwd=WORKDIR,
                           capture_output=True, text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"

def run_read(path: str, limit: int = None) -> str:
    try:
        lines = safe_path(path).read_text().splitlines()
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"

def run_write(path: str, content: str) -> str:
    try:
        fp = safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"Wrote {len(content)} bytes"
    except Exception as e:
        return f"Error: {e}"

def run_edit(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = safe_path(path)
        content = fp.read_text()
        if old_text not in content:
            return f"Error: Text not found in {path}"
        fp.write_text(content.replace(old_text, new_text, 1))
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"


TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
}

# Child gets all base tools except task (no recursive spawning)
CHILD_TOOLS = [
    {"name": "bash", "description": "Run a shell command.",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "read_file", "description": "Read file contents.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
    {"name": "write_file", "description": "Write content to file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
    {"name": "edit_file", "description": "Replace exact text in file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
]


# -- Subagent: fresh context, filtered tools, summary-only return --
def run_subagent(prompt: str) -> str:
    sub_messages = [{"role": "user", "content": prompt}]  # fresh context
    for _ in range(30):  # safety limit
        response = client.messages.create(
            model=MODEL, system=SUBAGENT_SYSTEM, messages=sub_messages,
            tools=CHILD_TOOLS, max_tokens=8000,
        )
        sub_messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            break
        results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)[:50000]})
        sub_messages.append({"role": "user", "content": results})
    # Only the final text returns to the parent -- child context is discarded
    return "".join(b.text for b in response.content if hasattr(b, "text")) or "(no summary)"


# -- Parent tools: base tools + task dispatcher --
PARENT_TOOLS = CHILD_TOOLS + [
    {"name": "task", "description": "Spawn a subagent with fresh context. It shares the filesystem but not conversation history.",
     "input_schema": {"type": "object", "properties": {"prompt": {"type": "string"}, "description": {"type": "string", "description": "Short description of the task"}}, "required": ["prompt"]}},
]


def agent_loop(messages: list):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=PARENT_TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        for block in response.content:
            if block.type == "tool_use":
                if block.name == "task":
                    desc = block.input.get("description", "subtask")
                    print(f"> task ({desc}): {block.input['prompt'][:80]}")
                    output = run_subagent(block.input["prompt"])
                else:
                    handler = TOOL_HANDLERS.get(block.name)
                    output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                print(f"  {str(output)[:200]}")
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)})
        messages.append({"role": "user", "content": results})


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms04 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s05_skill_loading.py
================================================
#!/usr/bin/env python3
# Harness: on-demand knowledge -- domain expertise, loaded when the model asks.
"""
s05_skill_loading.py - Skills

Two-layer skill injection that avoids bloating the system prompt:

    Layer 1 (cheap): skill names in system prompt (~100 tokens/skill)
    Layer 2 (on demand): full skill body in tool_result

    skills/
      pdf/
        SKILL.md          <-- frontmatter (name, description) + body
      code-review/
        SKILL.md

    System prompt:
    +--------------------------------------+
    | You are a coding agent.              |
    | Skills available:                    |
    |   - pdf: Process PDF files...        |  <-- Layer 1: metadata only
    |   - code-review: Review code...      |
    +--------------------------------------+

    When model calls load_skill("pdf"):
    +--------------------------------------+
    | tool_result:                         |
    | <skill>                              |
    |   Full PDF processing instructions   |  <-- Layer 2: full body
    |   Step 1: ...                        |
    |   Step 2: ...                        |
    | </skill>                             |
    +--------------------------------------+

Key insight: "Don't put everything in the system prompt. Load on demand."
"""

import os
import re
import subprocess
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)

if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]
SKILLS_DIR = WORKDIR / "skills"


# -- SkillLoader: scan skills/<name>/SKILL.md with YAML frontmatter --
class SkillLoader:
    def __init__(self, skills_dir: Path):
        self.skills_dir = skills_dir
        self.skills = {}
        self._load_all()

    def _load_all(self):
        if not self.skills_dir.exists():
            return
        for f in sorted(self.skills_dir.rglob("SKILL.md")):
            text = f.read_text()
            meta, body = self._parse_frontmatter(text)
            name = meta.get("name", f.parent.name)
            self.skills[name] = {"meta": meta, "body": body, "path": str(f)}

    def _parse_frontmatter(self, text: str) -> tuple:
        """Parse YAML frontmatter between --- delimiters."""
        match = re.match(r"^---\n(.*?)\n---\n(.*)", text, re.DOTALL)
        if not match:
            return {}, text
        meta = {}
        for line in match.group(1).strip().splitlines():
            if ":" in line:
                key, val = line.split(":", 1)
                meta[key.strip()] = val.strip()
        return meta, match.group(2).strip()

    def get_descriptions(self) -> str:
        """Layer 1: short descriptions for the system prompt."""
        if not self.skills:
            return "(no skills available)"
        lines = []
        for name, skill in self.skills.items():
            desc = skill["meta"].get("description", "No description")
            tags = skill["meta"].get("tags", "")
            line = f"  - {name}: {desc}"
            if tags:
                line += f" [{tags}]"
            lines.append(line)
        return "\n".join(lines)

    def get_content(self, name: str) -> str:
        """Layer 2: full skill body returned in tool_result."""
        skill = self.skills.get(name)
        if not skill:
            return f"Error: Unknown skill '{name}'. Available: {', '.join(self.skills.keys())}"
        return f"<skill name=\"{name}\">\n{skill['body']}\n</skill>"


SKILL_LOADER = SkillLoader(SKILLS_DIR)

# Layer 1: skill metadata injected into system prompt
SYSTEM = f"""You are a coding agent at {WORKDIR}.
Use load_skill to access specialized knowledge before tackling unfamiliar topics.

Skills available:
{SKILL_LOADER.get_descriptions()}"""


# -- Tool implementations --
def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(command, shell=True, cwd=WORKDIR,
                           capture_output=True, text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"

def run_read(path: str, limit: int = None) -> str:
    try:
        lines = safe_path(path).read_text().splitlines()
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"

def run_write(path: str, content: str) -> str:
    try:
        fp = safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"Wrote {len(content)} bytes"
    except Exception as e:
        return f"Error: {e}"

def run_edit(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = safe_path(path)
        content = fp.read_text()
        if old_text not in content:
            return f"Error: Text not found in {path}"
        fp.write_text(content.replace(old_text, new_text, 1))
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"


TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
    "load_skill": lambda **kw: SKILL_LOADER.get_content(kw["name"]),
}

TOOLS = [
    {"name": "bash", "description": "Run a shell command.",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "read_file", "description": "Read file contents.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
    {"name": "write_file", "description": "Write content to file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
    {"name": "edit_file", "description": "Replace exact text in file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
    {"name": "load_skill", "description": "Load specialized knowledge by name.",
     "input_schema": {"type": "object", "properties": {"name": {"type": "string", "description": "Skill name to load"}}, "required": ["name"]}},
]


def agent_loop(messages: list):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                try:
                    output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                except Exception as e:
                    output = f"Error: {e}"
                print(f"> {block.name}: {str(output)[:200]}")
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)})
        messages.append({"role": "user", "content": results})


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms05 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s06_context_compact.py
================================================
#!/usr/bin/env python3
# Harness: compression -- clean memory for infinite sessions.
"""
s06_context_compact.py - Compact

Three-layer compression pipeline so the agent can work forever:

    Every turn:
    +------------------+
    | Tool call result |
    +------------------+
            |
            v
    [Layer 1: micro_compact]        (silent, every turn)
      Replace tool_result content older than last 3
      with "[Previous: used {tool_name}]"
            |
            v
    [Check: tokens > 50000?]
       |               |
       no              yes
       |               |
       v               v
    continue    [Layer 2: auto_compact]
                  Save full transcript to .transcripts/
                  Ask LLM to summarize conversation.
                  Replace all messages with [summary].
                        |
                        v
                [Layer 3: compact tool]
                  Model calls compact -> immediate summarization.
                  Same as auto, triggered manually.

Key insight: "The agent can forget strategically and keep working forever."
"""

import json
import os
import subprocess
import time
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)

if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]

SYSTEM = f"You are a coding agent at {WORKDIR}. Use tools to solve tasks."

THRESHOLD = 50000
TRANSCRIPT_DIR = WORKDIR / ".transcripts"
KEEP_RECENT = 3


def estimate_tokens(messages: list) -> int:
    """Rough token count: ~4 chars per token."""
    return len(str(messages)) // 4


# -- Layer 1: micro_compact - replace old tool results with placeholders --
def micro_compact(messages: list) -> list:
    # Collect (msg_index, part_index, tool_result_dict) for all tool_result entries
    tool_results = []
    for msg_idx, msg in enumerate(messages):
        if msg["role"] == "user" and isinstance(msg.get("content"), list):
            for part_idx, part in enumerate(msg["content"]):
                if isinstance(part, dict) and part.get("type") == "tool_result":
                    tool_results.append((msg_idx, part_idx, part))
    if len(tool_results) <= KEEP_RECENT:
        return messages
    # Find tool_name for each result by matching tool_use_id in prior assistant messages
    tool_name_map = {}
    for msg in messages:
        if msg["role"] == "assistant":
            content = msg.get("content", [])
            if isinstance(content, list):
                for block in content:
                    if hasattr(block, "type") and block.type == "tool_use":
                        tool_name_map[block.id] = block.name
    # Clear old results (keep last KEEP_RECENT)
    to_clear = tool_results[:-KEEP_RECENT]
    for _, _, result in to_clear:
        if isinstance(result.get("content"), str) and len(result["content"]) > 100:
            tool_id = result.get("tool_use_id", "")
            tool_name = tool_name_map.get(tool_id, "unknown")
            result["content"] = f"[Previous: used {tool_name}]"
    return messages


# -- Layer 2: auto_compact - save transcript, summarize, replace messages --
def auto_compact(messages: list) -> list:
    # Save full transcript to disk
    TRANSCRIPT_DIR.mkdir(exist_ok=True)
    transcript_path = TRANSCRIPT_DIR / f"transcript_{int(time.time())}.jsonl"
    with open(transcript_path, "w") as f:
        for msg in messages:
            f.write(json.dumps(msg, default=str) + "\n")
    print(f"[transcript saved: {transcript_path}]")
    # Ask LLM to summarize
    conversation_text = json.dumps(messages, default=str)[:80000]
    response = client.messages.create(
        model=MODEL,
        messages=[{"role": "user", "content":
            "Summarize this conversation for continuity. Include: "
            "1) What was accomplished, 2) Current state, 3) Key decisions made. "
            "Be concise but preserve critical details.\n\n" + conversation_text}],
        max_tokens=2000,
    )
    summary = response.content[0].text
    # Replace all messages with compressed summary
    return [
        {"role": "user", "content": f"[Conversation compressed. Transcript: {transcript_path}]\n\n{summary}"},
        {"role": "assistant", "content": "Understood. I have the context from the summary. Continuing."},
    ]


# -- Tool implementations --
def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(command, shell=True, cwd=WORKDIR,
                           capture_output=True, text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"

def run_read(path: str, limit: int = None) -> str:
    try:
        lines = safe_path(path).read_text().splitlines()
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"

def run_write(path: str, content: str) -> str:
    try:
        fp = safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"Wrote {len(content)} bytes"
    except Exception as e:
        return f"Error: {e}"

def run_edit(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = safe_path(path)
        content = fp.read_text()
        if old_text not in content:
            return f"Error: Text not found in {path}"
        fp.write_text(content.replace(old_text, new_text, 1))
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"


TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
    "compact":    lambda **kw: "Manual compression requested.",
}

TOOLS = [
    {"name": "bash", "description": "Run a shell command.",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "read_file", "description": "Read file contents.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
    {"name": "write_file", "description": "Write content to file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
    {"name": "edit_file", "description": "Replace exact text in file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
    {"name": "compact", "description": "Trigger manual conversation compression.",
     "input_schema": {"type": "object", "properties": {"focus": {"type": "string", "description": "What to preserve in the summary"}}}},
]


def agent_loop(messages: list):
    while True:
        # Layer 1: micro_compact before each LLM call
        micro_compact(messages)
        # Layer 2: auto_compact if token estimate exceeds threshold
        if estimate_tokens(messages) > THRESHOLD:
            print("[auto_compact triggered]")
            messages[:] = auto_compact(messages)
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        manual_compact = False
        for block in response.content:
            if block.type == "tool_use":
                if block.name == "compact":
                    manual_compact = True
                    output = "Compressing..."
                else:
                    handler = TOOL_HANDLERS.get(block.name)
                    try:
                        output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                    except Exception as e:
                        output = f"Error: {e}"
                print(f"> {block.name}: {str(output)[:200]}")
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)})
        messages.append({"role": "user", "content": results})
        # Layer 3: manual compact triggered by the compact tool
        if manual_compact:
            print("[manual compact]")
            messages[:] = auto_compact(messages)


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms06 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s07_task_system.py
================================================
#!/usr/bin/env python3
# Harness: persistent tasks -- goals that outlive any single conversation.
"""
s07_task_system.py - Tasks

Tasks persist as JSON files in .tasks/ so they survive context compression.
Each task has a dependency graph (blockedBy/blocks).

    .tasks/
      task_1.json  {"id":1, "subject":"...", "status":"completed", ...}
      task_2.json  {"id":2, "blockedBy":[1], "status":"pending", ...}
      task_3.json  {"id":3, "blockedBy":[2], "blocks":[], ...}

    Dependency resolution:
    +----------+     +----------+     +----------+
    | task 1   | --> | task 2   | --> | task 3   |
    | complete |     | blocked  |     | blocked  |
    +----------+     +----------+     +----------+
         |                ^
         +--- completing task 1 removes it from task 2's blockedBy

Key insight: "State that survives compression -- because it's outside the conversation."
"""

import json
import os
import subprocess
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)

if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]
TASKS_DIR = WORKDIR / ".tasks"

SYSTEM = f"You are a coding agent at {WORKDIR}. Use task tools to plan and track work."


# -- TaskManager: CRUD with dependency graph, persisted as JSON files --
class TaskManager:
    def __init__(self, tasks_dir: Path):
        self.dir = tasks_dir
        self.dir.mkdir(exist_ok=True)
        self._next_id = self._max_id() + 1

    def _max_id(self) -> int:
        ids = [int(f.stem.split("_")[1]) for f in self.dir.glob("task_*.json")]
        return max(ids) if ids else 0

    def _load(self, task_id: int) -> dict:
        path = self.dir / f"task_{task_id}.json"
        if not path.exists():
            raise ValueError(f"Task {task_id} not found")
        return json.loads(path.read_text())

    def _save(self, task: dict):
        path = self.dir / f"task_{task['id']}.json"
        path.write_text(json.dumps(task, indent=2))

    def create(self, subject: str, description: str = "") -> str:
        task = {
            "id": self._next_id, "subject": subject, "description": description,
            "status": "pending", "blockedBy": [], "blocks": [], "owner": "",
        }
        self._save(task)
        self._next_id += 1
        return json.dumps(task, indent=2)

    def get(self, task_id: int) -> str:
        return json.dumps(self._load(task_id), indent=2)

    def update(self, task_id: int, status: str = None,
               add_blocked_by: list = None, add_blocks: list = None) -> str:
        task = self._load(task_id)
        if status:
            if status not in ("pending", "in_progress", "completed"):
                raise ValueError(f"Invalid status: {status}")
            task["status"] = status
            # When a task is completed, remove it from all other tasks' blockedBy
            if status == "completed":
                self._clear_dependency(task_id)
        if add_blocked_by:
            task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by))
        if add_blocks:
            task["blocks"] = list(set(task["blocks"] + add_blocks))
            # Bidirectional: also update the blocked tasks' blockedBy lists
            for blocked_id in add_blocks:
                try:
                    blocked = self._load(blocked_id)
                    if task_id not in blocked["blockedBy"]:
                        blocked["blockedBy"].append(task_id)
                        self._save(blocked)
                except ValueError:
                    pass
        self._save(task)
        return json.dumps(task, indent=2)

    def _clear_dependency(self, completed_id: int):
        """Remove completed_id from all other tasks' blockedBy lists."""
        for f in self.dir.glob("task_*.json"):
            task = json.loads(f.read_text())
            if completed_id in task.get("blockedBy", []):
                task["blockedBy"].remove(completed_id)
                self._save(task)

    def list_all(self) -> str:
        tasks = []
        for f in sorted(self.dir.glob("task_*.json")):
            tasks.append(json.loads(f.read_text()))
        if not tasks:
            return "No tasks."
        lines = []
        for t in tasks:
            marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}.get(t["status"], "[?]")
            blocked = f" (blocked by: {t['blockedBy']})" if t.get("blockedBy") else ""
            lines.append(f"{marker} #{t['id']}: {t['subject']}{blocked}")
        return "\n".join(lines)


TASKS = TaskManager(TASKS_DIR)


# -- Base tool implementations --
def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(command, shell=True, cwd=WORKDIR,
                           capture_output=True, text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"

def run_read(path: str, limit: int = None) -> str:
    try:
        lines = safe_path(path).read_text().splitlines()
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"

def run_write(path: str, content: str) -> str:
    try:
        fp = safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"Wrote {len(content)} bytes"
    except Exception as e:
        return f"Error: {e}"

def run_edit(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = safe_path(path)
        c = fp.read_text()
        if old_text not in c:
            return f"Error: Text not found in {path}"
        fp.write_text(c.replace(old_text, new_text, 1))
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"


TOOL_HANDLERS = {
    "bash":        lambda **kw: run_bash(kw["command"]),
    "read_file":   lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file":  lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":   lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
    "task_create": lambda **kw: TASKS.create(kw["subject"], kw.get("description", "")),
    "task_update": lambda **kw: TASKS.update(kw["task_id"], kw.get("status"), kw.get("addBlockedBy"), kw.get("addBlocks")),
    "task_list":   lambda **kw: TASKS.list_all(),
    "task_get":    lambda **kw: TASKS.get(kw["task_id"]),
}

TOOLS = [
    {"name": "bash", "description": "Run a shell command.",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "read_file", "description": "Read file contents.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
    {"name": "write_file", "description": "Write content to file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
    {"name": "edit_file", "description": "Replace exact text in file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
    {"name": "task_create", "description": "Create a new task.",
     "input_schema": {"type": "object", "properties": {"subject": {"type": "string"}, "description": {"type": "string"}}, "required": ["subject"]}},
    {"name": "task_update", "description": "Update a task's status or dependencies.",
     "input_schema": {"type": "object", "properties": {"task_id": {"type": "integer"}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]}, "addBlockedBy": {"type": "array", "items": {"type": "integer"}}, "addBlocks": {"type": "array", "items": {"type": "integer"}}}, "required": ["task_id"]}},
    {"name": "task_list", "description": "List all tasks with status summary.",
     "input_schema": {"type": "object", "properties": {}}},
    {"name": "task_get", "description": "Get full details of a task by ID.",
     "input_schema": {"type": "object", "properties": {"task_id": {"type": "integer"}}, "required": ["task_id"]}},
]


def agent_loop(messages: list):
    while True:
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                try:
                    output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                except Exception as e:
                    output = f"Error: {e}"
                print(f"> {block.name}: {str(output)[:200]}")
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)})
        messages.append({"role": "user", "content": results})


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms07 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s08_background_tasks.py
================================================
#!/usr/bin/env python3
# Harness: background execution -- the model thinks while the harness waits.
"""
s08_background_tasks.py - Background Tasks

Run commands in background threads. A notification queue is drained
before each LLM call to deliver results.

    Main thread                Background thread
    +-----------------+        +-----------------+
    | agent loop      |        | task executes   |
    | ...             |        | ...             |
    | [LLM call] <---+------- | enqueue(result) |
    |  ^drain queue   |        +-----------------+
    +-----------------+

    Timeline:
    Agent ----[spawn A]----[spawn B]----[other work]----
                 |              |
                 v              v
              [A runs]      [B runs]        (parallel)
                 |              |
                 +-- notification queue --> [results injected]

Key insight: "Fire and forget -- the agent doesn't block while the command runs."
"""

import os
import subprocess
import threading
import uuid
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)

if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]

SYSTEM = f"You are a coding agent at {WORKDIR}. Use background_run for long-running commands."


# -- BackgroundManager: threaded execution + notification queue --
class BackgroundManager:
    def __init__(self):
        self.tasks = {}  # task_id -> {status, result, command}
        self._notification_queue = []  # completed task results
        self._lock = threading.Lock()

    def run(self, command: str) -> str:
        """Start a background thread, return task_id immediately."""
        task_id = str(uuid.uuid4())[:8]
        self.tasks[task_id] = {"status": "running", "result": None, "command": command}
        thread = threading.Thread(
            target=self._execute, args=(task_id, command), daemon=True
        )
        thread.start()
        return f"Background task {task_id} started: {command[:80]}"

    def _execute(self, task_id: str, command: str):
        """Thread target: run subprocess, capture output, push to queue."""
        try:
            r = subprocess.run(
                command, shell=True, cwd=WORKDIR,
                capture_output=True, text=True, timeout=300
            )
            output = (r.stdout + r.stderr).strip()[:50000]
            status = "completed"
        except subprocess.TimeoutExpired:
            output = "Error: Timeout (300s)"
            status = "timeout"
        except Exception as e:
            output = f"Error: {e}"
            status = "error"
        self.tasks[task_id]["status"] = status
        self.tasks[task_id]["result"] = output or "(no output)"
        with self._lock:
            self._notification_queue.append({
                "task_id": task_id,
                "status": status,
                "command": command[:80],
                "result": (output or "(no output)")[:500],
            })

    def check(self, task_id: str = None) -> str:
        """Check status of one task or list all."""
        if task_id:
            t = self.tasks.get(task_id)
            if not t:
                return f"Error: Unknown task {task_id}"
            return f"[{t['status']}] {t['command'][:60]}\n{t.get('result') or '(running)'}"
        lines = []
        for tid, t in self.tasks.items():
            lines.append(f"{tid}: [{t['status']}] {t['command'][:60]}")
        return "\n".join(lines) if lines else "No background tasks."

    def drain_notifications(self) -> list:
        """Return and clear all pending completion notifications."""
        with self._lock:
            notifs = list(self._notification_queue)
            self._notification_queue.clear()
        return notifs


BG = BackgroundManager()


# -- Tool implementations --
def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

def run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(command, shell=True, cwd=WORKDIR,
                           capture_output=True, text=True, timeout=120)
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"

def run_read(path: str, limit: int = None) -> str:
    try:
        lines = safe_path(path).read_text().splitlines()
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"

def run_write(path: str, content: str) -> str:
    try:
        fp = safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"Wrote {len(content)} bytes"
    except Exception as e:
        return f"Error: {e}"

def run_edit(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = safe_path(path)
        c = fp.read_text()
        if old_text not in c:
            return f"Error: Text not found in {path}"
        fp.write_text(c.replace(old_text, new_text, 1))
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"


TOOL_HANDLERS = {
    "bash":             lambda **kw: run_bash(kw["command"]),
    "read_file":        lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file":       lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":        lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
    "background_run":   lambda **kw: BG.run(kw["command"]),
    "check_background": lambda **kw: BG.check(kw.get("task_id")),
}

TOOLS = [
    {"name": "bash", "description": "Run a shell command (blocking).",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "read_file", "description": "Read file contents.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
    {"name": "write_file", "description": "Write content to file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
    {"name": "edit_file", "description": "Replace exact text in file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
    {"name": "background_run", "description": "Run command in background thread. Returns task_id immediately.",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "check_background", "description": "Check background task status. Omit task_id to list all.",
     "input_schema": {"type": "object", "properties": {"task_id": {"type": "string"}}}},
]


def agent_loop(messages: list):
    while True:
        # Drain background notifications and inject as system message before LLM call
        notifs = BG.drain_notifications()
        if notifs and messages:
            notif_text = "\n".join(
                f"[bg:{n['task_id']}] {n['status']}: {n['result']}" for n in notifs
            )
            messages.append({"role": "user", "content": f"<background-results>\n{notif_text}\n</background-results>"})
            messages.append({"role": "assistant", "content": "Noted background results."})
        response = client.messages.create(
            model=MODEL, system=SYSTEM, messages=messages,
            tools=TOOLS, max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                try:
                    output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                except Exception as e:
                    output = f"Error: {e}"
                print(f"> {block.name}: {str(output)[:200]}")
                results.append({"type": "tool_result", "tool_use_id": block.id, "content": str(output)})
        messages.append({"role": "user", "content": results})


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms08 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s09_agent_teams.py
================================================
#!/usr/bin/env python3
# Harness: team mailboxes -- multiple models, coordinated through files.
"""
s09_agent_teams.py - Agent Teams

Persistent named agents with file-based JSONL inboxes. Each teammate runs
its own agent loop in a separate thread. Communication via append-only inboxes.

    Subagent (s04):  spawn -> execute -> return summary -> destroyed
    Teammate (s09):  spawn -> work -> idle -> work -> ... -> shutdown

    .team/config.json                   .team/inbox/
    +----------------------------+      +------------------+
    | {"team_name": "default",   |      | alice.jsonl      |
    |  "members": [              |      | bob.jsonl        |
    |    {"name":"alice",        |      | lead.jsonl       |
    |     "role":"coder",        |      +------------------+
    |     "status":"idle"}       |
    |  ]}                        |      send_message("alice", "fix bug"):
    +----------------------------+        open("alice.jsonl", "a").write(msg)

                                        read_inbox("alice"):
    spawn_teammate("alice","coder",...)   msgs = [json.loads(l) for l in ...]
         |                                open("alice.jsonl", "w").close()
         v                                return msgs  # drain
    Thread: alice             Thread: bob
    +------------------+      +------------------+
    | agent_loop       |      | agent_loop       |
    | status: working  |      | status: idle     |
    | ... runs tools   |      | ... waits ...    |
    | status -> idle   |      |                  |
    +------------------+      +------------------+

    5 message types (all declared, not all handled here):
    +-------------------------+-----------------------------------+
    | message                 | Normal text message               |
    | broadcast               | Sent to all teammates             |
    | shutdown_request        | Request graceful shutdown (s10)   |
    | shutdown_response       | Approve/reject shutdown (s10)     |
    | plan_approval_response  | Approve/reject plan (s10)         |
    +-------------------------+-----------------------------------+

Key insight: "Teammates that can talk to each other."
"""

import json
import os
import subprocess
import threading
import time
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)
if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]
TEAM_DIR = WORKDIR / ".team"
INBOX_DIR = TEAM_DIR / "inbox"

SYSTEM = f"You are a team lead at {WORKDIR}. Spawn teammates and communicate via inboxes."

VALID_MSG_TYPES = {
    "message",
    "broadcast",
    "shutdown_request",
    "shutdown_response",
    "plan_approval_response",
}


# -- MessageBus: JSONL inbox per teammate --
class MessageBus:
    def __init__(self, inbox_dir: Path):
        self.dir = inbox_dir
        self.dir.mkdir(parents=True, exist_ok=True)

    def send(self, sender: str, to: str, content: str,
             msg_type: str = "message", extra: dict = None) -> str:
        if msg_type not in VALID_MSG_TYPES:
            return f"Error: Invalid type '{msg_type}'. Valid: {VALID_MSG_TYPES}"
        msg = {
            "type": msg_type,
            "from": sender,
            "content": content,
            "timestamp": time.time(),
        }
        if extra:
            msg.update(extra)
        inbox_path = self.dir / f"{to}.jsonl"
        with open(inbox_path, "a") as f:
            f.write(json.dumps(msg) + "\n")
        return f"Sent {msg_type} to {to}"

    def read_inbox(self, name: str) -> list:
        inbox_path = self.dir / f"{name}.jsonl"
        if not inbox_path.exists():
            return []
        messages = []
        for line in inbox_path.read_text().strip().splitlines():
            if line:
                messages.append(json.loads(line))
        inbox_path.write_text("")
        return messages

    def broadcast(self, sender: str, content: str, teammates: list) -> str:
        count = 0
        for name in teammates:
            if name != sender:
                self.send(sender, name, content, "broadcast")
                count += 1
        return f"Broadcast to {count} teammates"


BUS = MessageBus(INBOX_DIR)


# -- TeammateManager: persistent named agents with config.json --
class TeammateManager:
    def __init__(self, team_dir: Path):
        self.dir = team_dir
        self.dir.mkdir(exist_ok=True)
        self.config_path = self.dir / "config.json"
        self.config = self._load_config()
        self.threads = {}

    def _load_config(self) -> dict:
        if self.config_path.exists():
            return json.loads(self.config_path.read_text())
        return {"team_name": "default", "members": []}

    def _save_config(self):
        self.config_path.write_text(json.dumps(self.config, indent=2))

    def _find_member(self, name: str) -> dict:
        for m in self.config["members"]:
            if m["name"] == name:
                return m
        return None

    def spawn(self, name: str, role: str, prompt: str) -> str:
        member = self._find_member(name)
        if member:
            if member["status"] not in ("idle", "shutdown"):
                return f"Error: '{name}' is currently {member['status']}"
            member["status"] = "working"
            member["role"] = role
        else:
            member = {"name": name, "role": role, "status": "working"}
            self.config["members"].append(member)
        self._save_config()
        thread = threading.Thread(
            target=self._teammate_loop,
            args=(name, role, prompt),
            daemon=True,
        )
        self.threads[name] = thread
        thread.start()
        return f"Spawned '{name}' (role: {role})"

    def _teammate_loop(self, name: str, role: str, prompt: str):
        sys_prompt = (
            f"You are '{name}', role: {role}, at {WORKDIR}. "
            f"Use send_message to communicate. Complete your task."
        )
        messages = [{"role": "user", "content": prompt}]
        tools = self._teammate_tools()
        for _ in range(50):
            inbox = BUS.read_inbox(name)
            for msg in inbox:
                messages.append({"role": "user", "content": json.dumps(msg)})
            try:
                response = client.messages.create(
                    model=MODEL,
                    system=sys_prompt,
                    messages=messages,
                    tools=tools,
                    max_tokens=8000,
                )
            except Exception:
                break
            messages.append({"role": "assistant", "content": response.content})
            if response.stop_reason != "tool_use":
                break
            results = []
            for block in response.content:
                if block.type == "tool_use":
                    output = self._exec(name, block.name, block.input)
                    print(f"  [{name}] {block.name}: {str(output)[:120]}")
                    results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": str(output),
                    })
            messages.append({"role": "user", "content": results})
        member = self._find_member(name)
        if member and member["status"] != "shutdown":
            member["status"] = "idle"
            self._save_config()

    def _exec(self, sender: str, tool_name: str, args: dict) -> str:
        # these base tools are unchanged from s02
        if tool_name == "bash":
            return _run_bash(args["command"])
        if tool_name == "read_file":
            return _run_read(args["path"])
        if tool_name == "write_file":
            return _run_write(args["path"], args["content"])
        if tool_name == "edit_file":
            return _run_edit(args["path"], args["old_text"], args["new_text"])
        if tool_name == "send_message":
            return BUS.send(sender, args["to"], args["content"], args.get("msg_type", "message"))
        if tool_name == "read_inbox":
            return json.dumps(BUS.read_inbox(sender), indent=2)
        return f"Unknown tool: {tool_name}"

    def _teammate_tools(self) -> list:
        # these base tools are unchanged from s02
        return [
            {"name": "bash", "description": "Run a shell command.",
             "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
            {"name": "read_file", "description": "Read file contents.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}},
            {"name": "write_file", "description": "Write content to file.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
            {"name": "edit_file", "description": "Replace exact text in file.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
            {"name": "send_message", "description": "Send message to a teammate.",
             "input_schema": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}},
            {"name": "read_inbox", "description": "Read and drain your inbox.",
             "input_schema": {"type": "object", "properties": {}}},
        ]

    def list_all(self) -> str:
        if not self.config["members"]:
            return "No teammates."
        lines = [f"Team: {self.config['team_name']}"]
        for m in self.config["members"]:
            lines.append(f"  {m['name']} ({m['role']}): {m['status']}")
        return "\n".join(lines)

    def member_names(self) -> list:
        return [m["name"] for m in self.config["members"]]


TEAM = TeammateManager(TEAM_DIR)


# -- Base tool implementations (these base tools are unchanged from s02) --
def _safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path


def _run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(
            command, shell=True, cwd=WORKDIR,
            capture_output=True, text=True, timeout=120,
        )
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"


def _run_read(path: str, limit: int = None) -> str:
    try:
        lines = _safe_path(path).read_text().splitlines()
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"


def _run_write(path: str, content: str) -> str:
    try:
        fp = _safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"Wrote {len(content)} bytes"
    except Exception as e:
        return f"Error: {e}"


def _run_edit(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = _safe_path(path)
        c = fp.read_text()
        if old_text not in c:
            return f"Error: Text not found in {path}"
        fp.write_text(c.replace(old_text, new_text, 1))
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"


# -- Lead tool dispatch (9 tools) --
TOOL_HANDLERS = {
    "bash":            lambda **kw: _run_bash(kw["command"]),
    "read_file":       lambda **kw: _run_read(kw["path"], kw.get("limit")),
    "write_file":      lambda **kw: _run_write(kw["path"], kw["content"]),
    "edit_file":       lambda **kw: _run_edit(kw["path"], kw["old_text"], kw["new_text"]),
    "spawn_teammate":  lambda **kw: TEAM.spawn(kw["name"], kw["role"], kw["prompt"]),
    "list_teammates":  lambda **kw: TEAM.list_all(),
    "send_message":    lambda **kw: BUS.send("lead", kw["to"], kw["content"], kw.get("msg_type", "message")),
    "read_inbox":      lambda **kw: json.dumps(BUS.read_inbox("lead"), indent=2),
    "broadcast":       lambda **kw: BUS.broadcast("lead", kw["content"], TEAM.member_names()),
}

# these base tools are unchanged from s02
TOOLS = [
    {"name": "bash", "description": "Run a shell command.",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "read_file", "description": "Read file contents.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
    {"name": "write_file", "description": "Write content to file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
    {"name": "edit_file", "description": "Replace exact text in file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
    {"name": "spawn_teammate", "description": "Spawn a persistent teammate that runs in its own thread.",
     "input_schema": {"type": "object", "properties": {"name": {"type": "string"}, "role": {"type": "string"}, "prompt": {"type": "string"}}, "required": ["name", "role", "prompt"]}},
    {"name": "list_teammates", "description": "List all teammates with name, role, status.",
     "input_schema": {"type": "object", "properties": {}}},
    {"name": "send_message", "description": "Send a message to a teammate's inbox.",
     "input_schema": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}},
    {"name": "read_inbox", "description": "Read and drain the lead's inbox.",
     "input_schema": {"type": "object", "properties": {}}},
    {"name": "broadcast", "description": "Send a message to all teammates.",
     "input_schema": {"type": "object", "properties": {"content": {"type": "string"}}, "required": ["content"]}},
]


def agent_loop(messages: list):
    while True:
        inbox = BUS.read_inbox("lead")
        if inbox:
            messages.append({
                "role": "user",
                "content": f"<inbox>{json.dumps(inbox, indent=2)}</inbox>",
            })
            messages.append({
                "role": "assistant",
                "content": "Noted inbox messages.",
            })
        response = client.messages.create(
            model=MODEL,
            system=SYSTEM,
            messages=messages,
            tools=TOOLS,
            max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                try:
                    output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                except Exception as e:
                    output = f"Error: {e}"
                print(f"> {block.name}: {str(output)[:200]}")
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": str(output),
                })
        messages.append({"role": "user", "content": results})


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms09 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        if query.strip() == "/team":
            print(TEAM.list_all())
            continue
        if query.strip() == "/inbox":
            print(json.dumps(BUS.read_inbox("lead"), indent=2))
            continue
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s10_team_protocols.py
================================================
#!/usr/bin/env python3
# Harness: protocols -- structured handshakes between models.
"""
s10_team_protocols.py - Team Protocols

Shutdown protocol and plan approval protocol, both using the same
request_id correlation pattern. Builds on s09's team messaging.

    Shutdown FSM: pending -> approved | rejected

    Lead                              Teammate
    +---------------------+          +---------------------+
    | shutdown_request     |          |                     |
    | {                    | -------> | receives request    |
    |   request_id: abc    |          | decides: approve?   |
    | }                    |          |                     |
    +---------------------+          +---------------------+
                                             |
    +---------------------+          +-------v-------------+
    | shutdown_response    | <------- | shutdown_response   |
    | {                    |          | {                   |
    |   request_id: abc    |          |   request_id: abc   |
    |   approve: true      |          |   approve: true     |
    | }                    |          | }                   |
    +---------------------+          +---------------------+
            |
            v
    status -> "shutdown", thread stops

    Plan approval FSM: pending -> approved | rejected

    Teammate                          Lead
    +---------------------+          +---------------------+
    | plan_approval        |          |                     |
    | submit: {plan:"..."}| -------> | reviews plan text   |
    +---------------------+          | approve/reject?     |
                                     +---------------------+
                                             |
    +---------------------+          +-------v-------------+
    | plan_approval_resp   | <------- | plan_approval       |
    | {approve: true}      |          | review: {req_id,    |
    +---------------------+          |   approve: true}     |
                                     +---------------------+

    Trackers: {request_id: {"target|from": name, "status": "pending|..."}}

Key insight: "Same request_id correlation pattern, two domains."
"""

import json
import os
import subprocess
import threading
import time
import uuid
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)
if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]
TEAM_DIR = WORKDIR / ".team"
INBOX_DIR = TEAM_DIR / "inbox"

SYSTEM = f"You are a team lead at {WORKDIR}. Manage teammates with shutdown and plan approval protocols."

VALID_MSG_TYPES = {
    "message",
    "broadcast",
    "shutdown_request",
    "shutdown_response",
    "plan_approval_response",
}

# -- Request trackers: correlate by request_id --
shutdown_requests = {}
plan_requests = {}
_tracker_lock = threading.Lock()


# -- MessageBus: JSONL inbox per teammate --
class MessageBus:
    def __init__(self, inbox_dir: Path):
        self.dir = inbox_dir
        self.dir.mkdir(parents=True, exist_ok=True)

    def send(self, sender: str, to: str, content: str,
             msg_type: str = "message", extra: dict = None) -> str:
        if msg_type not in VALID_MSG_TYPES:
            return f"Error: Invalid type '{msg_type}'. Valid: {VALID_MSG_TYPES}"
        msg = {
            "type": msg_type,
            "from": sender,
            "content": content,
            "timestamp": time.time(),
        }
        if extra:
            msg.update(extra)
        inbox_path = self.dir / f"{to}.jsonl"
        with open(inbox_path, "a") as f:
            f.write(json.dumps(msg) + "\n")
        return f"Sent {msg_type} to {to}"

    def read_inbox(self, name: str) -> list:
        inbox_path = self.dir / f"{name}.jsonl"
        if not inbox_path.exists():
            return []
        messages = []
        for line in inbox_path.read_text().strip().splitlines():
            if line:
                messages.append(json.loads(line))
        inbox_path.write_text("")
        return messages

    def broadcast(self, sender: str, content: str, teammates: list) -> str:
        count = 0
        for name in teammates:
            if name != sender:
                self.send(sender, name, content, "broadcast")
                count += 1
        return f"Broadcast to {count} teammates"


BUS = MessageBus(INBOX_DIR)


# -- TeammateManager with shutdown + plan approval --
class TeammateManager:
    def __init__(self, team_dir: Path):
        self.dir = team_dir
        self.dir.mkdir(exist_ok=True)
        self.config_path = self.dir / "config.json"
        self.config = self._load_config()
        self.threads = {}

    def _load_config(self) -> dict:
        if self.config_path.exists():
            return json.loads(self.config_path.read_text())
        return {"team_name": "default", "members": []}

    def _save_config(self):
        self.config_path.write_text(json.dumps(self.config, indent=2))

    def _find_member(self, name: str) -> dict:
        for m in self.config["members"]:
            if m["name"] == name:
                return m
        return None

    def spawn(self, name: str, role: str, prompt: str) -> str:
        member = self._find_member(name)
        if member:
            if member["status"] not in ("idle", "shutdown"):
                return f"Error: '{name}' is currently {member['status']}"
            member["status"] = "working"
            member["role"] = role
        else:
            member = {"name": name, "role": role, "status": "working"}
            self.config["members"].append(member)
        self._save_config()
        thread = threading.Thread(
            target=self._teammate_loop,
            args=(name, role, prompt),
            daemon=True,
        )
        self.threads[name] = thread
        thread.start()
        return f"Spawned '{name}' (role: {role})"

    def _teammate_loop(self, name: str, role: str, prompt: str):
        sys_prompt = (
            f"You are '{name}', role: {role}, at {WORKDIR}. "
            f"Submit plans via plan_approval before major work. "
            f"Respond to shutdown_request with shutdown_response."
        )
        messages = [{"role": "user", "content": prompt}]
        tools = self._teammate_tools()
        should_exit = False
        for _ in range(50):
            inbox = BUS.read_inbox(name)
            for msg in inbox:
                messages.append({"role": "user", "content": json.dumps(msg)})
            if should_exit:
                break
            try:
                response = client.messages.create(
                    model=MODEL,
                    system=sys_prompt,
                    messages=messages,
                    tools=tools,
                    max_tokens=8000,
                )
            except Exception:
                break
            messages.append({"role": "assistant", "content": response.content})
            if response.stop_reason != "tool_use":
                break
            results = []
            for block in response.content:
                if block.type == "tool_use":
                    output = self._exec(name, block.name, block.input)
                    print(f"  [{name}] {block.name}: {str(output)[:120]}")
                    results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": str(output),
                    })
                    if block.name == "shutdown_response" and block.input.get("approve"):
                        should_exit = True
            messages.append({"role": "user", "content": results})
        member = self._find_member(name)
        if member:
            member["status"] = "shutdown" if should_exit else "idle"
            self._save_config()

    def _exec(self, sender: str, tool_name: str, args: dict) -> str:
        # these base tools are unchanged from s02
        if tool_name == "bash":
            return _run_bash(args["command"])
        if tool_name == "read_file":
            return _run_read(args["path"])
        if tool_name == "write_file":
            return _run_write(args["path"], args["content"])
        if tool_name == "edit_file":
            return _run_edit(args["path"], args["old_text"], args["new_text"])
        if tool_name == "send_message":
            return BUS.send(sender, args["to"], args["content"], args.get("msg_type", "message"))
        if tool_name == "read_inbox":
            return json.dumps(BUS.read_inbox(sender), indent=2)
        if tool_name == "shutdown_response":
            req_id = args["request_id"]
            approve = args["approve"]
            with _tracker_lock:
                if req_id in shutdown_requests:
                    shutdown_requests[req_id]["status"] = "approved" if approve else "rejected"
            BUS.send(
                sender, "lead", args.get("reason", ""),
                "shutdown_response", {"request_id": req_id, "approve": approve},
            )
            return f"Shutdown {'approved' if approve else 'rejected'}"
        if tool_name == "plan_approval":
            plan_text = args.get("plan", "")
            req_id = str(uuid.uuid4())[:8]
            with _tracker_lock:
                plan_requests[req_id] = {"from": sender, "plan": plan_text, "status": "pending"}
            BUS.send(
                sender, "lead", plan_text, "plan_approval_response",
                {"request_id": req_id, "plan": plan_text},
            )
            return f"Plan submitted (request_id={req_id}). Waiting for lead approval."
        return f"Unknown tool: {tool_name}"

    def _teammate_tools(self) -> list:
        # these base tools are unchanged from s02
        return [
            {"name": "bash", "description": "Run a shell command.",
             "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
            {"name": "read_file", "description": "Read file contents.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}},
            {"name": "write_file", "description": "Write content to file.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
            {"name": "edit_file", "description": "Replace exact text in file.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
            {"name": "send_message", "description": "Send message to a teammate.",
             "input_schema": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}},
            {"name": "read_inbox", "description": "Read and drain your inbox.",
             "input_schema": {"type": "object", "properties": {}}},
            {"name": "shutdown_response", "description": "Respond to a shutdown request. Approve to shut down, reject to keep working.",
             "input_schema": {"type": "object", "properties": {"request_id": {"type": "string"}, "approve": {"type": "boolean"}, "reason": {"type": "string"}}, "required": ["request_id", "approve"]}},
            {"name": "plan_approval", "description": "Submit a plan for lead approval. Provide plan text.",
             "input_schema": {"type": "object", "properties": {"plan": {"type": "string"}}, "required": ["plan"]}},
        ]

    def list_all(self) -> str:
        if not self.config["members"]:
            return "No teammates."
        lines = [f"Team: {self.config['team_name']}"]
        for m in self.config["members"]:
            lines.append(f"  {m['name']} ({m['role']}): {m['status']}")
        return "\n".join(lines)

    def member_names(self) -> list:
        return [m["name"] for m in self.config["members"]]


TEAM = TeammateManager(TEAM_DIR)


# -- Base tool implementations (these base tools are unchanged from s02) --
def _safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path


def _run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(
            command, shell=True, cwd=WORKDIR,
            capture_output=True, text=True, timeout=120,
        )
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"


def _run_read(path: str, limit: int = None) -> str:
    try:
        lines = _safe_path(path).read_text().splitlines()
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"


def _run_write(path: str, content: str) -> str:
    try:
        fp = _safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"Wrote {len(content)} bytes"
    except Exception as e:
        return f"Error: {e}"


def _run_edit(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = _safe_path(path)
        c = fp.read_text()
        if old_text not in c:
            return f"Error: Text not found in {path}"
        fp.write_text(c.replace(old_text, new_text, 1))
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"


# -- Lead-specific protocol handlers --
def handle_shutdown_request(teammate: str) -> str:
    req_id = str(uuid.uuid4())[:8]
    with _tracker_lock:
        shutdown_requests[req_id] = {"target": teammate, "status": "pending"}
    BUS.send(
        "lead", teammate, "Please shut down gracefully.",
        "shutdown_request", {"request_id": req_id},
    )
    return f"Shutdown request {req_id} sent to '{teammate}' (status: pending)"


def handle_plan_review(request_id: str, approve: bool, feedback: str = "") -> str:
    with _tracker_lock:
        req = plan_requests.get(request_id)
    if not req:
        return f"Error: Unknown plan request_id '{request_id}'"
    with _tracker_lock:
        req["status"] = "approved" if approve else "rejected"
    BUS.send(
        "lead", req["from"], feedback, "plan_approval_response",
        {"request_id": request_id, "approve": approve, "feedback": feedback},
    )
    return f"Plan {req['status']} for '{req['from']}'"


def _check_shutdown_status(request_id: str) -> str:
    with _tracker_lock:
        return json.dumps(shutdown_requests.get(request_id, {"error": "not found"}))


# -- Lead tool dispatch (12 tools) --
TOOL_HANDLERS = {
    "bash":              lambda **kw: _run_bash(kw["command"]),
    "read_file":         lambda **kw: _run_read(kw["path"], kw.get("limit")),
    "write_file":        lambda **kw: _run_write(kw["path"], kw["content"]),
    "edit_file":         lambda **kw: _run_edit(kw["path"], kw["old_text"], kw["new_text"]),
    "spawn_teammate":    lambda **kw: TEAM.spawn(kw["name"], kw["role"], kw["prompt"]),
    "list_teammates":    lambda **kw: TEAM.list_all(),
    "send_message":      lambda **kw: BUS.send("lead", kw["to"], kw["content"], kw.get("msg_type", "message")),
    "read_inbox":        lambda **kw: json.dumps(BUS.read_inbox("lead"), indent=2),
    "broadcast":         lambda **kw: BUS.broadcast("lead", kw["content"], TEAM.member_names()),
    "shutdown_request":  lambda **kw: handle_shutdown_request(kw["teammate"]),
    "shutdown_response": lambda **kw: _check_shutdown_status(kw.get("request_id", "")),
    "plan_approval":     lambda **kw: handle_plan_review(kw["request_id"], kw["approve"], kw.get("feedback", "")),
}

# these base tools are unchanged from s02
TOOLS = [
    {"name": "bash", "description": "Run a shell command.",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "read_file", "description": "Read file contents.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
    {"name": "write_file", "description": "Write content to file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
    {"name": "edit_file", "description": "Replace exact text in file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
    {"name": "spawn_teammate", "description": "Spawn a persistent teammate.",
     "input_schema": {"type": "object", "properties": {"name": {"type": "string"}, "role": {"type": "string"}, "prompt": {"type": "string"}}, "required": ["name", "role", "prompt"]}},
    {"name": "list_teammates", "description": "List all teammates.",
     "input_schema": {"type": "object", "properties": {}}},
    {"name": "send_message", "description": "Send a message to a teammate.",
     "input_schema": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}},
    {"name": "read_inbox", "description": "Read and drain the lead's inbox.",
     "input_schema": {"type": "object", "properties": {}}},
    {"name": "broadcast", "description": "Send a message to all teammates.",
     "input_schema": {"type": "object", "properties": {"content": {"type": "string"}}, "required": ["content"]}},
    {"name": "shutdown_request", "description": "Request a teammate to shut down gracefully. Returns a request_id for tracking.",
     "input_schema": {"type": "object", "properties": {"teammate": {"type": "string"}}, "required": ["teammate"]}},
    {"name": "shutdown_response", "description": "Check the status of a shutdown request by request_id.",
     "input_schema": {"type": "object", "properties": {"request_id": {"type": "string"}}, "required": ["request_id"]}},
    {"name": "plan_approval", "description": "Approve or reject a teammate's plan. Provide request_id + approve + optional feedback.",
     "input_schema": {"type": "object", "properties": {"request_id": {"type": "string"}, "approve": {"type": "boolean"}, "feedback": {"type": "string"}}, "required": ["request_id", "approve"]}},
]


def agent_loop(messages: list):
    while True:
        inbox = BUS.read_inbox("lead")
        if inbox:
            messages.append({
                "role": "user",
                "content": f"<inbox>{json.dumps(inbox, indent=2)}</inbox>",
            })
            messages.append({
                "role": "assistant",
                "content": "Noted inbox messages.",
            })
        response = client.messages.create(
            model=MODEL,
            system=SYSTEM,
            messages=messages,
            tools=TOOLS,
            max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                try:
                    output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                except Exception as e:
                    output = f"Error: {e}"
                print(f"> {block.name}: {str(output)[:200]}")
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": str(output),
                })
        messages.append({"role": "user", "content": results})


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms10 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        if query.strip() == "/team":
            print(TEAM.list_all())
            continue
        if query.strip() == "/inbox":
            print(json.dumps(BUS.read_inbox("lead"), indent=2))
            continue
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s11_autonomous_agents.py
================================================
#!/usr/bin/env python3
# Harness: autonomy -- models that find work without being told.
"""
s11_autonomous_agents.py - Autonomous Agents

Idle cycle with task board polling, auto-claiming unclaimed tasks, and
identity re-injection after context compression. Builds on s10's protocols.

    Teammate lifecycle:
    +-------+
    | spawn |
    +---+---+
        |
        v
    +-------+  tool_use    +-------+
    | WORK  | <----------- |  LLM  |
    +---+---+              +-------+
        |
        | stop_reason != tool_use
        v
    +--------+
    | IDLE   | poll every 5s for up to 60s
    +---+----+
        |
        +---> check inbox -> message? -> resume WORK
        |
        +---> scan .tasks/ -> unclaimed? -> claim -> resume WORK
        |
        +---> timeout (60s) -> shutdown

    Identity re-injection after compression:
    messages = [identity_block, ...remaining...]
    "You are 'coder', role: backend, team: my-team"

Key insight: "The agent finds work itself."
"""

import json
import os
import subprocess
import threading
import time
import uuid
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)
if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]
TEAM_DIR = WORKDIR / ".team"
INBOX_DIR = TEAM_DIR / "inbox"
TASKS_DIR = WORKDIR / ".tasks"

POLL_INTERVAL = 5
IDLE_TIMEOUT = 60

SYSTEM = f"You are a team lead at {WORKDIR}. Teammates are autonomous -- they find work themselves."

VALID_MSG_TYPES = {
    "message",
    "broadcast",
    "shutdown_request",
    "shutdown_response",
    "plan_approval_response",
}

# -- Request trackers --
shutdown_requests = {}
plan_requests = {}
_tracker_lock = threading.Lock()
_claim_lock = threading.Lock()


# -- MessageBus: JSONL inbox per teammate --
class MessageBus:
    def __init__(self, inbox_dir: Path):
        self.dir = inbox_dir
        self.dir.mkdir(parents=True, exist_ok=True)

    def send(self, sender: str, to: str, content: str,
             msg_type: str = "message", extra: dict = None) -> str:
        if msg_type not in VALID_MSG_TYPES:
            return f"Error: Invalid type '{msg_type}'. Valid: {VALID_MSG_TYPES}"
        msg = {
            "type": msg_type,
            "from": sender,
            "content": content,
            "timestamp": time.time(),
        }
        if extra:
            msg.update(extra)
        inbox_path = self.dir / f"{to}.jsonl"
        with open(inbox_path, "a") as f:
            f.write(json.dumps(msg) + "\n")
        return f"Sent {msg_type} to {to}"

    def read_inbox(self, name: str) -> list:
        inbox_path = self.dir / f"{name}.jsonl"
        if not inbox_path.exists():
            return []
        messages = []
        for line in inbox_path.read_text().strip().splitlines():
            if line:
                messages.append(json.loads(line))
        inbox_path.write_text("")
        return messages

    def broadcast(self, sender: str, content: str, teammates: list) -> str:
        count = 0
        for name in teammates:
            if name != sender:
                self.send(sender, name, content, "broadcast")
                count += 1
        return f"Broadcast to {count} teammates"


BUS = MessageBus(INBOX_DIR)


# -- Task board scanning --
def scan_unclaimed_tasks() -> list:
    TASKS_DIR.mkdir(exist_ok=True)
    unclaimed = []
    for f in sorted(TASKS_DIR.glob("task_*.json")):
        task = json.loads(f.read_text())
        if (task.get("status") == "pending"
                and not task.get("owner")
                and not task.get("blockedBy")):
            unclaimed.append(task)
    return unclaimed


def claim_task(task_id: int, owner: str) -> str:
    with _claim_lock:
        path = TASKS_DIR / f"task_{task_id}.json"
        if not path.exists():
            return f"Error: Task {task_id} not found"
        task = json.loads(path.read_text())
        task["owner"] = owner
        task["status"] = "in_progress"
        path.write_text(json.dumps(task, indent=2))
    return f"Claimed task #{task_id} for {owner}"


# -- Identity re-injection after compression --
def make_identity_block(name: str, role: str, team_name: str) -> dict:
    return {
        "role": "user",
        "content": f"<identity>You are '{name}', role: {role}, team: {team_name}. Continue your work.</identity>",
    }


# -- Autonomous TeammateManager --
class TeammateManager:
    def __init__(self, team_dir: Path):
        self.dir = team_dir
        self.dir.mkdir(exist_ok=True)
        self.config_path = self.dir / "config.json"
        self.config = self._load_config()
        self.threads = {}

    def _load_config(self) -> dict:
        if self.config_path.exists():
            return json.loads(self.config_path.read_text())
        return {"team_name": "default", "members": []}

    def _save_config(self):
        self.config_path.write_text(json.dumps(self.config, indent=2))

    def _find_member(self, name: str) -> dict:
        for m in self.config["members"]:
            if m["name"] == name:
                return m
        return None

    def _set_status(self, name: str, status: str):
        member = self._find_member(name)
        if member:
            member["status"] = status
            self._save_config()

    def spawn(self, name: str, role: str, prompt: str) -> str:
        member = self._find_member(name)
        if member:
            if member["status"] not in ("idle", "shutdown"):
                return f"Error: '{name}' is currently {member['status']}"
            member["status"] = "working"
            member["role"] = role
        else:
            member = {"name": name, "role": role, "status": "working"}
            self.config["members"].append(member)
        self._save_config()
        thread = threading.Thread(
            target=self._loop,
            args=(name, role, prompt),
            daemon=True,
        )
        self.threads[name] = thread
        thread.start()
        return f"Spawned '{name}' (role: {role})"

    def _loop(self, name: str, role: str, prompt: str):
        team_name = self.config["team_name"]
        sys_prompt = (
            f"You are '{name}', role: {role}, team: {team_name}, at {WORKDIR}. "
            f"Use idle tool when you have no more work. You will auto-claim new tasks."
        )
        messages = [{"role": "user", "content": prompt}]
        tools = self._teammate_tools()

        while True:
            # -- WORK PHASE: standard agent loop --
            for _ in range(50):
                inbox = BUS.read_inbox(name)
                for msg in inbox:
                    if msg.get("type") == "shutdown_request":
                        self._set_status(name, "shutdown")
                        return
                    messages.append({"role": "user", "content": json.dumps(msg)})
                try:
                    response = client.messages.create(
                        model=MODEL,
                        system=sys_prompt,
                        messages=messages,
                        tools=tools,
                        max_tokens=8000,
                    )
                except Exception:
                    self._set_status(name, "idle")
                    return
                messages.append({"role": "assistant", "content": response.content})
                if response.stop_reason != "tool_use":
                    break
                results = []
                idle_requested = False
                for block in response.content:
                    if block.type == "tool_use":
                        if block.name == "idle":
                            idle_requested = True
                            output = "Entering idle phase. Will poll for new tasks."
                        else:
                            output = self._exec(name, block.name, block.input)
                        print(f"  [{name}] {block.name}: {str(output)[:120]}")
                        results.append({
                            "type": "tool_result",
                            "tool_use_id": block.id,
                            "content": str(output),
                        })
                messages.append({"role": "user", "content": results})
                if idle_requested:
                    break

            # -- IDLE PHASE: poll for inbox messages and unclaimed tasks --
            self._set_status(name, "idle")
            resume = False
            polls = IDLE_TIMEOUT // max(POLL_INTERVAL, 1)
            for _ in range(polls):
                time.sleep(POLL_INTERVAL)
                inbox = BUS.read_inbox(name)
                if inbox:
                    for msg in inbox:
                        if msg.get("type") == "shutdown_request":
                            self._set_status(name, "shutdown")
                            return
                        messages.append({"role": "user", "content": json.dumps(msg)})
                    resume = True
                    break
                unclaimed = scan_unclaimed_tasks()
                if unclaimed:
                    task = unclaimed[0]
                    claim_task(task["id"], name)
                    task_prompt = (
                        f"<auto-claimed>Task #{task['id']}: {task['subject']}\n"
                        f"{task.get('description', '')}</auto-claimed>"
                    )
                    if len(messages) <= 3:
                        messages.insert(0, make_identity_block(name, role, team_name))
                        messages.insert(1, {"role": "assistant", "content": f"I am {name}. Continuing."})
                    messages.append({"role": "user", "content": task_prompt})
                    messages.append({"role": "assistant", "content": f"Claimed task #{task['id']}. Working on it."})
                    resume = True
                    break

            if not resume:
                self._set_status(name, "shutdown")
                return
            self._set_status(name, "working")

    def _exec(self, sender: str, tool_name: str, args: dict) -> str:
        # these base tools are unchanged from s02
        if tool_name == "bash":
            return _run_bash(args["command"])
        if tool_name == "read_file":
            return _run_read(args["path"])
        if tool_name == "write_file":
            return _run_write(args["path"], args["content"])
        if tool_name == "edit_file":
            return _run_edit(args["path"], args["old_text"], args["new_text"])
        if tool_name == "send_message":
            return BUS.send(sender, args["to"], args["content"], args.get("msg_type", "message"))
        if tool_name == "read_inbox":
            return json.dumps(BUS.read_inbox(sender), indent=2)
        if tool_name == "shutdown_response":
            req_id = args["request_id"]
            with _tracker_lock:
                if req_id in shutdown_requests:
                    shutdown_requests[req_id]["status"] = "approved" if args["approve"] else "rejected"
            BUS.send(
                sender, "lead", args.get("reason", ""),
                "shutdown_response", {"request_id": req_id, "approve": args["approve"]},
            )
            return f"Shutdown {'approved' if args['approve'] else 'rejected'}"
        if tool_name == "plan_approval":
            plan_text = args.get("plan", "")
            req_id = str(uuid.uuid4())[:8]
            with _tracker_lock:
                plan_requests[req_id] = {"from": sender, "plan": plan_text, "status": "pending"}
            BUS.send(
                sender, "lead", plan_text, "plan_approval_response",
                {"request_id": req_id, "plan": plan_text},
            )
            return f"Plan submitted (request_id={req_id}). Waiting for approval."
        if tool_name == "claim_task":
            return claim_task(args["task_id"], sender)
        return f"Unknown tool: {tool_name}"

    def _teammate_tools(self) -> list:
        # these base tools are unchanged from s02
        return [
            {"name": "bash", "description": "Run a shell command.",
             "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
            {"name": "read_file", "description": "Read file contents.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}},
            {"name": "write_file", "description": "Write content to file.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
            {"name": "edit_file", "description": "Replace exact text in file.",
             "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
            {"name": "send_message", "description": "Send message to a teammate.",
             "input_schema": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}},
            {"name": "read_inbox", "description": "Read and drain your inbox.",
             "input_schema": {"type": "object", "properties": {}}},
            {"name": "shutdown_response", "description": "Respond to a shutdown request.",
             "input_schema": {"type": "object", "properties": {"request_id": {"type": "string"}, "approve": {"type": "boolean"}, "reason": {"type": "string"}}, "required": ["request_id", "approve"]}},
            {"name": "plan_approval", "description": "Submit a plan for lead approval.",
             "input_schema": {"type": "object", "properties": {"plan": {"type": "string"}}, "required": ["plan"]}},
            {"name": "idle", "description": "Signal that you have no more work. Enters idle polling phase.",
             "input_schema": {"type": "object", "properties": {}}},
            {"name": "claim_task", "description": "Claim a task from the task board by ID.",
             "input_schema": {"type": "object", "properties": {"task_id": {"type": "integer"}}, "required": ["task_id"]}},
        ]

    def list_all(self) -> str:
        if not self.config["members"]:
            return "No teammates."
        lines = [f"Team: {self.config['team_name']}"]
        for m in self.config["members"]:
            lines.append(f"  {m['name']} ({m['role']}): {m['status']}")
        return "\n".join(lines)

    def member_names(self) -> list:
        return [m["name"] for m in self.config["members"]]


TEAM = TeammateManager(TEAM_DIR)


# -- Base tool implementations (these base tools are unchanged from s02) --
def _safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path


def _run_bash(command: str) -> str:
    dangerous = ["rm -rf /", "sudo", "shutdown", "reboot"]
    if any(d in command for d in dangerous):
        return "Error: Dangerous command blocked"
    try:
        r = subprocess.run(
            command, shell=True, cwd=WORKDIR,
            capture_output=True, text=True, timeout=120,
        )
        out = (r.stdout + r.stderr).strip()
        return out[:50000] if out else "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Timeout (120s)"


def _run_read(path: str, limit: int = None) -> str:
    try:
        lines = _safe_path(path).read_text().splitlines()
        if limit and limit < len(lines):
            lines = lines[:limit] + [f"... ({len(lines) - limit} more)"]
        return "\n".join(lines)[:50000]
    except Exception as e:
        return f"Error: {e}"


def _run_write(path: str, content: str) -> str:
    try:
        fp = _safe_path(path)
        fp.parent.mkdir(parents=True, exist_ok=True)
        fp.write_text(content)
        return f"Wrote {len(content)} bytes"
    except Exception as e:
        return f"Error: {e}"


def _run_edit(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = _safe_path(path)
        c = fp.read_text()
        if old_text not in c:
            return f"Error: Text not found in {path}"
        fp.write_text(c.replace(old_text, new_text, 1))
        return f"Edited {path}"
    except Exception as e:
        return f"Error: {e}"


# -- Lead-specific protocol handlers --
def handle_shutdown_request(teammate: str) -> str:
    req_id = str(uuid.uuid4())[:8]
    with _tracker_lock:
        shutdown_requests[req_id] = {"target": teammate, "status": "pending"}
    BUS.send(
        "lead", teammate, "Please shut down gracefully.",
        "shutdown_request", {"request_id": req_id},
    )
    return f"Shutdown request {req_id} sent to '{teammate}'"


def handle_plan_review(request_id: str, approve: bool, feedback: str = "") -> str:
    with _tracker_lock:
        req = plan_requests.get(request_id)
    if not req:
        return f"Error: Unknown plan request_id '{request_id}'"
    with _tracker_lock:
        req["status"] = "approved" if approve else "rejected"
    BUS.send(
        "lead", req["from"], feedback, "plan_approval_response",
        {"request_id": request_id, "approve": approve, "feedback": feedback},
    )
    return f"Plan {req['status']} for '{req['from']}'"


def _check_shutdown_status(request_id: str) -> str:
    with _tracker_lock:
        return json.dumps(shutdown_requests.get(request_id, {"error": "not found"}))


# -- Lead tool dispatch (14 tools) --
TOOL_HANDLERS = {
    "bash":              lambda **kw: _run_bash(kw["command"]),
    "read_file":         lambda **kw: _run_read(kw["path"], kw.get("limit")),
    "write_file":        lambda **kw: _run_write(kw["path"], kw["content"]),
    "edit_file":         lambda **kw: _run_edit(kw["path"], kw["old_text"], kw["new_text"]),
    "spawn_teammate":    lambda **kw: TEAM.spawn(kw["name"], kw["role"], kw["prompt"]),
    "list_teammates":    lambda **kw: TEAM.list_all(),
    "send_message":      lambda **kw: BUS.send("lead", kw["to"], kw["content"], kw.get("msg_type", "message")),
    "read_inbox":        lambda **kw: json.dumps(BUS.read_inbox("lead"), indent=2),
    "broadcast":         lambda **kw: BUS.broadcast("lead", kw["content"], TEAM.member_names()),
    "shutdown_request":  lambda **kw: handle_shutdown_request(kw["teammate"]),
    "shutdown_response": lambda **kw: _check_shutdown_status(kw.get("request_id", "")),
    "plan_approval":     lambda **kw: handle_plan_review(kw["request_id"], kw["approve"], kw.get("feedback", "")),
    "idle":              lambda **kw: "Lead does not idle.",
    "claim_task":        lambda **kw: claim_task(kw["task_id"], "lead"),
}

# these base tools are unchanged from s02
TOOLS = [
    {"name": "bash", "description": "Run a shell command.",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
    {"name": "read_file", "description": "Read file contents.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
    {"name": "write_file", "description": "Write content to file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
    {"name": "edit_file", "description": "Replace exact text in file.",
     "input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
    {"name": "spawn_teammate", "description": "Spawn an autonomous teammate.",
     "input_schema": {"type": "object", "properties": {"name": {"type": "string"}, "role": {"type": "string"}, "prompt": {"type": "string"}}, "required": ["name", "role", "prompt"]}},
    {"name": "list_teammates", "description": "List all teammates.",
     "input_schema": {"type": "object", "properties": {}}},
    {"name": "send_message", "description": "Send a message to a teammate.",
     "input_schema": {"type": "object", "properties": {"to": {"type": "string"}, "content": {"type": "string"}, "msg_type": {"type": "string", "enum": list(VALID_MSG_TYPES)}}, "required": ["to", "content"]}},
    {"name": "read_inbox", "description": "Read and drain the lead's inbox.",
     "input_schema": {"type": "object", "properties": {}}},
    {"name": "broadcast", "description": "Send a message to all teammates.",
     "input_schema": {"type": "object", "properties": {"content": {"type": "string"}}, "required": ["content"]}},
    {"name": "shutdown_request", "description": "Request a teammate to shut down.",
     "input_schema": {"type": "object", "properties": {"teammate": {"type": "string"}}, "required": ["teammate"]}},
    {"name": "shutdown_response", "description": "Check shutdown request status.",
     "input_schema": {"type": "object", "properties": {"request_id": {"type": "string"}}, "required": ["request_id"]}},
    {"name": "plan_approval", "description": "Approve or reject a teammate's plan.",
     "input_schema": {"type": "object", "properties": {"request_id": {"type": "string"}, "approve": {"type": "boolean"}, "feedback": {"type": "string"}}, "required": ["request_id", "approve"]}},
    {"name": "idle", "description": "Enter idle state (for lead -- rarely used).",
     "input_schema": {"type": "object", "properties": {}}},
    {"name": "claim_task", "description": "Claim a task from the board by ID.",
     "input_schema": {"type": "object", "properties": {"task_id": {"type": "integer"}}, "required": ["task_id"]}},
]


def agent_loop(messages: list):
    while True:
        inbox = BUS.read_inbox("lead")
        if inbox:
            messages.append({
                "role": "user",
                "content": f"<inbox>{json.dumps(inbox, indent=2)}</inbox>",
            })
            messages.append({
                "role": "assistant",
                "content": "Noted inbox messages.",
            })
        response = client.messages.create(
            model=MODEL,
            system=SYSTEM,
            messages=messages,
            tools=TOOLS,
            max_tokens=8000,
        )
        messages.append({"role": "assistant", "content": response.content})
        if response.stop_reason != "tool_use":
            return
        results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                try:
                    output = handler(**block.input) if handler else f"Unknown tool: {block.name}"
                except Exception as e:
                    output = f"Error: {e}"
                print(f"> {block.name}: {str(output)[:200]}")
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": str(output),
                })
        messages.append({"role": "user", "content": results})


if __name__ == "__main__":
    history = []
    while True:
        try:
            query = input("\033[36ms11 >> \033[0m")
        except (EOFError, KeyboardInterrupt):
            break
        if query.strip().lower() in ("q", "exit", ""):
            break
        if query.strip() == "/team":
            print(TEAM.list_all())
            continue
        if query.strip() == "/inbox":
            print(json.dumps(BUS.read_inbox("lead"), indent=2))
            continue
        if query.strip() == "/tasks":
            TASKS_DIR.mkdir(exist_ok=True)
            for f in sorted(TASKS_DIR.glob("task_*.json")):
                t = json.loads(f.read_text())
                marker = {"pending": "[ ]", "in_progress": "[>]", "completed": "[x]"}.get(t["status"], "[?]")
                owner = f" @{t['owner']}" if t.get("owner") else ""
                print(f"  {marker} #{t['id']}: {t['subject']}{owner}")
            continue
        history.append({"role": "user", "content": query})
        agent_loop(history)
        response_content = history[-1]["content"]
        if isinstance(response_content, list):
            for block in response_content:
                if hasattr(block, "text"):
                    print(block.text)
        print()


================================================
FILE: agents/s12_worktree_task_isolation.py
================================================
#!/usr/bin/env python3
# Harness: directory isolation -- parallel execution lanes that never collide.
"""
s12_worktree_task_isolation.py - Worktree + Task Isolation

Directory-level isolation for parallel task execution.
Tasks are the control plane and worktrees are the execution plane.

    .tasks/task_12.json
      {
        "id": 12,
        "subject": "Implement auth refactor",
        "status": "in_progress",
        "worktree": "auth-refactor"
      }

    .worktrees/index.json
      {
        "worktrees": [
          {
            "name": "auth-refactor",
            "path": ".../.worktrees/auth-refactor",
            "branch": "wt/auth-refactor",
            "task_id": 12,
            "status": "active"
          }
        ]
      }

Key insight: "Isolate by directory, coordinate by task ID."
"""

import json
import os
import re
import subprocess
import time
from pathlib import Path

from anthropic import Anthropic
from dotenv import load_dotenv

load_dotenv(override=True)

if os.getenv("ANTHROPIC_BASE_URL"):
    os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)

WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]


def detect_repo_root(cwd: Path) -> Path | None:
    """Return git repo root if cwd is inside a repo, else None."""
    try:
        r = subprocess.run(
            ["git", "rev-parse", "--show-toplevel"],
            cwd=cwd,
            capture_output=True,
            text=True,
            timeout=10,
        )
        if r.returncode != 0:
            return None
        root = Path(r.stdout.strip())
        return root if root.exists() else None
    except Exception:
        return None


REPO_ROOT = detect_repo_root(WORKDIR) or WORKDIR

SYSTEM = (
    f"You are a coding agent at {WORKDIR}. "
    "Use task + worktree tools for multi-task work. "
    "For parallel or risky changes: create tasks, allocate worktree lanes, "
    "run commands in those lanes, then choose keep/remove for closeout. "
    "Use worktree_events when you need lifecycle visibility."
)


# -- EventBus: append-only lifecycle events for observability --
class EventBus:
    def __init__(self, event_log_path: Path):
        self.path = event_log_path
        self.path.parent.mkdir(parents=True, exist_ok=True)
        if not self.path.exists():
            self.path.write_text("")

    def emit(
        self,
        event: str,
        task: dict | None = None,
        worktree: dict | None = None,
        error: str | None = None,
    ):
        payload = {
            "event": event,
            "ts": time.time(),
            "task": task or {},
            "worktree": worktree or {},
        }
        if error:
            payload["error"] = error
        with self.path.open("a", encoding="utf-8") as f:
            f.write(json.dumps(payload) + "\n")

    def list_recent(self, limit: int = 20) -> str:
        n = max(1, min(int(limit or 20), 200))
        lines = self.path.read_text(encoding="utf-8").splitlines()
        recent = lines[-n:]
        items = []
        for line in recent:
            try:
                items.append(json.loads(line))
            except Exception:
                items.append({"event": "parse_error", "raw": line})
        return json.dumps(items, indent=2)


# -- TaskManager: persistent task board with optional worktree binding --
class TaskManager:
    def __init__(self, tasks_dir: Path):
        self.dir = tasks_dir
        self.dir.mkdir(parents=True, exist_ok=True)
        self._next_id = self._max_id() + 1

    def _max_id(self) -> int:
        ids = []
        for f in self.dir.glob("task_*.json"):
            try:
                ids.append(int(f.stem.split("_")[1]))
            except Exception:
                pass
        return max(ids) if ids else 0

    def _path(self, task_id: int) -> Path:
        return self.dir / f"task_{task_id}.json"

    def _load(self, task_id: int) -> dict:
        path = self._path(task_id)
        if not path.exists():
            raise ValueError(f"Task {task_id} not found")
        return json.loads(path.read_text())

    def _save(self, task: dict):
        self._path(task["id"]).write_text(json.dumps(task, indent=2))

    def create(self, subject: str, description: str = "") -> str:
        task = {
            "id": self._next_id,
            "subject": subject,
            "description": description,
            "status": "pending",
            "owner": "",
            "worktree": "",
            "blockedBy": [],
            "created_at": time.time(),
            "updated_at": time.time(),
        }
        self._save(task)
        self._next_id += 1
        return json.dumps(task, indent=2)

    def get(self, task_id: int) -> str:
        return json.dumps(self._load(task_id), indent=2)

    def exists(self, task_id: int) -> bool:
        return self._path(task_id).exists()

    def update(self, task_id: int, status: str = None, owner: str = None) -> str:
        task = self._load(task_id)
        if status:
            if status not in ("pending", "in_progress", "completed"):
                raise ValueError(f"Invalid status: {status}")
            task["status"] = status
        if owner is not None:
            task["owner"] = owner
        task["updated_at"] = time.time()
        self._save(task)
        return json.dumps(task, indent=2)

    def bind_worktree(self, task_id: int, worktree: str, owner: str = "") -> str:
        task = self._load(task_id)
        task["worktree"] = worktree
        if owner:
            task["owner"] = owner
        if task["status"] == "pending":
            task["status"] = "in_progress"
        task["updated_at"] = time.time()
        self._save(task)
        return json.dumps(task, indent=2)

    def unbind_worktree(self, task_id: int) -> str:
        task = self._load(task_id)
        task["worktree"] = ""
        task["updated_at"] = time.time()
        self._save(task)
        return json.dumps(task, indent=2)

    def list_all(self) -> str:
        tasks = []
        for f in sorted(self.dir.glob("task_*.json")):
            tasks.append(json.loads(f.read_text()))
        if not tasks:
            return "No tasks."
        lines = []
        for t in tasks:
            marker = {
                "pending": "[ ]",
                "in_progress": "[>]",
                "completed": "[x]",
            }.get(t["status"], "[?]")
            owner = f" owner={t['owner']}" if t.get("owner") else ""
            wt = f" wt={t['worktree']}" if t.get("worktree") else ""
            lines.append(f"{marker} #{t['id']}: {t['subject']}{owner}{wt}")
        return "\n".join(lines)


TASKS = TaskManager(REPO_ROOT / ".tasks")
EVENTS = EventBus(REPO_ROOT / ".worktrees" / "events.jsonl")


# -- WorktreeManager: create/list/run/remove git worktrees + lifecycle index --
class WorktreeManager:
    def __init__(self, repo_root: Path, tasks: TaskManager, events: EventBus):
        self.repo_root = repo_root
        self.tasks = tasks
        self.events = events
        self.dir = repo_root / ".worktrees"
        self.dir.mkdir(parents=True, exist_ok=True)
        self.index_path = self.dir / "index.json"
        if not self.index_path.exists():
            self.index_path.write_text(json.dumps({"worktrees": []}, indent=2))
        self.git_available = self._is_git_repo()

    def _is_git_repo(self) -> bool:
        try:
            r = subprocess.run(
                ["git", "rev-parse", "--is-inside-work-tree"],
                cwd=self.repo_root,
                capture_output=True,
                text=True,
                timeout=10,
            )
            return r.returncode == 0
        except Exception:
            return False

    def _run_git(self, args: list[str]) -> str:
        if not self.git_available:
            raise RuntimeError("Not in a git repository. worktree tools require git.")
        r = subprocess.run(
            ["git", *args],
            cwd=self.repo_root,
            capture_output=True,
            text=True,
            timeout=120,
        )
        if r.returncode != 0:
            msg = (r.stdout + r.stderr).strip()
            raise RuntimeError(msg or f"git {' '.join(args)} failed")
        return (r.stdout + r.stderr).strip() or "(no output)"

    def _load_index(self) -> dict:
        return json.loads(self.index_path.read_text())

    def _save_index(self, data: dict):
        self.index_path.write_text(json.dumps(data, indent=2))

    def _find(self, name: str) -> dict | None:
        idx = self._load_index()
        for wt in idx.get("worktrees", []):
            if wt.get("name") == name:
                return wt
        return None

    def _validate_name(self, name: str):
        if not re.fullmatch(r"[A-Za-z0-9._-]{1,40}", name or ""):
            raise ValueError(
                "Invalid worktree name. Use 1-40 chars: letters, numbers, ., _, -"
            )

    def create(self, name: str, task_id: int = None, base_ref: str = "HEAD") -> str:
        self._validate_name(name)
        if self._find(name):
            raise ValueError(f"Worktree '{name}' already exists in index")
        if task_id is not None and not self.tasks.exists(task_id):
            raise ValueError(f"Task {task_id} not found")

        path = self.dir / name
        branch = f"wt/{name}"
        self.events.emit(
            "worktree.create.before",
            task={"id": task_id} if task_id is not None else {},
            worktree={"name": name, "base_ref": base_ref},
        )
        try:
            self._run_git(["worktree", "add", "-b", branch, str(path), base_ref])

            entry = {
                "name": name,
                "path": str(path),
                "branch": branch,
                "task_id": task_id,
                "status": "active",
                "created_at": time.time(),
            }

            idx = self._load_index()
            idx["worktrees"].append(entry)
            self._save_index(idx)

            if task_id is not None:
                self.tasks.bind_worktree(task_id, name)

            self.events.emit(
                "worktree.create.after",
                task={"id": task_id} if task_id is not None else {},
                worktree={
              
Download .txt
gitextract_p2krhmdp/

├── .github/
│   └── workflows/
│       ├── ci.yml
│       └── test.yml
├── .gitignore
├── LICENSE
├── README-ja.md
├── README-zh.md
├── README.md
├── agents/
│   ├── __init__.py
│   ├── s01_agent_loop.py
│   ├── s02_tool_use.py
│   ├── s03_todo_write.py
│   ├── s04_subagent.py
│   ├── s05_skill_loading.py
│   ├── s06_context_compact.py
│   ├── s07_task_system.py
│   ├── s08_background_tasks.py
│   ├── s09_agent_teams.py
│   ├── s10_team_protocols.py
│   ├── s11_autonomous_agents.py
│   ├── s12_worktree_task_isolation.py
│   └── s_full.py
├── docs/
│   ├── en/
│   │   ├── s01-the-agent-loop.md
│   │   ├── s02-tool-use.md
│   │   ├── s03-todo-write.md
│   │   ├── s04-subagent.md
│   │   ├── s05-skill-loading.md
│   │   ├── s06-context-compact.md
│   │   ├── s07-task-system.md
│   │   ├── s08-background-tasks.md
│   │   ├── s09-agent-teams.md
│   │   ├── s10-team-protocols.md
│   │   ├── s11-autonomous-agents.md
│   │   └── s12-worktree-task-isolation.md
│   ├── ja/
│   │   ├── s01-the-agent-loop.md
│   │   ├── s02-tool-use.md
│   │   ├── s03-todo-write.md
│   │   ├── s04-subagent.md
│   │   ├── s05-skill-loading.md
│   │   ├── s06-context-compact.md
│   │   ├── s07-task-system.md
│   │   ├── s08-background-tasks.md
│   │   ├── s09-agent-teams.md
│   │   ├── s10-team-protocols.md
│   │   ├── s11-autonomous-agents.md
│   │   └── s12-worktree-task-isolation.md
│   └── zh/
│       ├── s01-the-agent-loop.md
│       ├── s02-tool-use.md
│       ├── s03-todo-write.md
│       ├── s04-subagent.md
│       ├── s05-skill-loading.md
│       ├── s06-context-compact.md
│       ├── s07-task-system.md
│       ├── s08-background-tasks.md
│       ├── s09-agent-teams.md
│       ├── s10-team-protocols.md
│       ├── s11-autonomous-agents.md
│       └── s12-worktree-task-isolation.md
├── requirements.txt
├── skills/
│   ├── agent-builder/
│   │   ├── SKILL.md
│   │   ├── references/
│   │   │   ├── agent-philosophy.md
│   │   │   ├── minimal-agent.py
│   │   │   ├── subagent-pattern.py
│   │   │   └── tool-templates.py
│   │   └── scripts/
│   │       └── init_agent.py
│   ├── code-review/
│   │   └── SKILL.md
│   ├── mcp-builder/
│   │   └── SKILL.md
│   └── pdf/
│       └── SKILL.md
└── web/
    ├── .gitignore
    ├── README.md
    ├── next.config.ts
    ├── package.json
    ├── postcss.config.mjs
    ├── scripts/
    │   └── extract-content.ts
    ├── src/
    │   ├── app/
    │   │   ├── [locale]/
    │   │   │   ├── (learn)/
    │   │   │   │   ├── [version]/
    │   │   │   │   │   ├── client.tsx
    │   │   │   │   │   ├── diff/
    │   │   │   │   │   │   ├── diff-content.tsx
    │   │   │   │   │   │   └── page.tsx
    │   │   │   │   │   └── page.tsx
    │   │   │   │   ├── compare/
    │   │   │   │   │   └── page.tsx
    │   │   │   │   ├── layers/
    │   │   │   │   │   └── page.tsx
    │   │   │   │   ├── layout.tsx
    │   │   │   │   └── timeline/
    │   │   │   │       └── page.tsx
    │   │   │   ├── layout.tsx
    │   │   │   └── page.tsx
    │   │   ├── globals.css
    │   │   └── page.tsx
    │   ├── components/
    │   │   ├── architecture/
    │   │   │   ├── arch-diagram.tsx
    │   │   │   ├── design-decisions.tsx
    │   │   │   ├── execution-flow.tsx
    │   │   │   └── message-flow.tsx
    │   │   ├── code/
    │   │   │   └── source-viewer.tsx
    │   │   ├── diff/
    │   │   │   ├── code-diff.tsx
    │   │   │   └── whats-new.tsx
    │   │   ├── docs/
    │   │   │   └── doc-renderer.tsx
    │   │   ├── layout/
    │   │   │   ├── header.tsx
    │   │   │   └── sidebar.tsx
    │   │   ├── simulator/
    │   │   │   ├── agent-loop-simulator.tsx
    │   │   │   ├── simulator-controls.tsx
    │   │   │   └── simulator-message.tsx
    │   │   ├── timeline/
    │   │   │   └── timeline.tsx
    │   │   ├── ui/
    │   │   │   ├── badge.tsx
    │   │   │   ├── card.tsx
    │   │   │   └── tabs.tsx
    │   │   └── visualizations/
    │   │       ├── index.tsx
    │   │       ├── s01-agent-loop.tsx
    │   │       ├── s02-tool-dispatch.tsx
    │   │       ├── s03-todo-write.tsx
    │   │       ├── s04-subagent.tsx
    │   │       ├── s05-skill-loading.tsx
    │   │       ├── s06-context-compact.tsx
    │   │       ├── s07-task-system.tsx
    │   │       ├── s08-background-tasks.tsx
    │   │       ├── s09-agent-teams.tsx
    │   │       ├── s10-team-protocols.tsx
    │   │       ├── s11-autonomous-agents.tsx
    │   │       ├── s12-worktree-task-isolation.tsx
    │   │       └── shared/
    │   │           └── step-controls.tsx
    │   ├── data/
    │   │   ├── annotations/
    │   │   │   ├── s01.json
    │   │   │   ├── s02.json
    │   │   │   ├── s03.json
    │   │   │   ├── s04.json
    │   │   │   ├── s05.json
    │   │   │   ├── s06.json
    │   │   │   ├── s07.json
    │   │   │   ├── s08.json
    │   │   │   ├── s09.json
    │   │   │   ├── s10.json
    │   │   │   ├── s11.json
    │   │   │   └── s12.json
    │   │   ├── execution-flows.ts
    │   │   ├── generated/
    │   │   │   ├── docs.json
    │   │   │   └── versions.json
    │   │   └── scenarios/
    │   │       ├── s01.json
    │   │       ├── s02.json
    │   │       ├── s03.json
    │   │       ├── s04.json
    │   │       ├── s05.json
    │   │       ├── s06.json
    │   │       ├── s07.json
    │   │       ├── s08.json
    │   │       ├── s09.json
    │   │       ├── s10.json
    │   │       ├── s11.json
    │   │       └── s12.json
    │   ├── hooks/
    │   │   ├── useDarkMode.ts
    │   │   ├── useSimulator.ts
    │   │   └── useSteppedVisualization.ts
    │   ├── i18n/
    │   │   └── messages/
    │   │       ├── en.json
    │   │       ├── ja.json
    │   │       └── zh.json
    │   ├── lib/
    │   │   ├── constants.ts
    │   │   ├── i18n-server.ts
    │   │   ├── i18n.tsx
    │   │   └── utils.ts
    │   └── types/
    │       └── agent-data.ts
    ├── tsconfig.json
    └── vercel.json
Download .txt
SYMBOL INDEX (556 symbols across 69 files)

FILE: agents/s01_agent_loop.py
  function run_bash (line 54) | def run_bash(command: str) -> str:
  function agent_loop (line 68) | def agent_loop(messages: list):

FILE: agents/s02_tool_use.py
  function safe_path (line 41) | def safe_path(p: str) -> Path:
  function run_bash (line 48) | def run_bash(command: str) -> str:
  function run_read (line 61) | def run_read(path: str, limit: int = None) -> str:
  function run_write (line 72) | def run_write(path: str, content: str) -> str:
  function run_edit (line 82) | def run_edit(path: str, old_text: str, new_text: str) -> str:
  function agent_loop (line 114) | def agent_loop(messages: list):

FILE: agents/s03_todo_write.py
  class TodoManager (line 52) | class TodoManager:
    method __init__ (line 53) | def __init__(self):
    method update (line 56) | def update(self, items: list) -> str:
    method render (line 77) | def render(self) -> str:
  function safe_path (line 93) | def safe_path(p: str) -> Path:
  function run_bash (line 99) | def run_bash(command: str) -> str:
  function run_read (line 111) | def run_read(path: str, limit: int = None) -> str:
  function run_write (line 120) | def run_write(path: str, content: str) -> str:
  function run_edit (line 129) | def run_edit(path: str, old_text: str, new_text: str) -> str:
  function agent_loop (line 164) | def agent_loop(messages: list):

FILE: agents/s04_subagent.py
  function safe_path (line 47) | def safe_path(p: str) -> Path:
  function run_bash (line 53) | def run_bash(command: str) -> str:
  function run_read (line 65) | def run_read(path: str, limit: int = None) -> str:
  function run_write (line 74) | def run_write(path: str, content: str) -> str:
  function run_edit (line 83) | def run_edit(path: str, old_text: str, new_text: str) -> str:
  function run_subagent (line 116) | def run_subagent(prompt: str) -> str:
  function agent_loop (line 144) | def agent_loop(messages: list):

FILE: agents/s05_skill_loading.py
  class SkillLoader (line 58) | class SkillLoader:
    method __init__ (line 59) | def __init__(self, skills_dir: Path):
    method _load_all (line 64) | def _load_all(self):
    method _parse_frontmatter (line 73) | def _parse_frontmatter(self, text: str) -> tuple:
    method get_descriptions (line 85) | def get_descriptions(self) -> str:
    method get_content (line 99) | def get_content(self, name: str) -> str:
  function safe_path (line 118) | def safe_path(p: str) -> Path:
  function run_bash (line 124) | def run_bash(command: str) -> str:
  function run_read (line 136) | def run_read(path: str, limit: int = None) -> str:
  function run_write (line 145) | def run_write(path: str, content: str) -> str:
  function run_edit (line 154) | def run_edit(path: str, old_text: str, new_text: str) -> str:
  function agent_loop (line 188) | def agent_loop(messages: list):

FILE: agents/s06_context_compact.py
  function estimate_tokens (line 62) | def estimate_tokens(messages: list) -> int:
  function micro_compact (line 68) | def micro_compact(messages: list) -> list:
  function auto_compact (line 98) | def auto_compact(messages: list) -> list:
  function safe_path (line 125) | def safe_path(p: str) -> Path:
  function run_bash (line 131) | def run_bash(command: str) -> str:
  function run_read (line 143) | def run_read(path: str, limit: int = None) -> str:
  function run_write (line 152) | def run_write(path: str, content: str) -> str:
  function run_edit (line 161) | def run_edit(path: str, old_text: str, new_text: str) -> str:
  function agent_loop (line 195) | def agent_loop(messages: list):

FILE: agents/s07_task_system.py
  class TaskManager (line 47) | class TaskManager:
    method __init__ (line 48) | def __init__(self, tasks_dir: Path):
    method _max_id (line 53) | def _max_id(self) -> int:
    method _load (line 57) | def _load(self, task_id: int) -> dict:
    method _save (line 63) | def _save(self, task: dict):
    method create (line 67) | def create(self, subject: str, description: str = "") -> str:
    method get (line 76) | def get(self, task_id: int) -> str:
    method update (line 79) | def update(self, task_id: int, status: str = None,
    method _clear_dependency (line 105) | def _clear_dependency(self, completed_id: int):
    method list_all (line 113) | def list_all(self) -> str:
  function safe_path (line 131) | def safe_path(p: str) -> Path:
  function run_bash (line 137) | def run_bash(command: str) -> str:
  function run_read (line 149) | def run_read(path: str, limit: int = None) -> str:
  function run_write (line 158) | def run_write(path: str, content: str) -> str:
  function run_edit (line 167) | def run_edit(path: str, old_text: str, new_text: str) -> str:
  function agent_loop (line 210) | def agent_loop(messages: list):

FILE: agents/s08_background_tasks.py
  class BackgroundManager (line 50) | class BackgroundManager:
    method __init__ (line 51) | def __init__(self):
    method run (line 56) | def run(self, command: str) -> str:
    method _execute (line 66) | def _execute(self, task_id: str, command: str):
    method check (line 91) | def check(self, task_id: str = None) -> str:
    method drain_notifications (line 103) | def drain_notifications(self) -> list:
  function safe_path (line 115) | def safe_path(p: str) -> Path:
  function run_bash (line 121) | def run_bash(command: str) -> str:
  function run_read (line 133) | def run_read(path: str, limit: int = None) -> str:
  function run_write (line 142) | def run_write(path: str, content: str) -> str:
  function run_edit (line 151) | def run_edit(path: str, old_text: str, new_text: str) -> str:
  function agent_loop (line 188) | def agent_loop(messages: list):

FILE: agents/s09_agent_teams.py
  class MessageBus (line 78) | class MessageBus:
    method __init__ (line 79) | def __init__(self, inbox_dir: Path):
    method send (line 83) | def send(self, sender: str, to: str, content: str,
    method read_inbox (line 100) | def read_inbox(self, name: str) -> list:
    method broadcast (line 111) | def broadcast(self, sender: str, content: str, teammates: list) -> str:
  class TeammateManager (line 124) | class TeammateManager:
    method __init__ (line 125) | def __init__(self, team_dir: Path):
    method _load_config (line 132) | def _load_config(self) -> dict:
    method _save_config (line 137) | def _save_config(self):
    method _find_member (line 140) | def _find_member(self, name: str) -> dict:
    method spawn (line 146) | def spawn(self, name: str, role: str, prompt: str) -> str:
    method _teammate_loop (line 166) | def _teammate_loop(self, name: str, role: str, prompt: str):
    method _exec (line 206) | def _exec(self, sender: str, tool_name: str, args: dict) -> str:
    method _teammate_tools (line 222) | def _teammate_tools(self) -> list:
    method list_all (line 239) | def list_all(self) -> str:
    method member_names (line 247) | def member_names(self) -> list:
  function _safe_path (line 255) | def _safe_path(p: str) -> Path:
  function _run_bash (line 262) | def _run_bash(command: str) -> str:
  function _run_read (line 277) | def _run_read(path: str, limit: int = None) -> str:
  function _run_write (line 287) | def _run_write(path: str, content: str) -> str:
  function _run_edit (line 297) | def _run_edit(path: str, old_text: str, new_text: str) -> str:
  function agent_loop (line 345) | def agent_loop(messages: list):

FILE: agents/s10_team_protocols.py
  class MessageBus (line 88) | class MessageBus:
    method __init__ (line 89) | def __init__(self, inbox_dir: Path):
    method send (line 93) | def send(self, sender: str, to: str, content: str,
    method read_inbox (line 110) | def read_inbox(self, name: str) -> list:
    method broadcast (line 121) | def broadcast(self, sender: str, content: str, teammates: list) -> str:
  class TeammateManager (line 134) | class TeammateManager:
    method __init__ (line 135) | def __init__(self, team_dir: Path):
    method _load_config (line 142) | def _load_config(self) -> dict:
    method _save_config (line 147) | def _save_config(self):
    method _find_member (line 150) | def _find_member(self, name: str) -> dict:
    method spawn (line 156) | def spawn(self, name: str, role: str, prompt: str) -> str:
    method _teammate_loop (line 176) | def _teammate_loop(self, name: str, role: str, prompt: str):
    method _exec (line 222) | def _exec(self, sender: str, tool_name: str, args: dict) -> str:
    method _teammate_tools (line 259) | def _teammate_tools(self) -> list:
    method list_all (line 280) | def list_all(self) -> str:
    method member_names (line 288) | def member_names(self) -> list:
  function _safe_path (line 296) | def _safe_path(p: str) -> Path:
  function _run_bash (line 303) | def _run_bash(command: str) -> str:
  function _run_read (line 318) | def _run_read(path: str, limit: int = None) -> str:
  function _run_write (line 328) | def _run_write(path: str, content: str) -> str:
  function _run_edit (line 338) | def _run_edit(path: str, old_text: str, new_text: str) -> str:
  function handle_shutdown_request (line 351) | def handle_shutdown_request(teammate: str) -> str:
  function handle_plan_review (line 362) | def handle_plan_review(request_id: str, approve: bool, feedback: str = "...
  function _check_shutdown_status (line 376) | def _check_shutdown_status(request_id: str) -> str:
  function agent_loop (line 426) | def agent_loop(messages: list):

FILE: agents/s11_autonomous_agents.py
  class MessageBus (line 81) | class MessageBus:
    method __init__ (line 82) | def __init__(self, inbox_dir: Path):
    method send (line 86) | def send(self, sender: str, to: str, content: str,
    method read_inbox (line 103) | def read_inbox(self, name: str) -> list:
    method broadcast (line 114) | def broadcast(self, sender: str, content: str, teammates: list) -> str:
  function scan_unclaimed_tasks (line 127) | def scan_unclaimed_tasks() -> list:
  function claim_task (line 139) | def claim_task(task_id: int, owner: str) -> str:
  function make_identity_block (line 152) | def make_identity_block(name: str, role: str, team_name: str) -> dict:
  class TeammateManager (line 160) | class TeammateManager:
    method __init__ (line 161) | def __init__(self, team_dir: Path):
    method _load_config (line 168) | def _load_config(self) -> dict:
    method _save_config (line 173) | def _save_config(self):
    method _find_member (line 176) | def _find_member(self, name: str) -> dict:
    method _set_status (line 182) | def _set_status(self, name: str, status: str):
    method spawn (line 188) | def spawn(self, name: str, role: str, prompt: str) -> str:
    method _loop (line 208) | def _loop(self, name: str, role: str, prompt: str):
    method _exec (line 295) | def _exec(self, sender: str, tool_name: str, args: dict) -> str:
    method _teammate_tools (line 333) | def _teammate_tools(self) -> list:
    method list_all (line 358) | def list_all(self) -> str:
    method member_names (line 366) | def member_names(self) -> list:
  function _safe_path (line 374) | def _safe_path(p: str) -> Path:
  function _run_bash (line 381) | def _run_bash(command: str) -> str:
  function _run_read (line 396) | def _run_read(path: str, limit: int = None) -> str:
  function _run_write (line 406) | def _run_write(path: str, content: str) -> str:
  function _run_edit (line 416) | def _run_edit(path: str, old_text: str, new_text: str) -> str:
  function handle_shutdown_request (line 429) | def handle_shutdown_request(teammate: str) -> str:
  function handle_plan_review (line 440) | def handle_plan_review(request_id: str, approve: bool, feedback: str = "...
  function _check_shutdown_status (line 454) | def _check_shutdown_status(request_id: str) -> str:
  function agent_loop (line 510) | def agent_loop(messages: list):

FILE: agents/s12_worktree_task_isolation.py
  function detect_repo_root (line 53) | def detect_repo_root(cwd: Path) -> Path | None:
  class EventBus (line 83) | class EventBus:
    method __init__ (line 84) | def __init__(self, event_log_path: Path):
    method emit (line 90) | def emit(
    method list_recent (line 108) | def list_recent(self, limit: int = 20) -> str:
  class TaskManager (line 122) | class TaskManager:
    method __init__ (line 123) | def __init__(self, tasks_dir: Path):
    method _max_id (line 128) | def _max_id(self) -> int:
    method _path (line 137) | def _path(self, task_id: int) -> Path:
    method _load (line 140) | def _load(self, task_id: int) -> dict:
    method _save (line 146) | def _save(self, task: dict):
    method create (line 149) | def create(self, subject: str, description: str = "") -> str:
    method get (line 165) | def get(self, task_id: int) -> str:
    method exists (line 168) | def exists(self, task_id: int) -> bool:
    method update (line 171) | def update(self, task_id: int, status: str = None, owner: str = None) ...
    method bind_worktree (line 183) | def bind_worktree(self, task_id: int, worktree: str, owner: str = "") ...
    method unbind_worktree (line 194) | def unbind_worktree(self, task_id: int) -> str:
    method list_all (line 201) | def list_all(self) -> str:
  class WorktreeManager (line 225) | class WorktreeManager:
    method __init__ (line 226) | def __init__(self, repo_root: Path, tasks: TaskManager, events: EventB...
    method _is_git_repo (line 237) | def _is_git_repo(self) -> bool:
    method _run_git (line 250) | def _run_git(self, args: list[str]) -> str:
    method _load_index (line 265) | def _load_index(self) -> dict:
    method _save_index (line 268) | def _save_index(self, data: dict):
    method _find (line 271) | def _find(self, name: str) -> dict | None:
    method _validate_name (line 278) | def _validate_name(self, name: str):
    method create (line 284) | def create(self, name: str, task_id: int = None, base_ref: str = "HEAD...
    method list_all (line 337) | def list_all(self) -> str:
    method status (line 351) | def status(self, name: str) -> str:
    method run (line 368) | def run(self, name: str, command: str) -> str:
    method remove (line 394) | def remove(self, name: str, force: bool = False, complete_task: bool =...
    method keep (line 448) | def keep(self, name: str) -> str:
  function safe_path (line 478) | def safe_path(p: str) -> Path:
  function run_bash (line 485) | def run_bash(command: str) -> str:
  function run_read (line 504) | def run_read(path: str, limit: int = None) -> str:
  function run_write (line 514) | def run_write(path: str, content: str) -> str:
  function run_edit (line 524) | def run_edit(path: str, old_text: str, new_text: str) -> str:
  function agent_loop (line 729) | def agent_loop(messages: list):

FILE: agents/s_full.py
  function safe_path (line 74) | def safe_path(p: str) -> Path:
  function run_bash (line 80) | def run_bash(command: str) -> str:
  function run_read (line 92) | def run_read(path: str, limit: int = None) -> str:
  function run_write (line 101) | def run_write(path: str, content: str) -> str:
  function run_edit (line 110) | def run_edit(path: str, old_text: str, new_text: str) -> str:
  class TodoManager (line 123) | class TodoManager:
    method __init__ (line 124) | def __init__(self):
    method update (line 127) | def update(self, items: list) -> str:
    method render (line 144) | def render(self) -> str:
    method has_open_items (line 155) | def has_open_items(self) -> bool:
  function run_subagent (line 160) | def run_subagent(prompt: str, agent_type: str = "Explore") -> str:
  class SkillLoader (line 199) | class SkillLoader:
    method __init__ (line 200) | def __init__(self, skills_dir: Path):
    method descriptions (line 216) | def descriptions(self) -> str:
    method load (line 220) | def load(self, name: str) -> str:
  function estimate_tokens (line 227) | def estimate_tokens(messages: list) -> int:
  function microcompact (line 230) | def microcompact(messages: list):
  function auto_compact (line 243) | def auto_compact(messages: list) -> list:
  class TaskManager (line 263) | class TaskManager:
    method __init__ (line 264) | def __init__(self):
    method _next_id (line 267) | def _next_id(self) -> int:
    method _load (line 271) | def _load(self, tid: int) -> dict:
    method _save (line 276) | def _save(self, task: dict):
    method create (line 279) | def create(self, subject: str, description: str = "") -> str:
    method get (line 285) | def get(self, tid: int) -> str:
    method update (line 288) | def update(self, tid: int, status: str = None,
    method list_all (line 309) | def list_all(self) -> str:
    method claim (line 320) | def claim(self, tid: int, owner: str) -> str:
  class BackgroundManager (line 329) | class BackgroundManager:
    method __init__ (line 330) | def __init__(self):
    method run (line 334) | def run(self, command: str, timeout: int = 120) -> str:
    method _exec (line 340) | def _exec(self, tid: str, command: str, timeout: int):
    method check (line 351) | def check(self, tid: str = None) -> str:
    method drain (line 357) | def drain(self) -> list:
  class MessageBus (line 365) | class MessageBus:
    method __init__ (line 366) | def __init__(self):
    method send (line 369) | def send(self, sender: str, to: str, content: str,
    method read_inbox (line 378) | def read_inbox(self, name: str) -> list:
    method broadcast (line 385) | def broadcast(self, sender: str, content: str, names: list) -> str:
  class TeammateManager (line 400) | class TeammateManager:
    method __init__ (line 401) | def __init__(self, bus: MessageBus, task_mgr: TaskManager):
    method _load (line 409) | def _load(self) -> dict:
    method _save (line 414) | def _save(self):
    method _find (line 417) | def _find(self, name: str) -> dict:
    method spawn (line 422) | def spawn(self, name: str, role: str, prompt: str) -> str:
    method _set_status (line 436) | def _set_status(self, name: str, status: str):
    method _loop (line 442) | def _loop(self, name: str, role: str, prompt: str):
    method list_all (line 534) | def list_all(self) -> str:
    method member_names (line 541) | def member_names(self) -> list:
  function handle_shutdown_request (line 561) | def handle_shutdown_request(teammate: str) -> str:
  function handle_plan_review (line 568) | def handle_plan_review(request_id: str, approve: bool, feedback: str = "...
  function agent_loop (line 655) | def agent_loop(messages: list):

FILE: skills/agent-builder/references/minimal-agent.py
  function execute_tool (line 67) | def execute_tool(name: str, args: dict) -> str:
  function agent (line 97) | def agent(prompt: str, history: list = None) -> str:

FILE: skills/agent-builder/references/subagent-pattern.py
  function get_agent_descriptions (line 49) | def get_agent_descriptions() -> str:
  function get_tools_for_agent (line 57) | def get_tools_for_agent(agent_type: str, base_tools: list) -> list:
  function run_task (line 119) | def run_task(description: str, prompt: str, agent_type: str,

FILE: skills/agent-builder/references/tool-templates.py
  function safe_path (line 141) | def safe_path(p: str) -> Path:
  function run_bash (line 152) | def run_bash(command: str) -> str:
  function run_read_file (line 183) | def run_read_file(path: str, limit: int = None) -> str:
  function run_write_file (line 206) | def run_write_file(path: str, content: str) -> str:
  function run_edit_file (line 225) | def run_edit_file(path: str, old_text: str, new_text: str) -> str:
  function execute_tool (line 253) | def execute_tool(name: str, args: dict) -> str:

FILE: skills/agent-builder/scripts/init_agent.py
  function create_agent (line 217) | def create_agent(name: str, level: int, output_dir: Path):
  function main (line 255) | def main():

FILE: web/scripts/extract-content.ts
  constant WEB_DIR (line 12) | const WEB_DIR = path.resolve(__dirname, "..");
  constant REPO_ROOT (line 13) | const REPO_ROOT = path.resolve(WEB_DIR, "..");
  constant AGENTS_DIR (line 14) | const AGENTS_DIR = path.join(REPO_ROOT, "agents");
  constant DOCS_DIR (line 15) | const DOCS_DIR = path.join(REPO_ROOT, "docs");
  constant OUT_DIR (line 16) | const OUT_DIR = path.join(WEB_DIR, "src", "data", "generated");
  function filenameToVersionId (line 22) | function filenameToVersionId(filename: string): string | null {
  function extractClasses (line 33) | function extractClasses(
  function extractFunctions (line 63) | function extractFunctions(
  function extractTools (line 84) | function extractTools(source: string): string[] {
  function countLoc (line 95) | function countLoc(lines: string[]): number {
  function detectLocale (line 106) | function detectLocale(relPath: string): "en" | "zh" | "ja" {
  function extractDocVersion (line 113) | function extractDocVersion(filename: string): string | null {
  function main (line 119) | function main() {

FILE: web/src/app/[locale]/(learn)/[version]/client.tsx
  type VersionDetailClientProps (line 14) | interface VersionDetailClientProps {
  function VersionDetailClient (line 28) | function VersionDetailClient({

FILE: web/src/app/[locale]/(learn)/[version]/diff/diff-content.tsx
  type DiffPageContentProps (line 16) | interface DiffPageContentProps {
  function DiffPageContent (line 20) | function DiffPageContent({ version }: DiffPageContentProps) {

FILE: web/src/app/[locale]/(learn)/[version]/diff/page.tsx
  function generateStaticParams (line 4) | function generateStaticParams() {
  function DiffPage (line 8) | async function DiffPage({

FILE: web/src/app/[locale]/(learn)/[version]/page.tsx
  function generateStaticParams (line 8) | function generateStaticParams() {
  function VersionPage (line 12) | async function VersionPage({

FILE: web/src/app/[locale]/(learn)/compare/page.tsx
  function ComparePage (line 16) | function ComparePage() {

FILE: web/src/app/[locale]/(learn)/layers/page.tsx
  constant LAYER_BORDER_CLASSES (line 15) | const LAYER_BORDER_CLASSES: Record<string, string> = {
  constant LAYER_HEADER_BG (line 23) | const LAYER_HEADER_BG: Record<string, string> = {
  function LayersPage (line 31) | function LayersPage() {

FILE: web/src/app/[locale]/(learn)/layout.tsx
  function LearnLayout (line 3) | function LearnLayout({

FILE: web/src/app/[locale]/(learn)/timeline/page.tsx
  function TimelinePage (line 6) | function TimelinePage() {

FILE: web/src/app/[locale]/layout.tsx
  function generateStaticParams (line 12) | function generateStaticParams() {
  function generateMetadata (line 16) | async function generateMetadata({
  function RootLayout (line 29) | async function RootLayout({

FILE: web/src/app/[locale]/page.tsx
  constant LAYER_DOT_COLORS (line 12) | const LAYER_DOT_COLORS: Record<string, string> = {
  constant LAYER_BORDER_COLORS (line 20) | const LAYER_BORDER_COLORS: Record<string, string> = {
  constant LAYER_BAR_COLORS (line 28) | const LAYER_BAR_COLORS: Record<string, string> = {
  function getVersionData (line 36) | function getVersionData(id: string) {
  function HomePage (line 40) | function HomePage() {

FILE: web/src/app/page.tsx
  function RootPage (line 3) | function RootPage() {

FILE: web/src/components/architecture/arch-diagram.tsx
  constant CLASS_DESCRIPTIONS (line 8) | const CLASS_DESCRIPTIONS: Record<string, string> = {
  type ArchDiagramProps (line 21) | interface ArchDiagramProps {
  function getLayerColor (line 25) | function getLayerColor(versionId: string): string {
  function getLayerColorClasses (line 30) | function getLayerColorClasses(versionId: string): {
  function collectClassesUpTo (line 71) | function collectClassesUpTo(
  function getNewClassNames (line 96) | function getNewClassNames(version: string): Set<string> {
  function ArchDiagram (line 105) | function ArchDiagram({ version }: ArchDiagramProps) {

FILE: web/src/components/architecture/design-decisions.tsx
  type Decision (line 22) | interface Decision {
  type AnnotationFile (line 31) | interface AnnotationFile {
  constant ANNOTATIONS (line 36) | const ANNOTATIONS: Record<string, AnnotationFile> = {
  type DesignDecisionsProps (line 51) | interface DesignDecisionsProps {
  function DecisionCard (line 55) | function DecisionCard({
  function DesignDecisions (line 121) | function DesignDecisions({ version }: DesignDecisionsProps) {

FILE: web/src/components/architecture/execution-flow.tsx
  constant NODE_WIDTH (line 9) | const NODE_WIDTH = 140;
  constant NODE_HEIGHT (line 10) | const NODE_HEIGHT = 40;
  constant DIAMOND_SIZE (line 11) | const DIAMOND_SIZE = 50;
  constant LAYER_COLORS (line 13) | const LAYER_COLORS: Record<string, string> = {
  function getNodeCenter (line 21) | function getNodeCenter(node: FlowNode): { cx: number; cy: number } {
  function getEdgePath (line 25) | function getEdgePath(from: FlowNode, to: FlowNode): string {
  function NodeShape (line 44) | function NodeShape({ node }: { node: FlowNode }) {
  function EdgePath (line 137) | function EdgePath({
  type ExecutionFlowProps (line 184) | interface ExecutionFlowProps {
  function ExecutionFlow (line 188) | function ExecutionFlow({ version }: ExecutionFlowProps) {

FILE: web/src/components/architecture/message-flow.tsx
  constant FLOW_STEPS (line 6) | const FLOW_STEPS = [
  function MessageFlow (line 17) | function MessageFlow() {

FILE: web/src/components/code/source-viewer.tsx
  type SourceViewerProps (line 5) | interface SourceViewerProps {
  function highlightLine (line 10) | function highlightLine(line: string): React.ReactNode[] {
  function SourceViewer (line 71) | function SourceViewer({ source, filename }: SourceViewerProps) {

FILE: web/src/components/diff/code-diff.tsx
  type CodeDiffProps (line 7) | interface CodeDiffProps {
  function CodeDiff (line 14) | function CodeDiff({ oldSource, newSource, oldLabel, newLabel }: CodeDiff...
  function UnifiedView (line 62) | function UnifiedView({ changes }: { changes: Change[] }) {
  function SplitView (line 122) | function SplitView({ changes }: { changes: Change[] }) {

FILE: web/src/components/diff/whats-new.tsx
  type WhatsNewProps (line 7) | interface WhatsNewProps {
  function WhatsNew (line 18) | function WhatsNew({ diff }: WhatsNewProps) {

FILE: web/src/components/docs/doc-renderer.tsx
  type DocRendererProps (line 14) | interface DocRendererProps {
  function renderMarkdown (line 18) | function renderMarkdown(md: string): string {
  function postProcessHtml (line 30) | function postProcessHtml(html: string): string {
  function DocRenderer (line 61) | function DocRenderer({ version }: DocRendererProps) {

FILE: web/src/components/layout/header.tsx
  constant NAV_ITEMS (line 10) | const NAV_ITEMS = [
  constant LOCALES (line 16) | const LOCALES = [
  function Header (line 22) | function Header() {

FILE: web/src/components/layout/sidebar.tsx
  constant LAYER_DOT_BG (line 9) | const LAYER_DOT_BG: Record<string, string> = {
  function Sidebar (line 17) | function Sidebar() {

FILE: web/src/components/simulator/agent-loop-simulator.tsx
  type AgentLoopSimulatorProps (line 26) | interface AgentLoopSimulatorProps {
  function AgentLoopSimulator (line 30) | function AgentLoopSimulator({ version }: AgentLoopSimulatorProps) {

FILE: web/src/components/simulator/simulator-controls.tsx
  type SimulatorControlsProps (line 7) | interface SimulatorControlsProps {
  constant SPEEDS (line 20) | const SPEEDS = [0.5, 1, 2, 4];
  function SimulatorControls (line 22) | function SimulatorControls({

FILE: web/src/components/simulator/simulator-message.tsx
  type SimulatorMessageProps (line 8) | interface SimulatorMessageProps {
  constant TYPE_CONFIG (line 13) | const TYPE_CONFIG: Record<
  function SimulatorMessage (line 49) | function SimulatorMessage({ step, index }: SimulatorMessageProps) {

FILE: web/src/components/timeline/timeline.tsx
  constant LAYER_DOT_BG (line 11) | const LAYER_DOT_BG: Record<string, string> = {
  constant LAYER_LINE_BG (line 19) | const LAYER_LINE_BG: Record<string, string> = {
  constant LAYER_BAR_BG (line 27) | const LAYER_BAR_BG: Record<string, string> = {
  function getVersionData (line 35) | function getVersionData(id: string) {
  constant MAX_LOC (line 39) | const MAX_LOC = Math.max(
  function Timeline (line 45) | function Timeline() {

FILE: web/src/components/ui/badge.tsx
  constant LAYER_COLORS (line 3) | const LAYER_COLORS = {
  type BadgeProps (line 16) | interface BadgeProps {
  function LayerBadge (line 22) | function LayerBadge({ layer, children, className }: BadgeProps) {

FILE: web/src/components/ui/card.tsx
  type CardProps (line 3) | interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
  function Card (line 7) | function Card({ className, children, ...props }: CardProps) {
  function CardHeader (line 21) | function CardHeader({ className, children, ...props }: CardProps) {
  function CardTitle (line 29) | function CardTitle({

FILE: web/src/components/ui/tabs.tsx
  type TabsProps (line 6) | interface TabsProps {
  function Tabs (line 13) | function Tabs({ tabs, defaultTab, children, className }: TabsProps) {

FILE: web/src/components/visualizations/index.tsx
  function SessionVisualization (line 24) | function SessionVisualization({ version }: { version: string }) {

FILE: web/src/components/visualizations/s01-agent-loop.tsx
  type FlowNode (line 10) | interface FlowNode {
  constant NODES (line 20) | const NODES: FlowNode[] = [
  type FlowEdge (line 30) | interface FlowEdge {
  constant EDGES (line 36) | const EDGES: FlowEdge[] = [
  constant ACTIVE_NODES_PER_STEP (line 46) | const ACTIVE_NODES_PER_STEP: string[][] = [
  constant ACTIVE_EDGES_PER_STEP (line 57) | const ACTIVE_EDGES_PER_STEP: string[][] = [
  type MessageBlock (line 69) | interface MessageBlock {
  constant MESSAGES_PER_STEP (line 75) | const MESSAGES_PER_STEP: (MessageBlock | null)[][] = [
  constant STEP_INFO (line 90) | const STEP_INFO = [
  function getNode (line 102) | function getNode(id: string): FlowNode {
  function edgePath (line 106) | function edgePath(fromId: string, toId: string): string {
  function AgentLoop (line 138) | function AgentLoop({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s02-tool-dispatch.tsx
  type ToolDef (line 10) | interface ToolDef {
  constant TOOLS (line 19) | const TOOLS: ToolDef[] = [
  constant ACTIVE_TOOL_PER_STEP (line 55) | const ACTIVE_TOOL_PER_STEP: number[] = [-1, 0, 1, 2, 3, 4];
  constant REQUEST_PER_STEP (line 58) | const REQUEST_PER_STEP: (string | null)[] = [
  constant STEP_INFO (line 68) | const STEP_INFO = [
  constant SVG_WIDTH (line 78) | const SVG_WIDTH = 600;
  constant SVG_HEIGHT (line 79) | const SVG_HEIGHT = 320;
  constant DISPATCHER_X (line 80) | const DISPATCHER_X = SVG_WIDTH / 2;
  constant DISPATCHER_Y (line 81) | const DISPATCHER_Y = 60;
  constant DISPATCHER_W (line 82) | const DISPATCHER_W = 160;
  constant DISPATCHER_H (line 83) | const DISPATCHER_H = 50;
  constant CARD_Y (line 84) | const CARD_Y = 230;
  constant CARD_W (line 85) | const CARD_W = 110;
  constant CARD_H (line 86) | const CARD_H = 65;
  constant CARD_GAP (line 87) | const CARD_GAP = 20;
  function getCardX (line 89) | function getCardX(index: number): number {
  function ToolDispatch (line 95) | function ToolDispatch({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s03-todo-write.tsx
  type TaskStatus (line 9) | type TaskStatus = "pending" | "in_progress" | "done";
  type Task (line 11) | interface Task {
  constant TASK_STATES (line 18) | const TASK_STATES: Task[][] = [
  constant NAG_TIMER_PER_STEP (line 71) | const NAG_TIMER_PER_STEP = [0, 1, 2, 3, 0, 0, 0];
  constant NAG_THRESHOLD (line 72) | const NAG_THRESHOLD = 3;
  constant NAG_FIRES_PER_STEP (line 75) | const NAG_FIRES_PER_STEP = [false, false, false, true, false, false, fal...
  constant STEP_INFO (line 78) | const STEP_INFO = [
  function KanbanColumn (line 90) | function KanbanColumn({
  function TaskCard (line 129) | function TaskCard({ task }: { task: Task }) {
  function NagGauge (line 171) | function NagGauge({ value, max, firing }: { value: number; max: number; ...
  function TodoWrite (line 221) | function TodoWrite({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s04-subagent.tsx
  type MessageBlock (line 7) | interface MessageBlock {
  constant PARENT_BASE_MESSAGES (line 13) | const PARENT_BASE_MESSAGES: MessageBlock[] = [
  constant TASK_PROMPT (line 19) | const TASK_PROMPT: MessageBlock = {
  constant CHILD_WORK_MESSAGES (line 25) | const CHILD_WORK_MESSAGES: MessageBlock[] = [
  constant SUMMARY_BLOCK (line 30) | const SUMMARY_BLOCK: MessageBlock = {
  constant STEPS (line 36) | const STEPS = [
  function SubagentIsolation (line 69) | function SubagentIsolation({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s05-skill-loading.tsx
  type SkillEntry (line 7) | interface SkillEntry {
  constant SKILLS (line 14) | const SKILLS: SkillEntry[] = [
  constant TOKEN_STATES (line 61) | const TOKEN_STATES = [120, 120, 440, 440, 780, 780];
  constant MAX_TOKEN_DISPLAY (line 62) | const MAX_TOKEN_DISPLAY = 1000;
  constant STEPS (line 64) | const STEPS = [
  function SkillLoading (line 97) | function SkillLoading({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s06-context-compact.tsx
  type BlockType (line 8) | type BlockType = "user" | "assistant" | "tool_result";
  type ContextBlock (line 10) | interface ContextBlock {
  constant BLOCK_COLORS (line 17) | const BLOCK_COLORS: Record<BlockType, string> = {
  constant BLOCK_LABELS (line 23) | const BLOCK_LABELS: Record<BlockType, string> = {
  function generateBlocks (line 29) | function generateBlocks(count: number, seed: number): ContextBlock[] {
  constant MAX_TOKENS (line 46) | const MAX_TOKENS = 100000;
  constant WINDOW_HEIGHT (line 47) | const WINDOW_HEIGHT = 350;
  type StepState (line 49) | interface StepState {
  function computeStepState (line 56) | function computeStepState(step: number): StepState {
  constant STEPS (line 158) | const STEPS = [
  function ContextCompact (line 196) | function ContextCompact({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s07-task-system.tsx
  type TaskStatus (line 9) | type TaskStatus = "pending" | "in_progress" | "completed" | "blocked";
  type TaskNode (line 11) | interface TaskNode {
  type StepInfo (line 19) | interface StepInfo {
  constant TASKS (line 24) | const TASKS: TaskNode[] = [
  constant NODE_W (line 32) | const NODE_W = 140;
  constant NODE_H (line 33) | const NODE_H = 50;
  constant STEP_INFO (line 35) | const STEP_INFO: StepInfo[] = [
  function getTaskStatus (line 71) | function getTaskStatus(taskId: string, step: number): TaskStatus {
  function isEdgeActive (line 122) | function isEdgeActive(fromId: string, toId: string, step: number): boole...
  function getStatusColor (line 131) | function getStatusColor(status: TaskStatus) {
  function getStatusLabel (line 172) | function getStatusLabel(status: TaskStatus): string {
  function buildCurvePath (line 185) | function buildCurvePath(
  function TaskSystem (line 195) | function TaskSystem({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s08-background-tasks.tsx
  type StepInfo (line 8) | interface StepInfo {
  constant STEP_INFO (line 13) | const STEP_INFO: StepInfo[] = [
  constant LANE_Y (line 50) | const LANE_Y = {
  constant LANE_HEIGHT (line 56) | const LANE_HEIGHT = 44;
  constant TIMELINE_LEFT (line 57) | const TIMELINE_LEFT = 160;
  constant TIMELINE_RIGHT (line 58) | const TIMELINE_RIGHT = 720;
  constant TIMELINE_WIDTH (line 59) | const TIMELINE_WIDTH = TIMELINE_RIGHT - TIMELINE_LEFT;
  constant QUEUE_Y (line 61) | const QUEUE_Y = 300;
  type WorkBlock (line 63) | interface WorkBlock {
  constant WORK_BLOCKS (line 73) | const WORK_BLOCKS: WorkBlock[] = [
  type ForkArrow (line 102) | interface ForkArrow {
  constant FORK_ARROWS (line 108) | const FORK_ARROWS: ForkArrow[] = [
  type QueueCard (line 113) | interface QueueCard {
  constant QUEUE_CARDS (line 120) | const QUEUE_CARDS: QueueCard[] = [
  function fractionToX (line 135) | function fractionToX(fraction: number): number {
  function getBlockEndFraction (line 139) | function getBlockEndFraction(block: WorkBlock, step: number): number {
  function BackgroundTasks (line 151) | function BackgroundTasks({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s09-agent-teams.tsx
  constant SVG_W (line 9) | const SVG_W = 560;
  constant SVG_H (line 10) | const SVG_H = 340;
  constant AGENT_R (line 11) | const AGENT_R = 40;
  constant AGENTS (line 14) | const AGENTS = [
  constant TRAY_W (line 21) | const TRAY_W = 72;
  constant TRAY_H (line 22) | const TRAY_H = 22;
  constant TRAY_OFFSET_Y (line 23) | const TRAY_OFFSET_Y = AGENT_R + 14;
  constant MSG_W (line 26) | const MSG_W = 60;
  constant MSG_H (line 27) | const MSG_H = 20;
  function agentById (line 29) | function agentById(id: string) {
  function trayCenter (line 33) | function trayCenter(id: string) {
  constant STEPS (line 39) | const STEPS = [
  function agentGlows (line 50) | function agentGlows(agentId: string, step: number): boolean {
  function trayHasMessage (line 60) | function trayHasMessage(agentId: string, step: number): boolean {
  function TravelingMessage (line 68) | function TravelingMessage({
  function TraceLine (line 115) | function TraceLine({ from, to, strokeColor }: { from: string; to: string...
  function AgentTeams (line 134) | function AgentTeams({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s10-team-protocols.tsx
  type Protocol (line 9) | type Protocol = "shutdown" | "plan";
  constant SVG_W (line 12) | const SVG_W = 560;
  constant SVG_H (line 13) | const SVG_H = 360;
  constant LIFELINE_LEFT_X (line 14) | const LIFELINE_LEFT_X = 140;
  constant LIFELINE_RIGHT_X (line 15) | const LIFELINE_RIGHT_X = 420;
  constant LIFELINE_TOP (line 16) | const LIFELINE_TOP = 60;
  constant LIFELINE_BOTTOM (line 17) | const LIFELINE_BOTTOM = 330;
  constant ACTIVATION_W (line 18) | const ACTIVATION_W = 12;
  constant ARROW_Y_START (line 19) | const ARROW_Y_START = 110;
  constant ARROW_Y_GAP (line 20) | const ARROW_Y_GAP = 70;
  constant REQUEST_ID (line 23) | const REQUEST_ID = "req_abc";
  constant SHUTDOWN_STEPS (line 26) | const SHUTDOWN_STEPS = [
  constant PLAN_STEPS (line 34) | const PLAN_STEPS = [
  function SequenceArrow (line 41) | function SequenceArrow({
  function DecisionBox (line 131) | function DecisionBox({ x, y }: { x: number; y: number }) {
  function ActivationBar (line 159) | function ActivationBar({
  function TeamProtocols (line 185) | function TeamProtocols({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s11-autonomous-agents.tsx
  type Phase (line 9) | type Phase = "idle" | "poll" | "claim" | "work";
  constant FSM_CX (line 11) | const FSM_CX = 110;
  constant FSM_CY (line 12) | const FSM_CY = 110;
  constant FSM_R (line 13) | const FSM_R = 65;
  constant FSM_STATE_R (line 14) | const FSM_STATE_R = 22;
  constant FSM_STATES (line 16) | const FSM_STATES: { id: Phase; label: string; angle: number }[] = [
  constant FSM_TRANSITIONS (line 23) | const FSM_TRANSITIONS: { from: Phase; to: Phase }[] = [
  function fsmPos (line 30) | function fsmPos(angle: number) {
  constant PHASE_COLORS (line 34) | const PHASE_COLORS: Record<Phase, string> = {
  type TaskRow (line 42) | interface TaskRow {
  constant INITIAL_TASKS (line 49) | const INITIAL_TASKS: TaskRow[] = [
  constant BOARD_CX (line 57) | const BOARD_CX = 140;
  constant BOARD_CY (line 58) | const BOARD_CY = 90;
  constant AGENT_ORBIT (line 59) | const AGENT_ORBIT = 85;
  constant AGENT_R (line 60) | const AGENT_R = 20;
  constant AGENT_ANGLES (line 62) | const AGENT_ANGLES = [-Math.PI / 2, Math.PI / 6, (5 * Math.PI) / 6];
  function agentPos (line 64) | function agentPos(index: number) {
  constant STEPS (line 70) | const STEPS = [
  type AgentState (line 82) | interface AgentState {
  function getAgentStates (line 89) | function getAgentStates(step: number): AgentState[] {
  function getTaskStates (line 146) | function getTaskStates(step: number): TaskRow[] {
  function getActivePhase (line 155) | function getActivePhase(step: number): Phase {
  function TimerRing (line 165) | function TimerRing({ cx, cy, r, fill }: { cx: number; cy: number; r: num...
  function FSMArrow (line 189) | function FSMArrow({ from, to, active, inactiveStroke }: { from: Phase; t...
  function AutonomousAgents (line 224) | function AutonomousAgents({ title }: { title?: string }) {

FILE: web/src/components/visualizations/s12-worktree-task-isolation.tsx
  type TaskStatus (line 7) | type TaskStatus = "pending" | "in_progress" | "completed";
  type TaskRow (line 9) | interface TaskRow {
  type WorktreeRow (line 16) | interface WorktreeRow {
  type Lane (line 23) | interface Lane {
  type StepState (line 29) | interface StepState {
  constant STEPS (line 38) | const STEPS: StepState[] = [
  function statusClass (line 145) | function statusClass(status: TaskStatus): string {
  function worktreeClass (line 151) | function worktreeClass(state: WorktreeRow["state"]): string {
  function WorktreeTaskIsolation (line 158) | function WorktreeTaskIsolation({ title }: { title?: string }) {

FILE: web/src/components/visualizations/shared/step-controls.tsx
  type StepControlsProps (line 6) | interface StepControlsProps {
  function StepControls (line 19) | function StepControls({

FILE: web/src/data/execution-flows.ts
  type FlowDefinition (line 3) | interface FlowDefinition {
  constant FLOW_WIDTH (line 8) | const FLOW_WIDTH = 600;
  constant COL_CENTER (line 9) | const COL_CENTER = FLOW_WIDTH / 2;
  constant COL_LEFT (line 10) | const COL_LEFT = 140;
  constant COL_RIGHT (line 11) | const COL_RIGHT = FLOW_WIDTH - 140;
  constant EXECUTION_FLOWS (line 13) | const EXECUTION_FLOWS: Record<string, FlowDefinition> = {
  function getFlowForVersion (line 313) | function getFlowForVersion(version: string): FlowDefinition | null {

FILE: web/src/hooks/useDarkMode.ts
  function useDarkMode (line 5) | function useDarkMode(): boolean {
  type SvgPalette (line 23) | interface SvgPalette {
  function useSvgPalette (line 39) | function useSvgPalette(): SvgPalette {

FILE: web/src/hooks/useSimulator.ts
  type SimulatorState (line 6) | interface SimulatorState {
  function useSimulator (line 12) | function useSimulator(steps: SimStep[]) {

FILE: web/src/hooks/useSteppedVisualization.ts
  type SteppedVisualizationOptions (line 5) | interface SteppedVisualizationOptions {
  type SteppedVisualizationReturn (line 10) | interface SteppedVisualizationReturn {
  function useSteppedVisualization (line 23) | function useSteppedVisualization({

FILE: web/src/lib/constants.ts
  constant VERSION_ORDER (line 1) | const VERSION_ORDER = [
  constant LEARNING_PATH (line 5) | const LEARNING_PATH = VERSION_ORDER;
  type VersionId (line 7) | type VersionId = typeof LEARNING_PATH[number];
  constant VERSION_META (line 9) | const VERSION_META: Record<string, {
  constant LAYERS (line 31) | const LAYERS = [

FILE: web/src/lib/i18n-server.ts
  type Messages (line 5) | type Messages = typeof en;
  function getTranslations (line 9) | function getTranslations(locale: string, namespace: string) {

FILE: web/src/lib/i18n.tsx
  type Messages (line 7) | type Messages = typeof en;
  function I18nProvider (line 16) | function I18nProvider({ locale, children }: { locale: string; children: ...
  function useTranslations (line 25) | function useTranslations(namespace?: string) {
  function useLocale (line 34) | function useLocale() {

FILE: web/src/lib/utils.ts
  function cn (line 1) | function cn(...classes: (string | undefined | null | false)[]) {

FILE: web/src/types/agent-data.ts
  type AgentVersion (line 1) | interface AgentVersion {
  type VersionDiff (line 17) | interface VersionDiff {
  type DocContent (line 26) | interface DocContent {
  type VersionIndex (line 33) | interface VersionIndex {
  type SimStepType (line 38) | type SimStepType =
  type SimStep (line 45) | interface SimStep {
  type Scenario (line 53) | interface Scenario {
  type FlowNode (line 60) | interface FlowNode {
  type FlowEdge (line 68) | interface FlowEdge {
Condensed preview — 156 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,302K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 539,
    "preview": "name: CI\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build:\n    runs-on: ubuntu-late"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1530,
    "preview": "name: Test\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  unit-test:\n    runs-on: ubunt"
  },
  {
    "path": ".gitignore",
    "chars": 5004,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[codz]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packag"
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2024 shareAI Lab\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "README-ja.md",
    "chars": 15277,
    "preview": "# Learn Claude Code -- 真の Agent のための Harness Engineering\n\n[English](./README.md) | [中文](./README-zh.md) | [日本語](./README"
  },
  {
    "path": "README-zh.md",
    "chars": 13377,
    "preview": "# Learn Claude Code -- 真正的 Agent Harness 工程\n\n[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)\n\n## 模"
  },
  {
    "path": "README.md",
    "chars": 23791,
    "preview": "[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)\n# Learn Claude Code -- Harness Engineering for Rea"
  },
  {
    "path": "agents/__init__.py",
    "chars": 203,
    "preview": "# agents/ - Harness implementations (s01-s12) + full reference (s_full)\n# Each file is self-contained and runnable: pyth"
  },
  {
    "path": "agents/s01_agent_loop.py",
    "chars": 3599,
    "preview": "#!/usr/bin/env python3\n# Harness: the loop -- the model's first connection to the real world.\n\"\"\"\ns01_agent_loop.py - Th"
  },
  {
    "path": "agents/s02_tool_use.py",
    "chars": 5599,
    "preview": "#!/usr/bin/env python3\n# Harness: tool dispatch -- expanding what the model can reach.\n\"\"\"\ns02_tool_use.py - Tools\n\nThe "
  },
  {
    "path": "agents/s03_todo_write.py",
    "chars": 8509,
    "preview": "#!/usr/bin/env python3\n# Harness: planning -- keeping the model on course without scripting the route.\n\"\"\"\ns03_todo_writ"
  },
  {
    "path": "agents/s04_subagent.py",
    "chars": 7792,
    "preview": "#!/usr/bin/env python3\n# Harness: context isolation -- protecting the model's clarity of thought.\n\"\"\"\ns04_subagent.py - "
  },
  {
    "path": "agents/s05_skill_loading.py",
    "chars": 8566,
    "preview": "#!/usr/bin/env python3\n# Harness: on-demand knowledge -- domain expertise, loaded when the model asks.\n\"\"\"\ns05_skill_loa"
  },
  {
    "path": "agents/s06_context_compact.py",
    "chars": 9839,
    "preview": "#!/usr/bin/env python3\n# Harness: compression -- clean memory for infinite sessions.\n\"\"\"\ns06_context_compact.py - Compac"
  },
  {
    "path": "agents/s07_task_system.py",
    "chars": 10393,
    "preview": "#!/usr/bin/env python3\n# Harness: persistent tasks -- goals that outlive any single conversation.\n\"\"\"\ns07_task_system.py"
  },
  {
    "path": "agents/s08_background_tasks.py",
    "chars": 9420,
    "preview": "#!/usr/bin/env python3\n# Harness: background execution -- the model thinks while the harness waits.\n\"\"\"\ns08_background_t"
  },
  {
    "path": "agents/s09_agent_teams.py",
    "chars": 16949,
    "preview": "#!/usr/bin/env python3\n# Harness: team mailboxes -- multiple models, coordinated through files.\n\"\"\"\ns09_agent_teams.py -"
  },
  {
    "path": "agents/s10_team_protocols.py",
    "chars": 21142,
    "preview": "#!/usr/bin/env python3\n# Harness: protocols -- structured handshakes between models.\n\"\"\"\ns10_team_protocols.py - Team Pr"
  },
  {
    "path": "agents/s11_autonomous_agents.py",
    "chars": 24546,
    "preview": "#!/usr/bin/env python3\n# Harness: autonomy -- models that find work without being told.\n\"\"\"\ns11_autonomous_agents.py - A"
  },
  {
    "path": "agents/s12_worktree_task_isolation.py",
    "chars": 25862,
    "preview": "#!/usr/bin/env python3\n# Harness: directory isolation -- parallel execution lanes that never collide.\n\"\"\"\ns12_worktree_t"
  },
  {
    "path": "agents/s_full.py",
    "chars": 36423,
    "preview": "#!/usr/bin/env python3\n# Harness: all mechanisms combined -- the complete cockpit for the model.\n\"\"\"\ns_full.py - Full Re"
  },
  {
    "path": "docs/en/s01-the-agent-loop.md",
    "chars": 3581,
    "preview": "# s01: The Agent Loop\n\n`[ s01 ] s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"One loop & Bash is"
  },
  {
    "path": "docs/en/s02-tool-use.md",
    "chars": 3405,
    "preview": "# s02: Tool Use\n\n`s01 > [ s02 ] s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"Adding a tool means addi"
  },
  {
    "path": "docs/en/s03-todo-write.md",
    "chars": 3316,
    "preview": "# s03: TodoWrite\n\n`s01 > s02 > [ s03 ] s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"An agent without a plan"
  },
  {
    "path": "docs/en/s04-subagent.md",
    "chars": 3527,
    "preview": "# s04: Subagents\n\n`s01 > s02 > s03 > [ s04 ] s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"Break big tasks down; e"
  },
  {
    "path": "docs/en/s05-skill-loading.md",
    "chars": 3683,
    "preview": "# s05: Skills\n\n`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"Load knowledge when you ne"
  },
  {
    "path": "docs/en/s06-context-compact.md",
    "chars": 4385,
    "preview": "# s06: Context Compact\n\n`s01 > s02 > s03 > s04 > s05 > [ s06 ] | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"Context will fi"
  },
  {
    "path": "docs/en/s07-task-system.md",
    "chars": 4978,
    "preview": "# s07: Task System\n\n`s01 > s02 > s03 > s04 > s05 > s06 | [ s07 ] s08 > s09 > s10 > s11 > s12`\n\n> *\"Break big goals into "
  },
  {
    "path": "docs/en/s08-background-tasks.md",
    "chars": 3743,
    "preview": "# s08: Background Tasks\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > [ s08 ] s09 > s10 > s11 > s12`\n\n> *\"Run slow operati"
  },
  {
    "path": "docs/en/s09-agent-teams.md",
    "chars": 4575,
    "preview": "# s09: Agent Teams\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > [ s09 ] s10 > s11 > s12`\n\n> *\"When the task is too "
  },
  {
    "path": "docs/en/s10-team-protocols.md",
    "chars": 3962,
    "preview": "# s10: Team Protocols\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > [ s10 ] s11 > s12`\n\n> *\"Teammates need sha"
  },
  {
    "path": "docs/en/s11-autonomous-agents.md",
    "chars": 4705,
    "preview": "# s11: Autonomous Agents\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > [ s11 ] s12`\n\n> *\"Teammates scan "
  },
  {
    "path": "docs/en/s12-worktree-task-isolation.md",
    "chars": 5017,
    "preview": "# s12: Worktree + Task Isolation\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > [ s12 ]`\n\n> *\"Each "
  },
  {
    "path": "docs/ja/s01-the-agent-loop.md",
    "chars": 3156,
    "preview": "# s01: The Agent Loop\n\n`[ s01 ] s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"One loop & Bash is"
  },
  {
    "path": "docs/ja/s02-tool-use.md",
    "chars": 2990,
    "preview": "# s02: Tool Use\n\n`s01 > [ s02 ] s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"ツールを足すなら、ハンドラーを1つ足すだけ\"* "
  },
  {
    "path": "docs/ja/s03-todo-write.md",
    "chars": 2929,
    "preview": "# s03: TodoWrite\n\n`s01 > s02 > [ s03 ] s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"計画のないエージェントは行き当たりばったり\"*"
  },
  {
    "path": "docs/ja/s04-subagent.md",
    "chars": 3136,
    "preview": "# s04: Subagents\n\n`s01 > s02 > s03 > [ s04 ] s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"大きなタスクを分割し、各サブタスクにクリーンな"
  },
  {
    "path": "docs/ja/s05-skill-loading.md",
    "chars": 3252,
    "preview": "# s05: Skills\n\n`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"必要な知識を、必要な時に読み込む\"* -- syst"
  },
  {
    "path": "docs/ja/s06-context-compact.md",
    "chars": 3948,
    "preview": "# s06: Context Compact\n\n`s01 > s02 > s03 > s04 > s05 > [ s06 ] | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"コンテキストはいつか溢れる、空"
  },
  {
    "path": "docs/ja/s07-task-system.md",
    "chars": 3950,
    "preview": "# s07: Task System\n\n`s01 > s02 > s03 > s04 > s05 > s06 | [ s07 ] s08 > s09 > s10 > s11 > s12`\n\n> *\"大きな目標を小タスクに分解し、順序付けし、"
  },
  {
    "path": "docs/ja/s08-background-tasks.md",
    "chars": 3343,
    "preview": "# s08: Background Tasks\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > [ s08 ] s09 > s10 > s11 > s12`\n\n> *\"遅い操作はバックグラウンドへ、エ"
  },
  {
    "path": "docs/ja/s09-agent-teams.md",
    "chars": 4155,
    "preview": "# s09: Agent Teams\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > [ s09 ] s10 > s11 > s12`\n\n> *\"一人で終わらないなら、チームメイトに任せる"
  },
  {
    "path": "docs/ja/s10-team-protocols.md",
    "chars": 3396,
    "preview": "# s10: Team Protocols\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > [ s10 ] s11 > s12`\n\n> *\"チームメイト間には統一の通信ルールが"
  },
  {
    "path": "docs/ja/s11-autonomous-agents.md",
    "chars": 4260,
    "preview": "# s11: Autonomous Agents\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > [ s11 ] s12`\n\n> *\"チームメイトが自らボードを見て"
  },
  {
    "path": "docs/ja/s12-worktree-task-isolation.md",
    "chars": 4434,
    "preview": "# s12: Worktree + Task Isolation\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > [ s12 ]`\n\n> *\"各自のディ"
  },
  {
    "path": "docs/zh/s01-the-agent-loop.md",
    "chars": 3075,
    "preview": "# s01: The Agent Loop (智能体循环)\n\n`[ s01 ] s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"One loop &"
  },
  {
    "path": "docs/zh/s02-tool-use.md",
    "chars": 2959,
    "preview": "# s02: Tool Use (工具使用)\n\n`s01 > [ s02 ] s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"加一个工具, 只加一个 handl"
  },
  {
    "path": "docs/zh/s03-todo-write.md",
    "chars": 2915,
    "preview": "# s03: TodoWrite (待办写入)\n\n`s01 > s02 > [ s03 ] s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"没有计划的 agent 走哪算哪"
  },
  {
    "path": "docs/zh/s04-subagent.md",
    "chars": 3082,
    "preview": "# s04: Subagents (子智能体)\n\n`s01 > s02 > s03 > [ s04 ] s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"大任务拆小, 每个小任务干净的上"
  },
  {
    "path": "docs/zh/s05-skill-loading.md",
    "chars": 3211,
    "preview": "# s05: Skills (技能加载)\n\n`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"用到什么知识, 临时加载什么知识\"* "
  },
  {
    "path": "docs/zh/s06-context-compact.md",
    "chars": 3897,
    "preview": "# s06: Context Compact (上下文压缩)\n\n`s01 > s02 > s03 > s04 > s05 > [ s06 ] | s07 > s08 > s09 > s10 > s11 > s12`\n\n> *\"上下文总会满,"
  },
  {
    "path": "docs/zh/s07-task-system.md",
    "chars": 3783,
    "preview": "# s07: Task System (任务系统)\n\n`s01 > s02 > s03 > s04 > s05 > s06 | [ s07 ] s08 > s09 > s10 > s11 > s12`\n\n> *\"大目标要拆成小任务, 排好序"
  },
  {
    "path": "docs/zh/s08-background-tasks.md",
    "chars": 3263,
    "preview": "# s08: Background Tasks (后台任务)\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > [ s08 ] s09 > s10 > s11 > s12`\n\n> *\"慢操作丢后台, a"
  },
  {
    "path": "docs/zh/s09-agent-teams.md",
    "chars": 4115,
    "preview": "# s09: Agent Teams (智能体团队)\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > [ s09 ] s10 > s11 > s12`\n\n> *\"任务太大一个人干不完, 要"
  },
  {
    "path": "docs/zh/s10-team-protocols.md",
    "chars": 3305,
    "preview": "# s10: Team Protocols (团队协议)\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > [ s10 ] s11 > s12`\n\n> *\"队友之间要有统一的沟通"
  },
  {
    "path": "docs/zh/s11-autonomous-agents.md",
    "chars": 4104,
    "preview": "# s11: Autonomous Agents (自治智能体)\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > [ s11 ] s12`\n\n> *\"队友自己看看板"
  },
  {
    "path": "docs/zh/s12-worktree-task-isolation.md",
    "chars": 4187,
    "preview": "# s12: Worktree + Task Isolation (Worktree 任务隔离)\n\n`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > [ s"
  },
  {
    "path": "requirements.txt",
    "chars": 39,
    "preview": "anthropic>=0.25.0\npython-dotenv>=1.0.0\n"
  },
  {
    "path": "skills/agent-builder/SKILL.md",
    "chars": 4713,
    "preview": "---\nname: agent-builder\ndescription: |\n  Design and build AI agents for any domain. Use when users:\n  (1) ask to \"create"
  },
  {
    "path": "skills/agent-builder/references/agent-philosophy.md",
    "chars": 8030,
    "preview": "# The Philosophy of Agent Harness Engineering\n\n> **The model already knows how to be an agent. Your job is to build it a"
  },
  {
    "path": "skills/agent-builder/references/minimal-agent.py",
    "chars": 4148,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nMinimal Agent Template - Copy and customize this.\n\nThis is the simplest possible working agen"
  },
  {
    "path": "skills/agent-builder/references/subagent-pattern.py",
    "chars": 7773,
    "preview": "\"\"\"\nSubagent Pattern - How to implement Task tool for context isolation.\n\nThe key insight: spawn child agents with ISOLA"
  },
  {
    "path": "skills/agent-builder/references/tool-templates.py",
    "chars": 7881,
    "preview": "\"\"\"\nTool Templates - Copy and customize these for your agent.\n\nEach tool needs:\n1. Definition (JSON schema for the model"
  },
  {
    "path": "skills/agent-builder/scripts/init_agent.py",
    "chars": 9933,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nAgent Scaffold Script - Create a new agent project with best practices.\n\nUsage:\n    python in"
  },
  {
    "path": "skills/code-review/SKILL.md",
    "chars": 4266,
    "preview": "---\nname: code-review\ndescription: Perform thorough code reviews with security, performance, and maintainability analysi"
  },
  {
    "path": "skills/mcp-builder/SKILL.md",
    "chars": 4918,
    "preview": "---\nname: mcp-builder\ndescription: Build MCP (Model Context Protocol) servers that give Claude new capabilities. Use whe"
  },
  {
    "path": "skills/pdf/SKILL.md",
    "chars": 2581,
    "preview": "---\nname: pdf\ndescription: Process PDF files - extract text, create PDFs, merge documents. Use when user asks to read PD"
  },
  {
    "path": "web/.gitignore",
    "chars": 492,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": "web/README.md",
    "chars": 1450,
    "preview": "This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-re"
  },
  {
    "path": "web/next.config.ts",
    "chars": 181,
    "preview": "import type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n  output: \"export\",\n  images: { unoptimized: t"
  },
  {
    "path": "web/package.json",
    "chars": 916,
    "preview": "{\n  \"name\": \"web\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"extract\": \"tsx scripts/extract-content.t"
  },
  {
    "path": "web/postcss.config.mjs",
    "chars": 94,
    "preview": "const config = {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "web/scripts/extract-content.ts",
    "chars": 8923,
    "preview": "import * as fs from \"fs\";\nimport * as path from \"path\";\nimport type {\n  AgentVersion,\n  VersionDiff,\n  DocContent,\n  Ver"
  },
  {
    "path": "web/src/app/[locale]/(learn)/[version]/client.tsx",
    "chars": 2571,
    "preview": "\"use client\";\n\nimport { ArchDiagram } from \"@/components/architecture/arch-diagram\";\nimport { WhatsNew } from \"@/compone"
  },
  {
    "path": "web/src/app/[locale]/(learn)/[version]/diff/diff-content.tsx",
    "chars": 7641,
    "preview": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport Link from \"next/link\";\nimport { useLocale } from \"@/lib/i18n\";\nim"
  },
  {
    "path": "web/src/app/[locale]/(learn)/[version]/diff/page.tsx",
    "chars": 403,
    "preview": "import { LEARNING_PATH } from \"@/lib/constants\";\nimport { DiffPageContent } from \"./diff-content\";\n\nexport function gene"
  },
  {
    "path": "web/src/app/[locale]/(learn)/[version]/page.tsx",
    "chars": 4601,
    "preview": "import Link from \"next/link\";\nimport { LEARNING_PATH, VERSION_META, LAYERS } from \"@/lib/constants\";\nimport { LayerBadge"
  },
  {
    "path": "web/src/app/[locale]/(learn)/compare/page.tsx",
    "chars": 12764,
    "preview": "\"use client\";\n\nimport { useState, useMemo } from \"react\";\nimport { useLocale, useTranslations } from \"@/lib/i18n\";\nimpor"
  },
  {
    "path": "web/src/app/[locale]/(learn)/layers/page.tsx",
    "chars": 5507,
    "preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { useTranslations, useLocale } from \"@/lib/i18n\";\nimport { LAYERS, V"
  },
  {
    "path": "web/src/app/[locale]/(learn)/layout.tsx",
    "chars": 280,
    "preview": "import { Sidebar } from \"@/components/layout/sidebar\";\n\nexport default function LearnLayout({\n  children,\n}: {\n  childre"
  },
  {
    "path": "web/src/app/[locale]/(learn)/timeline/page.tsx",
    "chars": 468,
    "preview": "\"use client\";\n\nimport { useTranslations } from \"@/lib/i18n\";\nimport { Timeline } from \"@/components/timeline/timeline\";\n"
  },
  {
    "path": "web/src/app/[locale]/layout.tsx",
    "chars": 1821,
    "preview": "import type { Metadata } from \"next\";\nimport { I18nProvider } from \"@/lib/i18n\";\nimport { Header } from \"@/components/la"
  },
  {
    "path": "web/src/app/[locale]/page.tsx",
    "chars": 9571,
    "preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { useTranslations, useLocale } from \"@/lib/i18n\";\nimport { LEARNING_"
  },
  {
    "path": "web/src/app/globals.css",
    "chars": 9995,
    "preview": "@import \"tailwindcss\";\n\n@custom-variant dark (&:where(.dark, .dark *));\n\n:root {\n  --color-layer-tools: #3B82F6;\n  --col"
  },
  {
    "path": "web/src/app/page.tsx",
    "chars": 104,
    "preview": "import { redirect } from \"next/navigation\";\n\nexport default function RootPage() {\n  redirect(\"/en/\");\n}\n"
  },
  {
    "path": "web/src/components/architecture/arch-diagram.tsx",
    "chars": 7352,
    "preview": "\"use client\";\n\nimport { motion } from \"framer-motion\";\nimport { cn } from \"@/lib/utils\";\nimport { LAYERS } from \"@/lib/c"
  },
  {
    "path": "web/src/components/architecture/design-decisions.tsx",
    "chars": 4757,
    "preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useTr"
  },
  {
    "path": "web/src/components/architecture/execution-flow.tsx",
    "chars": 6281,
    "preview": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { useTranslat"
  },
  {
    "path": "web/src/components/architecture/message-flow.tsx",
    "chars": 2548,
    "preview": "\"use client\";\n\nimport { useState, useEffect, useRef } from \"react\";\nimport { motion, AnimatePresence } from \"framer-moti"
  },
  {
    "path": "web/src/components/code/source-viewer.tsx",
    "chars": 3446,
    "preview": "\"use client\";\n\nimport { useMemo } from \"react\";\n\ninterface SourceViewerProps {\n  source: string;\n  filename: string;\n}\n\n"
  },
  {
    "path": "web/src/components/diff/code-diff.tsx",
    "chars": 7466,
    "preview": "\"use client\";\n\nimport { useState, useMemo } from \"react\";\nimport { diffLines, Change } from \"diff\";\nimport { cn } from \""
  },
  {
    "path": "web/src/components/diff/whats-new.tsx",
    "chars": 4101,
    "preview": "\"use client\";\n\nimport { motion } from \"framer-motion\";\nimport { useTranslations } from \"@/lib/i18n\";\nimport { Card } fro"
  },
  {
    "path": "web/src/components/docs/doc-renderer.tsx",
    "chars": 2484,
    "preview": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport { useLocale } from \"@/lib/i18n\";\nimport docsData from \"@/data/gen"
  },
  {
    "path": "web/src/components/layout/header.tsx",
    "chars": 5920,
    "preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport { useTranslations, us"
  },
  {
    "path": "web/src/components/layout/sidebar.tsx",
    "chars": 2404,
    "preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport { LAYERS, VERSION_MET"
  },
  {
    "path": "web/src/components/simulator/agent-loop-simulator.tsx",
    "chars": 3665,
    "preview": "\"use client\";\n\nimport { useRef, useEffect, useState } from \"react\";\nimport { AnimatePresence } from \"framer-motion\";\nimp"
  },
  {
    "path": "web/src/components/simulator/simulator-controls.tsx",
    "chars": 3098,
    "preview": "\"use client\";\n\nimport { useTranslations } from \"@/lib/i18n\";\nimport { Play, Pause, SkipForward, RotateCcw } from \"lucide"
  },
  {
    "path": "web/src/components/simulator/simulator-message.tsx",
    "chars": 2899,
    "preview": "\"use client\";\n\nimport { motion } from \"framer-motion\";\nimport { cn } from \"@/lib/utils\";\nimport type { SimStep } from \"@"
  },
  {
    "path": "web/src/components/timeline/timeline.tsx",
    "chars": 7885,
    "preview": "\"use client\";\n\nimport Link from \"next/link\";\nimport { motion } from \"framer-motion\";\nimport { useTranslations, useLocale"
  },
  {
    "path": "web/src/components/ui/badge.tsx",
    "chars": 923,
    "preview": "import { cn } from \"@/lib/utils\";\n\nconst LAYER_COLORS = {\n  tools:\n    \"bg-blue-100 text-blue-800 dark:bg-blue-900/30 da"
  },
  {
    "path": "web/src/components/ui/card.tsx",
    "chars": 840,
    "preview": "import { cn } from \"@/lib/utils\";\n\ninterface CardProps extends React.HTMLAttributes<HTMLDivElement> {\n  children: React."
  },
  {
    "path": "web/src/components/ui/tabs.tsx",
    "chars": 1118,
    "preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface TabsProps {\n  tabs: { id: "
  },
  {
    "path": "web/src/components/visualizations/index.tsx",
    "chars": 1278,
    "preview": "\"use client\";\n\nimport { lazy, Suspense } from \"react\";\nimport { useTranslations } from \"@/lib/i18n\";\n\nconst visualizatio"
  },
  {
    "path": "web/src/components/visualizations/s01-agent-loop.tsx",
    "chars": 15634,
    "preview": "\"use client\";\n\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useSteppedVisualization } from \"@/hooks"
  },
  {
    "path": "web/src/components/visualizations/s02-tool-dispatch.tsx",
    "chars": 13778,
    "preview": "\"use client\";\n\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useSteppedVisualization } from \"@/hooks"
  },
  {
    "path": "web/src/components/visualizations/s03-todo-write.tsx",
    "chars": 11703,
    "preview": "\"use client\";\n\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useSteppedVisualization } from \"@/hooks"
  },
  {
    "path": "web/src/components/visualizations/s04-subagent.tsx",
    "chars": 11419,
    "preview": "\"use client\";\n\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useSteppedVisualization } from \"@/hooks"
  },
  {
    "path": "web/src/components/visualizations/s05-skill-loading.tsx",
    "chars": 16140,
    "preview": "\"use client\";\n\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useSteppedVisualization } from \"@/hooks"
  },
  {
    "path": "web/src/components/visualizations/s06-context-compact.tsx",
    "chars": 16647,
    "preview": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useSte"
  },
  {
    "path": "web/src/components/visualizations/s07-task-system.tsx",
    "chars": 14948,
    "preview": "\"use client\";\n\nimport { useMemo } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { useSteppedVisualization"
  },
  {
    "path": "web/src/components/visualizations/s08-background-tasks.tsx",
    "chars": 19191,
    "preview": "\"use client\";\n\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useSteppedVisualization } from \"@/hooks"
  },
  {
    "path": "web/src/components/visualizations/s09-agent-teams.tsx",
    "chars": 14744,
    "preview": "\"use client\";\n\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useSteppedVisualization } from \"@/hooks"
  },
  {
    "path": "web/src/components/visualizations/s10-team-protocols.tsx",
    "chars": 16977,
    "preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport { useSt"
  },
  {
    "path": "web/src/components/visualizations/s11-autonomous-agents.tsx",
    "chars": 17982,
    "preview": "\"use client\";\n\nimport { motion } from \"framer-motion\";\nimport { useSteppedVisualization } from \"@/hooks/useSteppedVisual"
  },
  {
    "path": "web/src/components/visualizations/s12-worktree-task-isolation.tsx",
    "chars": 12123,
    "preview": "\"use client\";\n\nimport { motion } from \"framer-motion\";\nimport { useSteppedVisualization } from \"@/hooks/useSteppedVisual"
  },
  {
    "path": "web/src/components/visualizations/shared/step-controls.tsx",
    "chars": 3386,
    "preview": "\"use client\";\n\nimport { Play, Pause, SkipBack, SkipForward, RotateCcw } from \"lucide-react\";\nimport { cn } from \"@/lib/u"
  },
  {
    "path": "web/src/data/annotations/s01.json",
    "chars": 4385,
    "preview": "{\n  \"version\": \"s01\",\n  \"decisions\": [\n    {\n      \"id\": \"one-tool-sufficiency\",\n      \"title\": \"Why Bash Alone Is Enoug"
  },
  {
    "path": "web/src/data/annotations/s02.json",
    "chars": 4697,
    "preview": "{\n  \"version\": \"s02\",\n  \"decisions\": [\n    {\n      \"id\": \"four-tools-not-twenty\",\n      \"title\": \"Why Exactly Four Tools"
  },
  {
    "path": "web/src/data/annotations/s03.json",
    "chars": 4395,
    "preview": "{\n  \"version\": \"s03\",\n  \"decisions\": [\n    {\n      \"id\": \"visible-planning\",\n      \"title\": \"Making Plans Visible via To"
  },
  {
    "path": "web/src/data/annotations/s04.json",
    "chars": 4762,
    "preview": "{\n  \"version\": \"s04\",\n  \"decisions\": [\n    {\n      \"id\": \"context-isolation\",\n      \"title\": \"Subagents Get Fresh Contex"
  },
  {
    "path": "web/src/data/annotations/s05.json",
    "chars": 5157,
    "preview": "{\n  \"version\": \"s05\",\n  \"decisions\": [\n    {\n      \"id\": \"tool-result-injection\",\n      \"title\": \"Skills Inject via tool"
  },
  {
    "path": "web/src/data/annotations/s06.json",
    "chars": 6741,
    "preview": "{\n  \"version\": \"s06\",\n  \"decisions\": [\n    {\n      \"id\": \"three-layer-compression\",\n      \"title\": \"Three-Layer Compress"
  },
  {
    "path": "web/src/data/annotations/s07.json",
    "chars": 5584,
    "preview": "{\n  \"version\": \"s07\",\n  \"decisions\": [\n    {\n      \"id\": \"file-based-persistence\",\n      \"title\": \"Tasks Stored as JSON "
  },
  {
    "path": "web/src/data/annotations/s08.json",
    "chars": 5073,
    "preview": "{\n  \"version\": \"s08\",\n  \"decisions\": [\n    {\n      \"id\": \"notification-bus\",\n      \"title\": \"threading.Queue as the Noti"
  },
  {
    "path": "web/src/data/annotations/s09.json",
    "chars": 4512,
    "preview": "{\n  \"version\": \"s09\",\n  \"decisions\": [\n    {\n      \"id\": \"teammate-vs-subagent\",\n      \"title\": \"Persistent Teammates vs"
  },
  {
    "path": "web/src/data/annotations/s10.json",
    "chars": 5005,
    "preview": "{\n  \"version\": \"s10\",\n  \"decisions\": [\n    {\n      \"id\": \"jsonl-inbox\",\n      \"title\": \"JSONL Inbox Files Instead of Sha"
  },
  {
    "path": "web/src/data/annotations/s11.json",
    "chars": 5126,
    "preview": "{\n  \"version\": \"s11\",\n  \"decisions\": [\n    {\n      \"id\": \"polling-not-events\",\n      \"title\": \"Polling for Unclaimed Tas"
  },
  {
    "path": "web/src/data/annotations/s12.json",
    "chars": 6275,
    "preview": "{\n  \"version\": \"s12\",\n  \"decisions\": [\n    {\n      \"id\": \"shared-board-isolated-lanes\",\n      \"title\": \"Shared Task Boar"
  },
  {
    "path": "web/src/data/execution-flows.ts",
    "chars": 15799,
    "preview": "import type { FlowNode, FlowEdge } from \"@/types/agent-data\";\n\nexport interface FlowDefinition {\n  nodes: FlowNode[];\n  "
  },
  {
    "path": "web/src/data/generated/docs.json",
    "chars": 141850,
    "preview": "[\n  {\n    \"version\": \"s01\",\n    \"locale\": \"en\",\n    \"title\": \"s01: The Agent Loop\",\n    \"content\": \"# s01: The Agent Loo"
  },
  {
    "path": "web/src/data/generated/versions.json",
    "chars": 185274,
    "preview": "{\n  \"versions\": [\n    {\n      \"id\": \"s01\",\n      \"filename\": \"s01_agent_loop.py\",\n      \"title\": \"The Agent Loop\",\n     "
  },
  {
    "path": "web/src/data/scenarios/s01.json",
    "chars": 1589,
    "preview": "{\n  \"version\": \"s01\",\n  \"title\": \"The Agent Loop\",\n  \"description\": \"A minimal agent that uses only bash to accomplish t"
  },
  {
    "path": "web/src/data/scenarios/s02.json",
    "chars": 1425,
    "preview": "{\n  \"version\": \"s02\",\n  \"title\": \"Tools\",\n  \"description\": \"Agent with read, write, edit, and bash tools\",\n  \"steps\": [\n"
  },
  {
    "path": "web/src/data/scenarios/s03.json",
    "chars": 2132,
    "preview": "{\n  \"version\": \"s03\",\n  \"title\": \"TodoWrite\",\n  \"description\": \"Agent creates a visible plan before executing\",\n  \"steps"
  },
  {
    "path": "web/src/data/scenarios/s04.json",
    "chars": 1837,
    "preview": "{\n  \"version\": \"s04\",\n  \"title\": \"Subagents\",\n  \"description\": \"Agent spawns a subagent for isolated subtask execution\","
  },
  {
    "path": "web/src/data/scenarios/s05.json",
    "chars": 1662,
    "preview": "{\n  \"version\": \"s05\",\n  \"title\": \"Skills\",\n  \"description\": \"Agent loads external knowledge from SKILL.md files\",\n  \"ste"
  },
  {
    "path": "web/src/data/scenarios/s06.json",
    "chars": 1991,
    "preview": "{\n  \"version\": \"s06\",\n  \"title\": \"Compact\",\n  \"description\": \"Agent compresses context when tokens exceed threshold\",\n  "
  },
  {
    "path": "web/src/data/scenarios/s07.json",
    "chars": 2057,
    "preview": "{\n  \"version\": \"s07\",\n  \"title\": \"Tasks\",\n  \"description\": \"Agent uses persistent file-based tasks with dependencies\",\n "
  },
  {
    "path": "web/src/data/scenarios/s08.json",
    "chars": 2096,
    "preview": "{\n  \"version\": \"s08\",\n  \"title\": \"Background Tasks\",\n  \"description\": \"Agent executes long-running tasks in background t"
  },
  {
    "path": "web/src/data/scenarios/s09.json",
    "chars": 2098,
    "preview": "{\n  \"version\": \"s09\",\n  \"title\": \"Agent Teams\",\n  \"description\": \"Persistent teammates communicate via JSONL inboxes\",\n "
  },
  {
    "path": "web/src/data/scenarios/s10.json",
    "chars": 1751,
    "preview": "{\n  \"version\": \"s10\",\n  \"title\": \"Team Protocols\",\n  \"description\": \"Request-response protocols for shutdown and plan ap"
  },
  {
    "path": "web/src/data/scenarios/s11.json",
    "chars": 2189,
    "preview": "{\n  \"version\": \"s11\",\n  \"title\": \"Autonomous Agents\",\n  \"description\": \"Teammates self-govern with idle cycles and auto-"
  },
  {
    "path": "web/src/data/scenarios/s12.json",
    "chars": 2843,
    "preview": "{\n  \"version\": \"s12\",\n  \"title\": \"Worktree + Task Isolation\",\n  \"description\": \"Use a shared task board with optional wo"
  },
  {
    "path": "web/src/hooks/useDarkMode.ts",
    "chars": 1753,
    "preview": "\"use client\";\n\nimport { useState, useEffect } from \"react\";\n\nexport function useDarkMode(): boolean {\n  const [isDark, s"
  },
  {
    "path": "web/src/hooks/useSimulator.ts",
    "chars": 2305,
    "preview": "\"use client\";\n\nimport { useState, useCallback, useRef, useEffect } from \"react\";\nimport type { SimStep } from \"@/types/a"
  },
  {
    "path": "web/src/hooks/useSteppedVisualization.ts",
    "chars": 2031,
    "preview": "\"use client\";\n\nimport { useState, useCallback, useEffect, useRef } from \"react\";\n\ninterface SteppedVisualizationOptions "
  },
  {
    "path": "web/src/i18n/messages/en.json",
    "chars": 4420,
    "preview": "{\n  \"meta\": { \"title\": \"Learn Claude Code\", \"description\": \"Build a nano Claude Code-like agent from 0 to 1, one mechani"
  },
  {
    "path": "web/src/i18n/messages/ja.json",
    "chars": 3343,
    "preview": "{\n  \"meta\": { \"title\": \"Learn Claude Code\", \"description\": \"0 から 1 へ nano Claude Code-like agent を構築し、毎回 1 つの仕組みを追加\" },\n"
  },
  {
    "path": "web/src/i18n/messages/zh.json",
    "chars": 3018,
    "preview": "{\n  \"meta\": { \"title\": \"Learn Claude Code\", \"description\": \"从 0 到 1 构建 nano Claude Code-like agent,每次只加一个机制\" },\n  \"nav\":"
  },
  {
    "path": "web/src/lib/constants.ts",
    "chars": 4151,
    "preview": "export const VERSION_ORDER = [\n  \"s01\", \"s02\", \"s03\", \"s04\", \"s05\", \"s06\", \"s07\", \"s08\", \"s09\", \"s10\", \"s11\", \"s12\"\n] as"
  },
  {
    "path": "web/src/lib/i18n-server.ts",
    "chars": 584,
    "preview": "import en from \"@/i18n/messages/en.json\";\nimport zh from \"@/i18n/messages/zh.json\";\nimport ja from \"@/i18n/messages/ja.j"
  },
  {
    "path": "web/src/lib/i18n.tsx",
    "chars": 1007,
    "preview": "\"use client\";\nimport { createContext, useContext, ReactNode } from \"react\";\nimport en from \"@/i18n/messages/en.json\";\nim"
  },
  {
    "path": "web/src/lib/utils.ts",
    "chars": 118,
    "preview": "export function cn(...classes: (string | undefined | null | false)[]) {\n  return classes.filter(Boolean).join(\" \");\n}\n"
  },
  {
    "path": "web/src/types/agent-data.ts",
    "chars": 1414,
    "preview": "export interface AgentVersion {\n  id: string;\n  filename: string;\n  title: string;\n  subtitle: string;\n  loc: number;\n  "
  },
  {
    "path": "web/tsconfig.json",
    "chars": 670,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2018\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    "
  },
  {
    "path": "web/vercel.json",
    "chars": 356,
    "preview": "{\n  \"redirects\": [\n    {\n      \"source\": \"/:path(.*)\",\n      \"has\": [\n        {\n          \"type\": \"host\",\n          \"val"
  }
]

About this extraction

This page contains the full source code of the shareAI-lab/learn-claude-code GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 156 files (1.2 MB), approximately 336.2k tokens, and a symbol index with 556 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!