refactor: restructure repo for cleaner GitHub landing page

Reduce visible top-level items from 21 to 14:
- Untrack test-results/, tmp/, public symlink (added to .gitignore)
- Move agent-teams/ → docs/agent-teams/
- Move mobile-test/ → test/mobile/
- Move tools/remotion/ → scripts/remotion/
- Move eslint.config.js, vitest.config.ts → config/

All path references updated across CLAUDE.md, package.json,
.prettierignore, vitest configs, and capture scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
arkon
2026-04-03 16:23:54 +02:00
co-authored by Claude Opus 4.6
parent 5078f5251d
commit 7101e64800
82 changed files with 53 additions and 46 deletions
+244
View File
@@ -0,0 +1,244 @@
# Claude Code Agent Teams — Reference
> Experimental feature (Feb 2026). Enable per-session via env var.
> Updated with experiment findings from 2026-02-12.
## Enabling
```bash
# Environment variable (set before starting Claude Code)
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
# In .claude/settings.local.json (case-scoped)
{
"env": {
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
}
}
# Note: "teammateMode" is NOT a valid settings key (validation rejects it).
# Display mode defaults to "in-process". For tmux, pass --teammate-mode flag via CLI.
```
## Filesystem Paths (Verified)
| Resource | Path |
|----------|------|
| Team config | `~/.claude/teams/{team-name}/config.json` |
| Teammate inboxes | `~/.claude/teams/{team-name}/inboxes/{name}.json` |
| Shared tasks | `~/.claude/tasks/{team-name}/` |
| Teammate transcripts | `~/.claude/projects/{hash}/{leadSessionId}/subagents/agent-{id}.jsonl` |
Note: Teammate transcripts appear in the **standard subagent directory** under the lead's session, NOT as separate top-level sessions.
### config.json format (verified)
```json
{
"name": "research-watchers",
"description": "Team description...",
"createdAt": 1770875105373,
"leadAgentId": "team-lead@research-watchers",
"leadSessionId": "461daa80-94ec-4e5e-a1bb-0518f78311bc",
"members": [
{
"agentId": "team-lead@research-watchers",
"name": "team-lead",
"agentType": "team-lead",
"model": "claude-opus-4-6",
"joinedAt": 1770875105373,
"tmuxPaneId": "",
"cwd": "/path/to/project",
"subscriptions": []
},
{
"agentId": "fs-researcher@research-watchers",
"name": "fs-researcher",
"agentType": "general-purpose",
"model": "claude-opus-4-6",
"prompt": "Full spawn prompt...",
"color": "blue",
"planModeRequired": false,
"joinedAt": 1770875126680,
"tmuxPaneId": "in-process",
"cwd": "/path/to/project",
"subscriptions": [],
"backendType": "in-process"
}
]
}
```
Key fields: `agentId` format is `{name}@{teamName}`, `leadSessionId` links to Codeman session, `backendType` indicates display mode, `color` for UI theming.
### Task file format (verified)
```json
{
"id": "1",
"subject": "Research Node.js fs.watch on Linux vs macOS",
"description": "Full description...",
"activeForm": "Researching Node.js fs.watch Linux vs macOS",
"status": "in_progress",
"blocks": [],
"blockedBy": [],
"owner": "fs-researcher"
}
```
Internal teammate tracking tasks have `"metadata": { "_internal": true }`.
Task states: `pending` → `in_progress` → `completed`. File locking via `.lock.lock` directory (mkdir-based atomic lock).
### Inbox message format (verified)
```json
[
{
"from": "team-lead",
"text": "{\"type\":\"task_assignment\",\"taskId\":\"1\",\"subject\":\"...\",\"assignedBy\":\"team-lead\",\"timestamp\":\"...\"}",
"timestamp": "2026-02-12T05:45:18.176Z",
"read": false
}
]
```
`text` is double-encoded JSON. Message types: `task_assignment`, `shutdown_request`, `shutdown_response`. File locking via `.json.lock` directory.
## Communication Model (CORRECTED)
**Hybrid: tool + filesystem.** The `SendMessage` tool writes to filesystem inbox files at `~/.claude/teams/{name}/inboxes/{teammate}.json`.
Each teammate AND the lead has an inbox JSON file. Messages are JSON arrays with `from`, `text` (double-encoded JSON), `timestamp`, `read` fields.
Message types observed:
- **task_assignment**: Lead assigns task to teammate
- **shutdown_request**: Lead asks teammate to shut down
- **shutdown_response**: Teammate confirms shutdown
- (Also: `message`, `broadcast`, `plan_approval_response` per docs)
**Implication:** We can intercept messages by watching inbox files AND potentially inject messages by writing to them (respecting `.json.lock` directory locking).
## Process Model (CORRECTED)
**Teammates are IN-PROCESS THREADS, not separate OS processes.**
In `in-process` mode (the default), all teammates run as threads within the single `claude` process. Only 1 claude process exists per Codeman session, regardless of team size.
This means:
- No separate PIDs to track per teammate
- All teammates share the lead's environment variables
- Lower resource overhead than separate processes
- Subagent transcript files still created (for progress tracking)
## Display Modes
| Mode | Trigger | UI | Requirement |
|------|---------|-----|------------|
| **in-process** (default) | Default | Shift+Up/Down to switch, Ctrl+T for tasks | Any terminal |
| **tmux** | `--teammate-mode tmux` | Split panes | tmux installed |
| **iTerm2** | Auto-detected | Native split panes | iTerm2 + `it2` CLI |
**For Codeman: use `in-process` only.** Codeman manages its own tmux sessions externally.
**In-process UI elements:**
- Status bar: `@main @teammate1 @teammate2 ...` with `shift+↑ to expand`
- Task list: Checkboxes with assignments `(@teammate-name)`
- Hint: `ctrl+t to show teammates`
## Hooks
Two new hook types for quality gates (verified in settings schema):
### TeammateIdle
Fires when a teammate is about to go idle.
- Exit code 0: Allow idle (normal)
- Exit code 2: Send feedback back, keep teammate working
### TaskCompleted
Fires when a task is being marked complete.
- Exit code 0: Allow completion
- Exit code 2: Prevent completion, send feedback
These are configured in `.claude/settings.local.json` alongside existing Codeman hooks.
## Subagent-Watcher Compatibility (Verified)
**Teammates appear as standard subagents.** They create transcript files at:
```
~/.claude/projects/{hash}/{leadSessionId}/subagents/agent-{id}.jsonl
```
Codeman's existing `subagent-watcher.ts` discovers them automatically. They appear in `/api/subagents` with status "active".
**Distinguishing teammates from regular subagents:**
- Description field starts with `<teammate-message teammate_id= team`
- Cross-reference with `~/.claude/teams/{name}/config.json` members
**Sub-subagents:** Teammates can spawn their own Task tool subagents, creating a 3-level hierarchy.
## Cleanup Behavior (Verified)
When the lead runs cleanup:
1. Shutdown requests sent to all teammate inboxes
2. Teammates shut down gracefully
3. ALL filesystem artifacts deleted:
- Inbox files and directory
- Config.json
- Team directory
- All task files
- Task directory
4. Cleanup is atomic — all files removed in the same second
## Comparison with Subagents (Task tool)
| Aspect | Subagents (Task tool) | Agent Teams |
|--------|----------------------|-------------|
| Spawn method | Claude's built-in Task tool | Explicit team creation |
| Process model | In-process threads | In-process threads (same!) |
| Discovery | `subagents/agent-{id}.jsonl` only | BOTH subagent dir + `~/.claude/teams/` |
| Communication | None (fire-and-forget) | Filesystem inboxes + SendMessage tool |
| Shared state | None | Shared task list + inboxes |
| Task tracking | Per-agent, no coordination | Shared with dependencies & ownership |
| Lifecycle | Auto-cleanup on completion | Lead cleanup (deletes all artifacts) |
| Sub-nesting | Can spawn sub-subagents | Teammates can spawn subagents too |
| Cost | Lower (single context) | Higher (N context windows) |
| Duration | Short-lived (seconds-minutes) | Longer-lived (minutes-hours) |
## Limitations
- No session resumption with in-process teammates (`/resume` doesn't restore them)
- One team per session, no nested teams
- Lead is fixed (cannot promote teammate)
- Permissions set at spawn (change individually after)
- Split panes require tmux or iTerm2 (not Screen)
- Task status can lag (teammates may fail to mark complete)
- Shutdown can be slow (waits for current tool call)
## Useful Commands
```bash
# Check if teams exist
ls ~/.claude/teams/
# Check team config
cat ~/.claude/teams/{name}/config.json | jq .
# Check teammate inboxes
cat ~/.claude/teams/{name}/inboxes/{teammate}.json | jq .
# Check team tasks
ls ~/.claude/tasks/{name}/
for f in ~/.claude/tasks/{name}/*.json; do cat "$f" | jq .; done
# Count Claude processes (teammates are threads, not processes)
ps aux | grep '[c]laude' | grep -v grep
# Check subagent detection of teammates
curl -s http://localhost:3000/api/subagents | jq '.data[] | select(.description | startswith("<teammate"))'
# Team interaction (in-process mode)
# Shift+Up/Down: Switch between teammates
# Enter: View teammate session
# Escape: Interrupt teammate's turn
# Ctrl+T: Toggle task list
```
+171
View File
@@ -0,0 +1,171 @@
# Codeman Agent Teams Integration — Design (Approach C: Hybrid)
> Updated 2026-02-12 with experiment findings. See `experiment-log.md` for raw data.
## Overview
Approach C combines filesystem monitoring (for team/task discovery and inbox watching) with the existing subagent-watcher (for live transcript tailing) and adjusted idle detection (to account for active teammates). The key finding from our experiment is that **teammates already appear as standard subagents**, so most infrastructure exists — we mainly need team awareness and idle detection fixes.
## Components
### 1. TeamWatcher (`src/team-watcher.ts`)
Monitors `~/.claude/teams/` for team creation/removal and tracks active teams.
**Discovery mechanism:**
- Poll `~/.claude/teams/` for directories (team names) every 3-5 seconds
- When found: parse `config.json` to get:
- `leadSessionId` → map to Codeman session
- `members` array → teammate names, agentIds, colors, models
- Watch for directory deletion (cleanup signal)
**CORRECTED from pre-experiment design:**
- ~~Each teammate has a separate Claude Code process~~ → Teammates are **in-process threads**, not separate processes
- ~~Find via `ps aux` + `/proc` PID matching~~ → Not needed, no separate PIDs
- Teammate transcripts are at `subagents/agent-{id}.jsonl` (standard subagent path), NOT separate session transcripts
**Association:**
- `config.json.leadSessionId` → Codeman session ID (direct match!)
- Each member's `agentId` (e.g., `fs-researcher@research-watchers`) → links to subagent files
- `agentType: "team-lead"` vs `"general-purpose"` distinguishes lead from teammates
**Inbox monitoring:**
- Watch `~/.claude/teams/{name}/inboxes/` for new messages
- Each teammate has a JSON file with message array
- Messages are double-encoded JSON with `from`, `text`, `timestamp`, `read` fields
- Message types: `task_assignment`, `shutdown_request`, `shutdown_response`
### 2. Team-Aware Idle Detection (HIGHEST PRIORITY)
**Problem (confirmed by experiment):** Lead session shows status "idle" in Codeman while teammates are actively working. Token count continues climbing but Codeman thinks the session is inactive.
**Solution:**
- Before declaring a session idle, check if it's a team lead
- If team lead: check `~/.claude/teams/*/config.json` for this session's `leadSessionId`
- If active team exists: check task files in `~/.claude/tasks/{team-name}/`
- Any task with `status: "in_progress"` → suppress idle detection
- All tasks `completed` AND no non-`_internal` tasks pending → allow idle
- Fallback: check subagent-watcher for active subagents on this session
**Integration points:**
- `src/ai-idle-checker.ts` — add team-awareness check before AI idle analysis
- `src/respawn-controller.ts` — consult TeamWatcher before transitioning to idle states
- `src/session.ts` — expose `hasActiveTeam()` method
**Liveness check (simplified from pre-experiment):**
- ~~Check `/proc/{pid}` existence~~ → Not needed (no separate processes)
- Check task file status instead (filesystem-based)
- Check subagent-watcher for active subagents under this session
### 3. Shared Task List UI
**Display:** New panel in web UI showing the team's shared task list.
**Data source:** Poll `~/.claude/tasks/{team-name}/` for task JSON files.
**Task file structure (verified):**
```json
{
"id": "1",
"subject": "Research Node.js fs.watch",
"description": "Full description...",
"activeForm": "Researching Node.js fs.watch",
"status": "in_progress", // pending | in_progress | completed
"blocks": [],
"blockedBy": [],
"owner": "fs-researcher" // Empty string = unassigned
}
```
Internal tracking tasks: `{ "metadata": { "_internal": true } }` — filter these from display.
**UI elements:**
- Task subject, status badge (color-coded), owner (teammate name with color)
- Dependency visualization (blockedBy indicators)
- Progress bar (completed / total non-internal tasks)
- Real-time updates via SSE
**API endpoint:** `GET /api/sessions/:id/team-tasks` → returns parsed task files
**Locking:** Respect `.lock.lock` directory lock when reading (skip if locked, retry next poll).
### 4. Teammate Display
**Decision: Option A — Enhanced subagent floating windows.**
Since teammates already appear as subagents in the existing infrastructure, we enhance rather than replace:
- **Badge:** Add "Teammate" badge to subagent windows for agents matching team config
- **Color:** Use teammate's `color` field from config.json (blue, green, yellow)
- **Name:** Show teammate name instead of agent ID
- **Persistence:** Teammate windows should stay open longer (they're longer-lived than regular subagents)
- **Status:** Show task assignment and progress from task files
**Detection logic:**
```
For each subagent detected by subagent-watcher:
1. Check if description starts with "<teammate-message"
2. OR cross-reference agentId with active team config members
3. If match → apply teammate badge, color, name
```
### 5. Inbox/Message Display
**CORRECTED: Inboxes ARE filesystem-based.**
Communication uses filesystem inbox files at `~/.claude/teams/{name}/inboxes/{teammate}.json`. We can:
1. **Watch inbox files** for real-time message monitoring
2. **Parse message types** for display:
- `task_assignment` → "Lead assigned Task #1 to fs-researcher"
- `shutdown_request` → "Lead requested shutdown"
- `shutdown_response` → "Teammate confirmed shutdown"
3. **Display as timeline** in team panel
**Potential for interaction (not tested, future work):**
- Write to teammate inbox files to inject messages
- Must respect `.json.lock` directory locking protocol
- Could enable "nudge" or "redirect" functionality from Codeman UI
## Answered Questions (from experiment)
| # | Question | Answer |
|---|----------|--------|
| 1 | Teammates in subagents dir? | **YES** — standard `subagents/agent-{id}.jsonl` path |
| 2 | subagent-watcher detects them? | **YES** — automatically, no changes needed |
| 3 | Task file structure? | Numbered JSON files with subject, status, owner, dependencies |
| 4 | Env var inheritance? | **YES** — in-process threads share parent's env |
| 5 | Processes per teammate? | **ZERO** — threads, not processes |
| 6 | config.json format? | Rich: name, agentId, agentType, model, prompt, color, backendType |
| 7 | Interact via stdin? | N/A (threads) — can interact via inbox files instead |
| 8 | In-process under Screen? | Works fine — single claude process, threads handle teammates |
| 9 | Hook events from teammates? | TeammateIdle + TaskCompleted hooks available in settings schema |
| 10 | Process tree? | Single process with threads — no child processes |
## Existing Infrastructure to Leverage
| Component | Reuse for | Status |
|-----------|-----------|--------|
| `subagent-watcher.ts` | Teammate transcript tailing | **Already works** |
| Subagent floating windows (`app.js`) | Teammate activity display | **Already works** (needs badges) |
| `task-tracker.ts` | Background task tracking patterns | Reuse patterns |
| LRUMap, StaleExpirationMap | Bounded caches for team state | Available |
| SSE broadcast | Real-time UI updates | Available |
| ~~`/proc` PID checking~~ | ~~Teammate liveness~~ | **Not needed** (threads) |
| `file-stream-manager.ts` | Watch inbox/task files | Available |
## Implementation Order (Revised)
1. **Team-aware idle detection** — prevent premature respawn/auto-compact (CRITICAL)
2. **TeamWatcher** — poll `~/.claude/teams/`, parse config.json, track active teams
3. **Teammate badge in subagent windows** — mark teammate subagents with name/color
4. **Team tasks API + UI** — `GET /api/sessions/:id/team-tasks` + task list panel
5. **Inbox monitoring** — watch inbox files, display message timeline
6. **TeammateIdle/TaskCompleted hooks** — add to Codeman's hooks config generator
## What We DON'T Need to Build
- ~~Process discovery for teammates~~ (they're threads)
- ~~Custom transcript tailing~~ (subagent-watcher handles it)
- ~~Separate teammate window infrastructure~~ (subagent windows work)
- ~~Message interception via transcript parsing~~ (inbox files are simpler)
+468
View File
@@ -0,0 +1,468 @@
# Agent Teams Experiment Log
> Experiment date: 2026-02-12
> Test case: `~/codeman-cases/agent-teams-test/`
> Team name: `research-watchers`
> Teammates: 3 (fs-researcher, perf-researcher, api-researcher)
> Lead session: `461daa80-94ec-4e5e-a1bb-0518f78311bc`
> Duration: ~3 minutes (06:45:01 → 06:48:07)
## Pre-Experiment State
```
~/.claude/teams/ — did NOT exist
~/.claude/tasks/ — 75 UUID-named directories (from regular Task tool subagents)
Claude processes — 7 (including watchers)
settings.local.json — edited to add CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
```
## Experiment Prompt
```
Create an agent team with 3 teammates to research the following topics in parallel:
Teammate 1 fs-researcher researches how Node.js fs.watch works on Linux vs macOS.
Teammate 2 perf-researcher researches inotify performance limits and alternatives.
Teammate 3 api-researcher researches the inotifywait command-line API.
Have each teammate write a brief summary of their findings in a separate file.
Name the team research-watchers.
```
---
## Question 1: What exact filesystem artifacts do agent teams create?
**Expected:** `~/.claude/teams/research-watchers/config.json` and `~/.claude/tasks/research-watchers/`
**Actual: CONFIRMED + SURPRISE inboxes/ directory**
```
~/.claude/teams/research-watchers/
├── config.json # Team config (members, lead, metadata)
└── inboxes/ # Filesystem-based messaging!
├── api-researcher.json # Per-teammate inbox
├── fs-researcher.json
├── perf-researcher.json
└── team-lead.json # Lead also has an inbox
~/.claude/tasks/research-watchers/
├── .lock # Empty file (presence = lock indicator?)
├── 1.json # Task: Research Node.js fs.watch
├── 2.json # Task: Research inotify performance
├── 3.json # Task: Research inotifywait CLI
├── 4.json # Internal: fs-researcher spawn tracking
├── 5.json # Internal: perf-researcher spawn tracking
└── 6.json # Internal: api-researcher spawn tracking
```
Subagent transcripts also appear in the standard subagent directory:
```
~/.claude/projects/-home-arkon-codeman-cases-agent-teams-test/
└── 461daa80.../
├── 461daa80...jsonl # Lead session transcript
└── subagents/
├── agent-ae50544.jsonl # Teammate: fs-researcher
├── agent-aa20c65.jsonl # Teammate: perf-researcher
├── agent-a29de32.jsonl # Teammate: api-researcher
├── agent-a04968e.jsonl # Sub-subagent (teammate's Task tool)
├── agent-a0d372e.jsonl # Sub-subagent
├── agent-a2ff939.jsonl # Sub-subagent
├── agent-a89ad82.jsonl # Sub-subagent
├── agent-aa1efc7.jsonl # Sub-subagent
└── agent-ab0ef07.jsonl # Sub-subagent
```
**Cleanup:** At 06:48:02, the lead deleted ALL artifacts — inboxes, config, tasks, the team directory itself. Clean removal.
---
## Question 2: Is the mailbox/communication filesystem-based or tool-based?
**Expected:** Tool-based (SendMessage tool), NOT filesystem
**Actual: BOTH! Hybrid — tool triggers filesystem writes.**
Communication uses the `SendMessage` tool internally, but the actual message delivery is via **filesystem inbox files**. Each teammate has `~/.claude/teams/{name}/inboxes/{teammate}.json` containing a JSON array of messages.
**Inbox message format:**
```json
[
{
"from": "team-lead",
"text": "{\"type\":\"task_assignment\",\"taskId\":\"1\",\"subject\":\"Research Node.js fs.watch...\",\"assignedBy\":\"team-lead\",\"timestamp\":\"...\"}",
"timestamp": "2026-02-12T05:45:18.176Z",
"read": false
}
]
```
Key observations:
- `text` field is a **JSON string** (double-encoded) containing a typed message object
- Message types observed: `task_assignment`, `shutdown_request`, `shutdown_response`
- `read` field tracks whether teammate has processed the message (false → true)
- **File locking** via `.json.lock` directories (mkdir-based atomic lock, created then deleted)
- Lead also has an inbox (`team-lead.json`) for receiving messages FROM teammates
**Implication for Codeman:** We CAN intercept messages by watching inbox JSON files! We can also potentially inject messages by writing to inbox files.
---
## Question 3: Do teammates appear in the subagents directory?
**Expected:** Unclear
**Actual: YES! Teammates appear as standard subagents.**
Teammates create transcript files at:
```
~/.claude/projects/{hash}/{leadSessionId}/subagents/agent-{agentId}.jsonl
```
This is the **exact same path pattern** that regular Task tool subagents use. The existing `subagent-watcher.ts` successfully discovers them.
Codeman's `/api/subagents` endpoint returned them with status "active":
```
Agent: ae50544 Status: active Tools: 8 Model: claude-opus-4-6
Desc: <teammate-message teammate_id= team
Agent: aa20c65 Status: active Tools: 9 Model: claude-opus-4-6
Desc: <teammate-message teammate_id= team
Agent: a29de32 Status: active Tools: 7 Model: claude-opus-4-6
Desc: <teammate-message teammate_id= team
```
**Distinguishing teammates from regular subagents:**
- Description starts with `<teammate-message teammate_id= team` (a unique marker)
- We can also cross-reference with `~/.claude/teams/{name}/config.json` members list
**Sub-subagents:** Teammates can spawn their own Task tool subagents. 3 teammates spawned 6 additional subagent files (9 total in the subagents directory).
---
## Question 4: What does config.json actually look like?
**Actual config.json (with all 3 teammates):**
```json
{
"name": "research-watchers",
"description": "Research team investigating file watching mechanisms...",
"createdAt": 1770875105373,
"leadAgentId": "team-lead@research-watchers",
"leadSessionId": "461daa80-94ec-4e5e-a1bb-0518f78311bc",
"members": [
{
"agentId": "team-lead@research-watchers",
"name": "team-lead",
"agentType": "team-lead",
"model": "claude-opus-4-6",
"joinedAt": 1770875105373,
"tmuxPaneId": "",
"cwd": "/home/arkon/codeman-cases/agent-teams-test",
"subscriptions": []
},
{
"agentId": "fs-researcher@research-watchers",
"name": "fs-researcher",
"agentType": "general-purpose",
"model": "claude-opus-4-6",
"prompt": "You are \"fs-researcher\" on the \"research-watchers\" team...",
"color": "blue",
"planModeRequired": false,
"joinedAt": 1770875126680,
"tmuxPaneId": "in-process",
"cwd": "/home/arkon/codeman-cases/agent-teams-test",
"subscriptions": [],
"backendType": "in-process"
},
{
"agentId": "perf-researcher@research-watchers",
"name": "perf-researcher",
"agentType": "general-purpose",
"model": "claude-opus-4-6",
"prompt": "...",
"color": "green",
"planModeRequired": false,
"joinedAt": 1770875130344,
"tmuxPaneId": "in-process",
"cwd": "/home/arkon/codeman-cases/agent-teams-test",
"subscriptions": [],
"backendType": "in-process"
},
{
"agentId": "api-researcher@research-watchers",
"name": "api-researcher",
"agentType": "general-purpose",
"model": "claude-opus-4-6",
"prompt": "...",
"color": "yellow",
"planModeRequired": false,
"joinedAt": 1770875134997,
"tmuxPaneId": "in-process",
"cwd": "/home/arkon/codeman-cases/agent-teams-test",
"subscriptions": [],
"backendType": "in-process"
}
]
}
```
**Key fields per member:**
- `agentId`: `{name}@{teamName}` format
- `agentType`: `"team-lead"` for lead, `"general-purpose"` for teammates
- `model`: Model used (inherits from lead)
- `prompt`: Full spawn prompt (only for teammates)
- `color`: UI color assignment (blue, green, yellow)
- `backendType`: `"in-process"` for in-process mode
- `tmuxPaneId`: `"in-process"` or actual pane ID for tmux mode
- `subscriptions`: Empty array (possibly for message routing)
**Config grows incrementally** — starts with just lead member (620 bytes), grows as teammates are added (→ 1886 → 3188 → 4551 bytes).
---
## Question 5: How do shared tasks differ from regular tasks?
**Expected:** Team name directory vs UUID, richer task format
**Actual: CONFIRMED**
**Team tasks (`~/.claude/tasks/research-watchers/`):**
```json
{
"id": "1",
"subject": "Research Node.js fs.watch on Linux vs macOS",
"description": "Research how Node.js fs.watch works differently...",
"activeForm": "Researching Node.js fs.watch Linux vs macOS",
"status": "in_progress",
"blocks": [],
"blockedBy": [],
"owner": "fs-researcher"
}
```
**Internal teammate tracking tasks (4.json, 5.json, 6.json):**
```json
{
"id": "4",
"subject": "fs-researcher",
"description": "You are \"fs-researcher\" on the \"research-watchers\" team...",
"status": "in_progress",
"blocks": [],
"blockedBy": [],
"metadata": { "_internal": true }
}
```
**Key differences from regular subagent tasks (`~/.claude/tasks/{UUID}/`):**
| Feature | Regular tasks | Team tasks |
|---------|--------------|------------|
| Directory name | UUID | Human-readable team name |
| File names | `.lock`, `.highwatermark` only | Numbered JSON files (1.json, 2.json...) |
| Content | Lock files only (no task JSON) | Full task JSON with metadata |
| Owner field | N/A | Teammate name |
| Locking | `.lock` file | `.lock.lock` directory (mkdir atomic) |
| Internal tasks | None | `_internal: true` for teammate spawn tracking |
---
## Question 6: Can we write to task/mailbox files to interact with teammates?
**Expected:** Possibly for tasks, no for messages
**Actual: LIKELY YES for both**
Evidence supporting external writes:
1. **Inbox files** are plain JSON arrays — we could append messages
2. **Task files** are plain JSON — we could modify status, add new tasks
3. **File locking** uses `.json.lock` directories — we'd need to respect the locking protocol
4. **Lock protocol**: Create directory `{file}.lock` → write → delete directory. Simple mkdir-based atomic lock.
**Not tested in this experiment** — would need a follow-up test to verify teammates actually pick up externally-added messages/tasks. But the format is clear and the locking is simple.
---
## Question 7: What happens to Codeman's idle detection with active teammates?
**Expected:** Lead may appear idle while teammates work
**Actual: Lead stays "idle" in Codeman's view, but terminal shows active status**
Observations:
- Codeman session status showed `"idle"` throughout the experiment
- The terminal output continued updating (task list checkboxes, teammate progress messages)
- Lead displayed "Befuddling..." spinner while waiting for teammates
- Token count climbed from 27k → 33k during the experiment
- The `stop` hook DID fire at the end when the team was cleaned up
**Implication:** Current idle detection may trigger prematurely if:
- It only checks Codeman's session status (which stays "idle")
- It doesn't account for active teammates
**What we need:** Check `~/.claude/teams/*/config.json` for active members before declaring idle.
---
## Question 8: How many Claude processes spawn per teammate?
**Expected:** 1 claude process per teammate
**Actual: ZERO separate processes! Teammates are in-process threads.**
```
# Only 2 claude processes (both Codeman sessions, none for teammates):
25405 claude --dangerously-skip-permissions --session-id 236f004f... (our main session)
383633 claude --dangerously-skip-permissions --session-id 461daa80... (test session + 3 teammates)
# Process tree for test session:
claude(383633)─┬─{claude}(383635)
├─{claude}(383636)
├─... (22 threads total)
└─{claude}(399252)
```
**In-process mode = threads, not processes.** All 3 teammates run as threads within the single `claude` process (PID 383633). This explains:
- No separate PIDs to track
- No `/proc/{pid}/environ` for individual teammates
- Lower resource overhead
- Shared env vars automatically
---
## Question 9: Do teammates inherit Codeman env vars (hook events)?
**Expected:** Yes, if child processes
**Actual: YES, trivially — they're in-process threads**
Since teammates are threads in the lead's process (PID 383633), they share the exact same environment:
```
CODEMAN_SCREEN=1
CODEMAN_SESSION_ID=461daa80-94ec-4e5e-a1bb-0518f78311bc
CODEMAN_SCREEN_NAME=codeman-461daa80
CODEMAN_API_URL=http://localhost:3000
```
The `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` env var was set via `settings.local.json`'s `env` key, which Claude Code reads at startup and sets on its process.
**Hook events:** The lead session's hooks (Notification, Stop) apply to the whole process. Teammate-specific hooks (`TeammateIdle`, `TaskCompleted`) are defined in the same `settings.local.json` and would fire for the lead's session.
---
## Question 10: Does subagent-watcher pick up teammates automatically?
**Expected:** Probably not
**Actual: YES! subagent-watcher detects teammates automatically.**
Teammates create transcript files in the standard subagent path:
```
~/.claude/projects/{hash}/{leadSessionId}/subagents/agent-{id}.jsonl
```
Codeman's `/api/subagents` endpoint returned all 3 teammates as active subagents. They're indistinguishable from regular Task tool subagents except:
1. Their `description` field starts with `<teammate-message teammate_id= team`
2. They can be cross-referenced with `~/.claude/teams/{name}/config.json`
3. They tend to be longer-lived than regular subagents
**Sub-subagents:** Teammates also spawn their own Task tool subagents (6 additional agents detected), creating a 3-level hierarchy: Lead → Teammates → Sub-subagents.
---
## Filesystem Event Timeline
```
06:45:01 Session transcript created
06:45:05 ~/.claude/teams/ created
06:45:05 ~/.claude/teams/research-watchers/ created
06:45:05 config.json created (lead member only, 620 bytes)
06:45:05 ~/.claude/tasks/research-watchers/ created with .lock
06:45:11 Task 1.json created (via .lock.lock directory lock)
06:45:13 Task 2.json created
06:45:15 Task 3.json created
06:45:18 inboxes/ directory created
06:45:18 fs-researcher.json inbox created (task_assignment message)
06:45:18 perf-researcher.json inbox created
06:45:19 api-researcher.json inbox created
06:45:26 config.json updated (fs-researcher added, 1886 bytes)
06:45:26 Subagent agent-ae50544.jsonl created (fs-researcher)
06:45:26 Task 4.json created (internal: fs-researcher tracking)
06:45:30 config.json updated (perf-researcher added, 3188 bytes)
06:45:30 Subagent agent-aa20c65.jsonl created (perf-researcher)
06:45:30 Task 5.json created (internal: perf-researcher tracking)
06:45:34 config.json updated (api-researcher added, 4551 bytes)
06:45:34 Task 6.json created (internal: api-researcher tracking)
06:45:35 Subagent agent-a29de32.jsonl created (api-researcher)
06:45:35+ Teammates working, additional subagent transcripts appearing
06:47:xx Tasks completed, shutdown_requests sent to teammate inboxes
06:47:56 team-lead.json inbox created (teammates reporting back)
06:47:57 config.json updated multiple times (member removal?)
06:48:02 CLEANUP: all inbox files deleted
06:48:02 CLEANUP: inboxes/ directory deleted
06:48:02 CLEANUP: config.json deleted
06:48:02 CLEANUP: research-watchers team directory deleted
06:48:02 CLEANUP: all task files deleted (1-6.json + .lock)
06:48:02 CLEANUP: research-watchers task directory deleted
```
---
## Web UI Observations
**Terminal output:**
- Task list appears with checkboxes: `☐ Research Node.js fs.watch on Linux vs macOS`
- Checkboxes fill in as tasks complete: `☑ Research Node.js fs.watch...`
- Each task shows assigned teammate: `(@fs-researcher)`
- Spinner shows active teammate with progress
**Status bar:**
- Shows team member selector: `@main @api-researcher @fs-researcher @perf-researcher`
- Hint: `shift+↑ to expand` and `ctrl+t to show teammates`
- Standard bypass permissions and token count still visible
**Subagent floating windows:**
- Teammates DID appear as subagent floating windows in Codeman's web UI
- They show the standard subagent info (model, tool calls, description)
- Sub-subagents (teammates' own Task tool usage) also appear
**In-process mode specifics:**
- No new terminal windows or panes
- Everything renders in the single terminal session
- Shift+Up/Down would switch between teammate views (not tested interactively)
---
## Conclusions & Key Surprises
### Surprises vs expectations
1. **Inboxes ARE filesystem-based** — contrary to docs saying "SendMessage tool". It's a hybrid: the tool writes to filesystem inboxes.
2. **Teammates are threads, not processes** — no new OS processes, just threads within the lead's claude process.
3. **Teammates appear as standard subagents** — existing subagent-watcher infrastructure works out of the box!
4. **Config grows incrementally** — members are added one-by-one, not all at once.
5. **Internal tracking tasks** — tasks 4-6 with `_internal: true` track teammate spawn state.
6. **Auto-cleanup** — lead automatically cleaned up ALL artifacts after shutdown.
7. **Sub-subagents** — teammates can spawn their own Task tool subagents (3-level hierarchy).
8. **`teammateMode` is NOT a valid settings key** — display mode defaults to `in-process`.
### Design implications for Codeman
1. **TeamWatcher can be simple** — just poll `~/.claude/teams/` for directories + parse config.json
2. **Subagent-watcher already works** — no new infrastructure needed for teammate transcript tailing
3. **Idle detection needs team awareness** — check config.json members before declaring idle
4. **Message interception is possible** — watch inbox JSON files for real-time message tracking
5. **Task visualization is straightforward** — parse numbered JSON files in task directory
6. **No process tracking needed** — teammates are threads, not separate processes
7. **Distinguish teammates from subagents** — use description prefix `<teammate-message` or cross-reference config.json
### What to build first
1. **Team-aware idle detection** — highest priority, prevents premature respawn
2. **TeamWatcher** — poll `~/.claude/teams/` for team creation/removal
3. **Team tasks API** — parse task JSON files for UI display
4. **Teammate badge in subagent windows** — mark teammate subagents differently from regular ones
5. **Message timeline** — parse inbox files for inter-teammate communication display
### What we DON'T need to build
- Process discovery for teammates (they're threads)
- Custom transcript tailing (subagent-watcher handles it)
- Separate teammate window infrastructure (subagent windows work)