agent-ecosystem/docs/research/claude-kanban-dataflow.md
iliya 71db7f153b feat(research): add comprehensive documentation on AI agent protocols and orchestration tools
- Introduced multiple new markdown files detailing the Agent Client Protocol (ACP), AI agent orchestration landscape, and various tools for managing multi-agent systems.
- Included in-depth analysis of protocol standards, governance structures, and emerging frameworks relevant to AI agent integration.
- Documented key features, architecture, and integration potential of various desktop and CLI orchestrators, enhancing understanding of the current ecosystem.
- Provided insights into best practices for integrating multi-provider agent support within the Electron framework.

This documentation aims to serve as a foundational resource for developers and stakeholders involved in AI agent orchestration and integration.
2026-03-25 14:47:52 +02:00

23 KiB

Claude Kanban Data Flow: Full Architecture Analysis

Executive Summary

Claude Code does NOT use its own built-in Agent Teams tools (TaskCreate, TaskUpdate, TaskList, etc.) for kanban management. Instead, our app injects a custom MCP server (agent-teams-mcp) that provides its own set of tools (task_create, task_list, task_start, task_complete, review_request, etc.). Claude's built-in TaskCreate is explicitly demoted to "optional for private planning only" via the provisioning prompt.

The data flow is: Claude calls MCP tools → agent-teams-controller writes JSON files to disk → fs.watch() detects changes → IPC event → React UI updates.


1. How the MCP Server Gets Injected

TeamMcpConfigBuilder (src/main/services/team/TeamMcpConfigBuilder.ts)

When a team is created or launched, TeamMcpConfigBuilder.writeConfigFile() generates a temporary JSON file:

/tmp/claude-team-mcp/agent-teams-mcp-<uuid>.json

Contents:

{
  "mcpServers": {
    "agent-teams": {
      "command": "node",
      "args": ["/path/to/mcp-server/index.js"]
    },
    ...userMcpServers
  }
}

This merges the user's ~/.claude.json MCP servers with the injected agent-teams server (our server wins on name collision).

CLI Launch Args (TeamProvisioningService.ts, lines 2986-2989)

'--mcp-config', mcpConfigPath,
'--disallowedTools', 'TeamDelete,TodoWrite',
  • --mcp-config points Claude CLI to our generated config
  • TeamDelete is blocked to prevent team cleanup
  • TodoWrite is blocked because Opus tends to use it instead of our MCP tools
  • Claude's native TaskCreate/TaskUpdate are NOT blocked — they are left available but deprioritized via prompt engineering

The Provisioning Prompt (line 724)

- TaskCreate is optional for private planning only; do NOT use it for team-board tasks.

The prompt then explicitly instructs Claude to use MCP tools:

Task board operations — use MCP tools directly:
- Get task details: task_get { teamName: "...", taskId: "<id>" }
- Create task: task_create { teamName: "...", subject: "...", ... }
- Start task: task_start { teamName: "...", taskId: "<id>" }
...

2. What MCP Tools Exist

MCP Server Structure (mcp-server/)

mcp-server/
├── src/
│   ├── index.ts          — FastMCP server, stdio transport
│   ├── controller.ts     — wraps agent-teams-controller
│   └── tools/
│       ├── taskTools.ts   — task_create, task_list, task_get, task_set_status, task_start,
│       │                    task_complete, task_set_owner, task_add_comment, task_link, etc.
│       ├── kanbanTools.ts — kanban_get, kanban_set_column, kanban_clear, kanban_add_reviewer
│       ├── reviewTools.ts — review_request, review_start, review_approve, review_request_changes
│       ├── messageTools.ts
│       ├── processTools.ts
│       ├── runtimeTools.ts
│       └── crossTeamTools.ts

Full MCP Tool List

Domain Tools
Task task_create, task_create_from_message, task_get, task_get_comment, task_list, task_set_status, task_start, task_complete, task_set_owner, task_add_comment, task_attach_file, task_attach_comment_file, task_set_clarification, task_link, task_unlink, member_briefing, task_briefing
Kanban kanban_get, kanban_set_column, kanban_clear, kanban_list_reviewers, kanban_add_reviewer, kanban_remove_reviewer
Review review_request, review_start, review_approve, review_request_changes
Message (message-related tools)
Process (process-related tools)
Runtime (runtime-related tools)
Cross-team cross_team_send, cross_team_list_targets, cross_team_get_outbox

3. Data Flow: Claude MCP Tool Call → Disk

The Shared Library: agent-teams-controller

Both the MCP server and the Electron main process use the same agent-teams-controller package (workspace dependency). This is a plain JS library that provides:

// agent-teams-controller/src/controller.js
function createController(options) {
  const context = createControllerContext(options); // { teamName, paths }
  return {
    tasks: bindModule(context, tasks),
    kanban: bindModule(context, kanban),
    review: bindModule(context, review),
    messages: bindModule(context, messages),
    ...
  };
}

Path Resolution

// agent-teams-controller/src/internal/runtimeHelpers.js
function getPaths(flags, teamName) {
  const claudeDir = getClaudeDir(flags); // ~/.claude
  return {
    teamDir:  path.join(claudeDir, 'teams', teamName),
    tasksDir: path.join(claudeDir, 'tasks', teamName),
    kanbanPath: path.join(claudeDir, 'teams', teamName, 'kanban-state.json'),
    ...
  };
}

So tasks live in ~/.claude/tasks/<teamName>/<taskId>.json and kanban state lives in ~/.claude/teams/<teamName>/kanban-state.json.

Task Creation Flow (MCP → Disk)

  1. Claude calls MCP tool: task_create { teamName: "my-team", subject: "Fix bug" }
  2. mcp-server/src/tools/taskTools.tsgetController(teamName).tasks.createTask(...)
  3. agent-teams-controller/src/internal/tasks.jstaskStore.createTask(context, params)
  4. agent-teams-controller/src/internal/taskStore.js:
    function writeJson(filePath, value) {
      ensureDir(path.dirname(filePath));
      const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
      fs.writeFileSync(tempPath, JSON.stringify(value, null, 2));
      fs.renameSync(tempPath, filePath);  // atomic write
    }
    
  5. Result: ~/.claude/tasks/my-team/<taskId>.json is created

Kanban State Flow

The kanban state is a separate JSON file (kanban-state.json) in the teams directory. When Claude calls review_request or kanban_set_column, the controller writes to ~/.claude/teams/<teamName>/kanban-state.json.


4. Data Flow: Disk → UI

FileWatcher (src/main/services/infrastructure/FileWatcher.ts)

There are two separate fs.watch() watchers:

  1. Teams watcher — watches ~/.claude/teams/ (recursive)

    • Detects: config.json, kanban-state.json, inboxes/*.json, sentMessages.json, processes.json
  2. Tasks watcher — watches ~/.claude/tasks/ (recursive)

    • Detects: <teamName>/<taskId>.json changes

When a file changes:

// FileWatcher.ts, line 404
this.tasksWatcher = fs.watch(this.tasksPath, { recursive: true }, (eventType, filename) => {
  this.handleTasksChange(eventType, filename);
});

processTasksChange() (line 1028) parses the filename to extract teamName and detail (e.g., "12.json"), then emits:

const event: TeamChangeEvent = { type: 'task', teamName, detail: relative };
this.emit('team-change', event);

Event Propagation (src/main/index.ts, line 500-608)

wireFileWatcherEvents() listens for team-change events:

context.fileWatcher.on('team-change', teamChangeHandler);

For task events (row.type === 'task'):

  1. Sends IPC to renderer: mainWindow.webContents.send(TEAM_CHANGE, event) (line 502)
  2. Broadcasts to HTTP SSE: httpServer?.broadcast('team-change', event) (line 504)
  3. Reconciles artifacts: teamDataService.reconcileTeamArtifacts(teamName) (line 583)
  4. Notifies lead: teamDataService.notifyLeadOnTeammateTaskStart(teamName, taskId) (line 590)
  5. Backs up task: teamBackupService.scheduleTaskBackup(teamName, detail) (line 606)

UI Data Reading

The renderer (React) receives TEAM_CHANGE events and re-fetches task data via IPC:

  • team:getTasks → calls TeamTaskReader.getTasks(teamName) which reads all ~/.claude/tasks/<teamName>/*.json files
  • team:updateKanban → calls TeamKanbanManager.updateTask() which reads/writes kanban-state.json

The Electron TeamTaskReader (src/main/services/team/TeamTaskReader.ts) re-reads all task JSON files from disk, parses them, filters out _internal tasks, normalizes fields, and returns TeamTask[] to the renderer.


5. Claude's Built-in Tools vs Our MCP Tools

Claude's Native Built-in Tools (Agent Teams Protocol)

Native Tool Purpose Blocked?
TeamCreate Create team structure (config.json, state) No — used during provisioning
TaskCreate Create a task via CLI internal mechanism No — but deprioritized by prompt ("optional for private planning only")
TaskUpdate Update task via CLI internal mechanism No — but never instructed to use
TaskList List tasks via CLI No — but never instructed to use
TaskGet Get task via CLI No — but never instructed to use
SendMessage Send message between agents No — actively used for inter-agent chat
TeamDelete Delete team YES — blocked via --disallowedTools
TodoWrite Write todo items YES — blocked via --disallowedTools
Agent Spawn subagent/teammate No — actively used to spawn teammates

Our MCP Tools (agent-teams-mcp)

MCP Tool Purpose Claude instructed to use?
task_create Create task on board YES — primary task creation
task_start Move task to in_progress YES
task_complete Move task to completed YES
task_add_comment Add comment to task YES
task_get Read task details YES
task_list List all tasks YES
review_request Move to review column YES
review_approve Approve review YES
kanban_set_column Move task on kanban YES

Why This Split?

Claude's native TaskCreate writes tasks to ~/.claude/tasks/<teamName>/<taskId>.json too — the same location. But:

  1. Our MCP tools add richer fields (displayId, workIntervals, historyEvents, comments, attachments, reviewState, sourceMessage, etc.)
  2. Our MCP tools enforce board discipline (via agent-teams-controller logic)
  3. Our kanban state is a separate file (kanban-state.json) that Claude's native tools don't manage
  4. Review workflow (review_request → review_start → review_approve / review_request_changes) is entirely our MCP layer

Claude's native TaskCreate creates simpler task JSON files. The CLI's internal Zod schema requires description, blocks, blockedBy fields — our TeamTaskWriter.createTask() (line 68-71) ensures CLI compatibility:

const cliCompatibleTask = {
  ...task,
  description: task.description ?? '',
  blocks: task.blocks ?? [],
  blockedBy: task.blockedBy ?? [],
};

6. The Two-Writer Problem

Both writers hit the same filesystem:

Writer Writes to When
MCP server (agent-teams-controller) ~/.claude/tasks/<teamName>/<taskId>.json Claude calls task_create, task_set_status, etc.
Electron main (TeamTaskWriter) ~/.claude/tasks/<teamName>/<taskId>.json UI creates/updates tasks (user clicks "Create Task", drag-drop, etc.)
Claude CLI built-in ~/.claude/tasks/<teamName>/<taskId>.json If Claude uses native TaskCreate (deprioritized)

All three write to the same files. Concurrent writes are handled by:

  • MCP: taskStore.writeJson() uses atomic temp+rename
  • Electron: TeamTaskWriter uses per-file locks + atomicWriteAsync()
  • CLI: Its own write mechanism

There is NO cross-process lock between MCP and Electron — they rely on atomic writes and eventual consistency (file watcher detects changes within ~100ms debounce).


7. Full Data Flow Diagram

┌─────────────────────────────────────────────────────────────────┐
│ Claude Code CLI (stream-json process)                           │
│                                                                 │
│  ┌─────────────────┐    ┌──────────────────────┐               │
│  │ Built-in Tools   │    │ MCP Tools             │               │
│  │ • SendMessage     │    │ (agent-teams-mcp)     │               │
│  │ • Agent           │    │ • task_create          │               │
│  │ • TaskCreate(*)   │    │ • task_start           │               │
│  │ • Read/Write/Bash │    │ • task_complete         │               │
│  └────────┬──────────┘    │ • task_add_comment      │               │
│           │               │ • review_request         │               │
│           │               │ • kanban_set_column      │               │
│           │               └───────────┬──────────────┘               │
│           │                           │                             │
│  stdout (stream-json)    agent-teams-controller                    │
│           │                           │                             │
└───────────┼───────────────────────────┼─────────────────────────────┘
            │                           │
            │                    ┌──────▼──────────────────┐
            │                    │ File System (disk)       │
            │                    │                          │
            │                    │ ~/.claude/tasks/<team>/  │
            │                    │   ├── 1.json             │
            │                    │   ├── 2.json             │
            │                    │   └── ...                │
            │                    │                          │
            │                    │ ~/.claude/teams/<team>/  │
            │                    │   ├── config.json        │
            │                    │   ├── kanban-state.json  │
            │                    │   └── inboxes/           │
            │                    └──────┬──────────────────┘
            │                           │
            │                    fs.watch() (recursive)
            │                           │
┌───────────┼───────────────────────────┼─────────────────────────────┐
│ Electron Main Process                 │                             │
│           │                           │                             │
│  ┌────────▼──────────┐    ┌───────────▼───────────┐                │
│  │ TeamProvisioning   │    │ FileWatcher            │                │
│  │ Service             │    │ • tasksWatcher         │                │
│  │ (parses stdout)     │    │ • teamsWatcher         │                │
│  │                     │    └───────────┬───────────┘                │
│  │ • captureSendMsg    │                │                            │
│  │ • captureSpawnEvt   │    TeamChangeEvent { type: 'task' }        │
│  │ • detectSessionId   │                │                            │
│  └─────────────────────┘    ┌───────────▼───────────┐                │
│                             │ wireFileWatcherEvents  │                │
│                             │ (src/main/index.ts)    │                │
│  ┌──────────────────────┐   └───────────┬───────────┘                │
│  │ TeamTaskReader        │              │                            │
│  │ (re-reads all .json)  │◄─────────────┤                            │
│  │                       │              │                            │
│  │ TeamKanbanManager     │    IPC: TEAM_CHANGE                      │
│  │ (reads kanban-state)  │              │                            │
│  └──────────────────────┘              │                            │
│                                        │                            │
│  ┌──────────────────────┐              │                            │
│  │ TeamTaskWriter        │              │                            │
│  │ (UI-initiated writes) │              │                            │
│  └──────────────────────┘              │                            │
└────────────────────────────────────────┼────────────────────────────┘
                                         │
                                    IPC (webContents.send)
                                         │
┌────────────────────────────────────────┼────────────────────────────┐
│ Renderer (React + Zustand)             │                            │
│                                        │                            │
│  team-change event → refetch tasks via IPC → update Zustand store  │
│  → re-render KanbanBoard                                           │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

(*) TaskCreate — Claude's native tool, deprioritized by prompt.
    Writes to same location but lacks our rich metadata.

8. Key Questions Answered

Does Claude currently use MCP for kanban management?

YES. Claude uses our agent-teams-mcp MCP server for ALL task board operations. The server is injected via --mcp-config when spawning the CLI process. Claude's native TaskCreate is not blocked but is explicitly deprioritized ("optional for private planning only") via the system prompt.

How does task data flow?

  1. Claude calls MCP tool (e.g., task_create) via the stdio MCP transport
  2. agent-teams-controller writes a JSON file to ~/.claude/tasks/<teamName>/<id>.json (atomic write via temp+rename)
  3. fs.watch() in FileWatcher detects the change (100ms debounce)
  4. TeamChangeEvent { type: 'task', teamName, detail: '<id>.json' } emitted
  5. wireFileWatcherEvents() forwards to renderer via IPC (webContents.send('team:change', event))
  6. Renderer re-fetches full task list via IPC → TeamTaskReader.getTasks() re-reads all JSON files
  7. Zustand store updates → React components re-render

Could we replace Claude's built-in tools with MCP tools?

We already did, effectively. Claude's built-in TaskCreate/TaskUpdate/TaskList/TaskGet are NOT blocked, but the prompt instructs Claude to use our MCP tools exclusively. The built-in SendMessage and Agent tools are still used (they handle inter-agent communication and teammate spawning — responsibilities our MCP server doesn't cover).

What we CANNOT replace via MCP:

  • SendMessage — this is Claude's native inter-agent messaging protocol
  • Agent — this is the tool that spawns teammate subprocesses
  • TeamCreate — this bootstraps the team structure

If Claude also used MCP (like Codex/Gemini would), would that unify the architecture?

Partially, but with important nuances:

What's already unified:

  • The agent-teams-controller package is the single source of truth for task/kanban/review operations. Both the MCP server and the Electron main process import it.
  • Any AI agent (Claude, Codex, Gemini) that connects to our MCP server gets the same tools and writes to the same files.

What would still differ per agent:

  • Team spawning — Claude uses Agent(team_name=...) which is proprietary. Other agents would need their own subprocess spawning mechanism.
  • Inter-agent messaging — Claude uses SendMessage (part of its Agent Teams protocol). Other agents would need a different approach (perhaps MCP-based send_message tool).
  • Process lifecycle — Claude's --input-format stream-json / --output-format stream-json keeps the CLI alive. Other agents would need different process management.
  • Prompt injection — Our provisioning prompt is Claude-specific. Other agents would need their own system prompts.

To truly unify for multi-agent support:

  1. The MCP server already provides all task/kanban operations — any agent with MCP support can use them
  2. We'd need to add MCP tools for messaging (send_message, read_inbox) to replace Claude-specific SendMessage
  3. We'd need a generic agent spawning mechanism (not Claude's Agent tool)
  4. The stdout parsing in TeamProvisioningService is Claude-specific — other agents would need different adapters

9. File Index

File Role
src/main/services/team/TeamProvisioningService.ts Spawns Claude CLI, attaches stdout parser, handles stream-json, manages team lifecycle
src/main/services/team/TeamMcpConfigBuilder.ts Generates --mcp-config JSON file that injects our MCP server
mcp-server/src/index.ts FastMCP server entry point (stdio transport)
mcp-server/src/controller.ts Wraps agent-teams-controller for MCP tools
mcp-server/src/tools/taskTools.ts Task CRUD MCP tools (17 tools)
mcp-server/src/tools/kanbanTools.ts Kanban state MCP tools (6 tools)
mcp-server/src/tools/reviewTools.ts Review workflow MCP tools
agent-teams-controller/src/controller.js Shared controller factory — creates context + binds all domain modules
agent-teams-controller/src/internal/taskStore.js Low-level task JSON file read/write operations
agent-teams-controller/src/internal/tasks.js Task business logic (create, start, complete, comment, etc.)
agent-teams-controller/src/internal/runtimeHelpers.js Path resolution (~/.claude/tasks/, ~/.claude/teams/)
src/main/services/infrastructure/FileWatcher.ts Watches ~/.claude/tasks/ and ~/.claude/teams/ with fs.watch()
src/main/index.ts (lines 425-620) wireFileWatcherEvents() — forwards file changes to renderer via IPC
src/main/services/team/TeamTaskReader.ts Reads all task JSON files, normalizes, returns TeamTask[]
src/main/services/team/TeamTaskWriter.ts UI-side writes (create, update status, add comment, etc.)
src/main/services/team/TeamKanbanManager.ts Reads/writes kanban-state.json for UI kanban overlay