analysis_claude_code/docs/en/s11-autonomous-agents.md
CrazyBoyM a9c71002d2 the model is the agent, the code is the harness
Comprehensive rewrite establishing the harness engineering narrative
across the entire repository.

README (EN/ZH/JA): added "The Model IS the Agent" manifesto with
historical proof (DQN, OpenAI Five, AlphaStar, Tencent Jueyu),
"What an Agent Is NOT" critique, harness engineer role definition,
"Why Claude Code" as masterclass in harness design, and universe
vision. Consistent framing: model = driver, harness = vehicle.

docs (36 files, 3 languages): injected one-line "Harness layer"
callout after the motto in every session document (s01-s12).

agents (13 Python files): added harness framing comment before
each module docstring.

skills/agent-philosophy.md: full rewrite aligned with harness
narrative.
2026-03-18 01:19:34 +08:00

4.6 KiB

s11: Autonomous Agents

s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > [ s11 ] s12

"Teammates scan the board and claim tasks themselves" -- no need for the lead to assign each one.

Harness layer: Autonomy -- models that find work without being told.

Problem

In s09-s10, teammates only work when explicitly told to. The lead must spawn each one with a specific prompt. 10 unclaimed tasks on the board? The lead assigns each one manually. Doesn't scale.

True autonomy: teammates scan the task board themselves, claim unclaimed tasks, work on them, then look for more.

One subtlety: after context compression (s06), the agent might forget who it is. Identity re-injection fixes this.

Solution

Teammate lifecycle with idle cycle:

+-------+
| spawn |
+---+---+
    |
    v
+-------+   tool_use     +-------+
| WORK  | <------------- |  LLM  |
+---+---+                +-------+
    |
    | stop_reason != tool_use (or idle tool called)
    v
+--------+
|  IDLE  |  poll every 5s for up to 60s
+---+----+
    |
    +---> check inbox --> message? ----------> WORK
    |
    +---> scan .tasks/ --> unclaimed? -------> claim -> WORK
    |
    +---> 60s timeout ----------------------> SHUTDOWN

Identity re-injection after compression:
  if len(messages) <= 3:
    messages.insert(0, identity_block)

How It Works

  1. The teammate loop has two phases: WORK and IDLE. When the LLM stops calling tools (or calls idle), the teammate enters IDLE.
def _loop(self, name, role, prompt):
    while True:
        # -- WORK PHASE --
        messages = [{"role": "user", "content": prompt}]
        for _ in range(50):
            response = client.messages.create(...)
            if response.stop_reason != "tool_use":
                break
            # execute tools...
            if idle_requested:
                break

        # -- IDLE PHASE --
        self._set_status(name, "idle")
        resume = self._idle_poll(name, messages)
        if not resume:
            self._set_status(name, "shutdown")
            return
        self._set_status(name, "working")
  1. The idle phase polls inbox and task board in a loop.
def _idle_poll(self, name, messages):
    for _ in range(IDLE_TIMEOUT // POLL_INTERVAL):  # 60s / 5s = 12
        time.sleep(POLL_INTERVAL)
        inbox = BUS.read_inbox(name)
        if inbox:
            messages.append({"role": "user",
                "content": f"<inbox>{inbox}</inbox>"})
            return True
        unclaimed = scan_unclaimed_tasks()
        if unclaimed:
            claim_task(unclaimed[0]["id"], name)
            messages.append({"role": "user",
                "content": f"<auto-claimed>Task #{unclaimed[0]['id']}: "
                           f"{unclaimed[0]['subject']}</auto-claimed>"})
            return True
    return False  # timeout -> shutdown
  1. Task board scanning: find pending, unowned, unblocked tasks.
def scan_unclaimed_tasks() -> list:
    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
  1. Identity re-injection: when context is too short (compression happened), insert an identity block.
if len(messages) <= 3:
    messages.insert(0, {"role": "user",
        "content": f"<identity>You are '{name}', role: {role}, "
                   f"team: {team_name}. Continue your work.</identity>"})
    messages.insert(1, {"role": "assistant",
        "content": f"I am {name}. Continuing."})

What Changed From s10

Component Before (s10) After (s11)
Tools 12 14 (+idle, +claim_task)
Autonomy Lead-directed Self-organizing
Idle phase None Poll inbox + task board
Task claiming Manual only Auto-claim unclaimed tasks
Identity System prompt + re-injection after compress
Timeout None 60s idle -> auto shutdown

Try It

cd learn-claude-code
python agents/s11_autonomous_agents.py
  1. Create 3 tasks on the board, then spawn alice and bob. Watch them auto-claim.
  2. Spawn a coder teammate and let it find work from the task board itself
  3. Create tasks with dependencies. Watch teammates respect the blocked order.
  4. Type /tasks to see the task board with owners
  5. Type /team to monitor who is working vs idle