Files
open-tasks/docs/research/open-coordinator-deep-dive.md
glm-5.1 9342dab70c Rename tool to taskgraph, use op dispatch field, add research reports
The built-in OpenCode 'task' tool spawns subagents for work delegation.
Naming our plugin 'tasks' would create confusion with two 'task' tools
that do completely different things. 'taskgraph' matches the core
library, clearly differentiates from the built-in, and describes what
the tool actually does.

The dispatch field is renamed from 'tool' to 'op' (operation) to
avoid collision with OpenCode's 'tool' terminology and match the
Rust CLI's subcommand pattern.

ADR-001 rewritten for taskgraph/op naming and Zod/TypeBox distinction.
ADR-007 added documenting the naming decision and the three 'task'
concepts (task, todowrite, taskgraph).

Research reports added:
- docs/research/opencode-task-tool-deep-dive.md
- docs/research/open-coordinator-deep-dive.md

Also: fixed SDD process link, resolved open question about 'show'
including full body, added todowrite to relationship table, clarified
Zod vs TypeBox roles, changed FileSource to async scan.
2026-04-28 11:30:20 +00:00

31 KiB

Research: Open Coordinator Plugin — Deep Dive

Metadata

Field Value
Plugin @alkdev/open-coordinator v2.1.0
Repository git@alk.dev:alkimiadev/open-coordinator (fork of 0xSero/open-trees)
Source Path /workspace/@alkimiadev/open-coordinator/
License MIT OR Apache-2.0 (dual)
Runtime Bun, TypeScript (ESM, strict)
Linter Biome
Key Dependency @opencode-ai/plugin ^1.1.3, jsonc-parser ^3.3.1
Research Date 2026-04-28

1. Plugin Structure

1.1 Source Tree

src/
├── index.ts                 # Plugin entry point + hooks
├── tools.ts                 # Single `worktree` tool definition
├── registry.ts              # Handler dispatch — operations, role detection, routing
├── state.ts                 # Session-to-worktree state persistence (state.json)
├── config.ts                # Config path helpers (XDG_CONFIG_HOME)
├── git.ts                   # Git command runner + porcelain parser
├── paths.ts                 # Worktree path resolution + branch name normalization
├── format.ts                # Output formatting (tables, errors, commands)
├── result.ts                # ToolResult type (ok/error union)
├── sdk.ts                   # OpenCode SDK response unwrapping
├── status.ts                # Git status porcelain parser
├── session-helpers.ts       # TUI session helpers (openSessions, updateTitle)
├── opencode-config.ts       # JSONC config manipulation (for `add` CLI)
├── cli.ts                   # Standalone CLI: `open-coordinator add`
├── worktree.ts              # Core worktree CRUD (create, remove, prune, merge)
├── worktree-session.ts      # Session creation: start, open, fork
├── worktree-spawn.ts        # Async spawn with per-task prompts
├── worktree-swarm.ts        # Batch worktree+session creation
├── worktree-status.ts       # Per-worktree git status
├── worktree-dashboard.ts    # Aggregated dashboard (state + git + sessions)
├── worktree-helpers.ts      # Shared helpers (pathExists, findWorktreeMatch, etc.)
└── detection/
    ├── index.ts             # SSE subscription + event loop + stall detection
    ├── types.ts             # Types: SessionMetrics, AnomalyType, thresholds
    ├── heuristics.ts        # Detection rules: model degradation, errors, stalls
    ├── metrics.ts           # Per-session metric tracking + updates
    └── notify.ts            # Coordinator notification via session.promptAsync

1.2 Architecture Overview

The plugin follows the single-tool registry pattern (same as @alkdev/open-memory):

LLM calls: worktree({action: "spawn", args: {tasks: ["auth", "db"]}})
                              |
                              v
                    registry.ts: route(action, args, context)
                              |
                              v
                    handlers[action]  <-- Record<string, Handler>
                              |
                              v
                    handlers.spawn(args, context)  --> returns string

The key architect files and their responsibilities:

File Responsibility
index.ts Plugin factory: creates tools, starts detection, sets up hooks
tools.ts Defines the single worktree tool schema + calls registry
registry.ts 17 operation handlers, role detection (detectRole), routing (route)
state.ts JSON file I/O for state.json, session mapping CRUD
git.ts Shell execution via ctx$ for git commands
worktree*.ts Implementation of each operation category
detection/ Real-time anomaly monitoring via SSE

1.3 Build System

bun run build     # bun build src/index.ts src/cli.ts → dist/ + tsc declarations
bun run typecheck # tsc --noEmit
bun run lint      # biome check .
bun run format    # biome format --write .
bun run test      # bun test

2. Tool Definition

2.1 Single Tool: worktree

Defined in src/tools.ts:

export const createTools = (ctx: PluginInput): Record<string, ToolDefinition> => ({
  worktree: tool({
    description: "Worktree coordinator: manage git worktrees, sessions, and communication...",
    args: {
      action: z.string().describe("Operation name: help, list, status, dashboard, ..."),
      args: z.record(z.string(), z.unknown()).optional().describe("Arguments for the operation."),
    },
    async execute(input, context) {
      const role = await detectRole(context.sessionID);
      return route(input.action, (input.args as Record<string, unknown>) ?? {}, {
        ctx,
        sessionID: context.sessionID,
        role,
      });
    },
  }),
});

Key design decisions:

  • One tool rather than 17 separate tools (reduces agent context bloat)
  • action field selects the operation (help, list, spawn, etc.)
  • args field is a loose Record<string, unknown> — no per-operation schema validation
  • Role detection happens on every invocation, not cached

2.2 Operations (17 total)

Coordinator operations (16 accessible):

Operation Handler File Description
help registry.ts Full reference or per-operation help
list worktree.ts List git worktrees
status worktree-status.ts Git status per worktree
dashboard worktree-dashboard.ts Aggregated state+git+session view
create worktree.ts Create worktree branch + checkout
start worktree-session.ts Worktree + fresh session
open worktree-session.ts Session in existing worktree
fork worktree-session.ts Worktree + forked session (parentID)
swarm worktree-swarm.ts Batch worktree+session creation
spawn worktree-spawn.ts Async per-task worktree+session+prompt
message registry.ts (inline) Send message to spawned session
notify registry.ts (inline) Report to coordinator session
sessions registry.ts (inline) Query spawned session status
abort registry.ts (inline) Abort a session + cleanup
cleanup registry.ts → worktree.ts Remove/prune/merged cleanup
merge worktree.ts Merge worktree branch into target

Implementation-only operations (4):

Operation Description
help Filtered help
current Show session's worktree mapping
notify Send message to coordinator
status Show worktree git status

2.3 Registry / Dispatch Pattern

// src/registry.ts

type Handler = (args: ToolArgs, hctx: HandlerContext) => Promise<HandlerResult>;
type HandlerContext = {
  ctx: PluginInput;
  sessionID?: string;
  role: "coordinator" | "implementation";
};

const COORDINATOR_OPS = new Set([
  "help", "list", "status", "dashboard", "create", "start", "open", "fork",
  "swarm", "spawn", "message", "notify", "sessions", "abort", "cleanup", "merge", "current"
]);

const IMPLEMENTATION_OPS = new Set(["help", "current", "notify", "status"]);

export const detectRole = async (sessionID?: string) => {
  if (!sessionID) return "coordinator";
  const entry = await findSessionEntry(sessionID);
  if (entry?.parentSessionID) return "implementation";
  return "coordinator";
};

export const route = async (action, args, hctx) => {
  const handler = handlers[action];
  if (!handler) return `Unknown operation: ${action}. Call worktree({action: "help"})...`;
  if (!isOpAllowed(action, hctx.role)) return formatError(`Operation "${action}" not available...`);
  try { return await handler(args, hctx); }
  catch (err) { return `Error in ${action}: ${err.message}`; }
};

Key points:

  • Role detection is per-invocation via findSessionEntry(sessionID) — looks up state.json
  • If session has parentSessionIDimplementation role (limited operations)
  • If no sessionID or no parentSessionID → coordinator role (all operations)
  • Unknown actions return help suggestion, not errors
  • Handler exceptions are caught and returned as strings

3. Worktree Orchestration

3.1 How Worktrees Are Created

The core creation path (in worktree.ts):

export const createWorktreeDetails = async (ctx, options) => {
  const repoRoot = getRepoRoot(ctx);        // ctx.worktree
  const name = options.name?.trim() ?? "";
  const branch = options.branch || normalizeBranchName(name);
  const base = options.base?.trim() || "HEAD";
  const worktreePath = options.path
    ? resolveWorktreePath(repoRoot, options.path)
    : defaultWorktreePath(repoRoot, branch);  // <repo>/.worktrees/<branch>

  // Validate branch name
  await runGit(ctx, ["check-ref-format", "--branch", branch], { cwd: repoRoot });
  
  // Check if branch exists
  const branchExists = await runGit(ctx, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]);
  
  const args = branchExists.ok
    ? ["worktree", "add", worktreePath, branch]           // existing branch
    : ["worktree", "add", "-b", branch, worktreePath, base]; // new branch from base
  
  await runGit(ctx, args, { cwd: repoRoot });
  return { branch, worktreePath, base, command, branchExists };
};

Git commands executed:

  • git rev-parse --show-toplevel (via ctx.worktreegetRepoRoot)
  • git worktree list --porcelain (list existing)
  • git check-ref-format --branch <name> (validate branch name)
  • git show-ref --verify --quiet refs/heads/<branch> (check existence)
  • git worktree add [-b <branch>] <path> [<base>] (create)
  • git worktree remove [--force] <path> (delete)
  • git worktree prune [--dry-run] (cleanup stale refs)
  • git status --porcelain (check dirty state)
  • git stash --include-untracked / git stash pop (merge safety)
  • git checkout <target> / git merge <branch> (merge flow)
  • git branch -d/-D <branch> (branch cleanup)
  • git push <remote> --delete <branch> (remote branch cleanup)

3.2 Path Resolution

// Default: <repo>/.worktrees/<branch>
defaultWorktreePath(repoRoot, branch) = path.join(repoRoot, ".worktrees", branch)

// Relative paths resolved under .worktrees/ (prevents traversal)
resolveWorktreePath(repoRoot, "feat/auth")  /repo/.worktrees/feat/auth

// Absolute paths accepted as-is
resolveWorktreePath(repoRoot, "/custom/path")  /custom/path

// Traversal blocked
resolveWorktreePath(repoRoot, "../escape")  Error

3.3 Branch Name Normalization

normalizeBranchName("Feature Auth Setup")  "feature-auth-setup"
// lowercases, replaces spaces/underscores with "-", 
// removes non-alphanumeric (except ./-), collapses multiple dashes

3.4 Removal Safety

  • removeWorktree refuses dirty worktrees unless force: true
  • Checks git status --porcelain before removal
  • cleanup with action: "merged" lists branches merged into HEAD and deletes local branches
  • Optional remote branch deletion with remote: true

4. Taskgraph CLI Usage

4.1 Finding: No Taskgraph CLI Usage

There is zero usage of the Rust taskgraph CLI or any task-related dependency analysis in the open-coordinator plugin. Comprehensive searches confirmed:

  • No imports of @alkdev/taskgraph
  • No invocations of taskgraph CLI binary
  • No reading of task markdown files with frontmatter
  • No dependency analysis, critical path, or decomposition logic
  • No concept of task files, task IDs, or task dependencies

4.2 What "Tasks" Mean in Open Coordinator

In open-coordinator, the word "task" appears in two contexts, both of which refer to human-provided string labels for worktree naming, not structured task objects:

  1. swarmtasks: string[] parameter: An array of names used to derive branch names (e.g., ["auth-setup", "db-schema"] → branches wt/auth-setup, wt/db-schema)
  2. spawntasks: string[] parameter: Same naming convention, plus optional prompt template with {{task}} substitution

The task field in state entries is a simple string label:

// state.ts
type WorktreeSessionEntry = {
  worktreePath: string;
  branch: string;
  sessionID: string;
  parentSessionID?: string;
  task?: string;            // ← Just a label, not a taskgraph object
  status?: SessionStatus;   // "active" | "completed" | "failed" | "aborted"
  createdAt: string;
  completedAt?: string;
};

4.3 What the AGENTS.md in open-tasks Says

The open-tasks AGENTS.md states:

open-coordinator (worktree): git worktree orchestration, session spawning, anomaly detection

And:

open-coordinator currently presumes using the Rust taskgraph CLI

This appears to be an aspirational/planned integration that does not yet exist in the open-coordinator codebase. The AGENTS.md in open-coordinator itself makes no reference to taskgraph.


5. Spawn vs Fork Concept

5.1 Session Creation Modes

There are three distinct session creation APIs, each with different semantics:

Operation Creates Worktree? Creates Session? Session Type ParentID? Prompt?
start Yes (or reuse) Yes Fresh (session.create) No No
fork Yes (or reuse) Yes Forked (session.create with parentID) Yes No
spawn Yes Yes Fresh (session.create with parentID) Yes Yes (template)

5.2 start — Fresh Session

// worktree-session.ts: startWorktreeSession
const sessionResponse = await ctx.client.session.create({
  query: { directory: target.worktreePath },  // workdir set to worktree
  body: { title: `wt:${target.branch}` },     // NO parentID
});
// → Creates independent session, no context from coordinator

5.3 fork — Forked Session (Context Inheritance)

// worktree-session.ts: forkWorktreeSession
const createResponse = await ctx.client.session.create({
  query: { directory: target.worktreePath },
  body: { title, parentID: sessionID },        // Inherits coordinator's context
});
// → Session starts with coordinator's conversation history

5.4 spawn — Fresh Session + Async Prompt (The Coordination Mode)

// worktree-spawn.ts: spawnWorktrees
// 1. Create worktree for each task
// 2. Create session with parentID (for hierarchy tracking)
const createResponse = await ctx.client.session.create({
  query: { directory: worktreeResult.result.worktreePath },
  body: { title, parentID: parentSessionID },  // Hierarchy tracking
});
// 3. Store session mapping with parentSessionID + task
await storeSessionMapping({
  worktreePath, branch, sessionID,
  parentSessionID,          // ← This determines "implementation" role
  task: rawTask,            // ← Task name stored in state
  status: "active",
});
// 4. Send initial prompt if template provided
if (options.prompt) {
  const promptText = substituteTemplate(options.prompt, rawTask);
  await ctx.client.session.promptAsync({
    path: { id: sessionID },
    body: {
      parts: [{ type: "text", text: promptText }],
      ...(options.agent && { agent: options.agent }),
      ...(effectiveModel && { model: effectiveModel }),
    },
  });
}

5.5 Model Inheritance

Spawned sessions can inherit the coordinator's model:

// worktree-spawn.ts: resolveCoordinatorModel
// Reads the coordinator's last assistant message to extract modelID + providerID
const response = await ctx.client.session.messages({ path: { id: coordinatorSessionID }, query: { limit: 20 } });
// Walks messages backwards to find the last assistant message with model info

This allows spawned sessions to use the same model as the coordinator by default, with an optional model override:

model: { providerID: "anthropic", modelID: "claude-4-sonnet" }

5.6 The Coordination Logic

The coordination flow is:

  1. Coordinator calls spawn with task names + prompt template
  2. Plugin creates worktree + session + stores state mapping with parentSessionID
  3. Plugin sends initial prompt to each session via session.promptAsync
  4. Sessions run asynchronously in background
  5. Detection module monitors sessions via SSE for anomalies
  6. If anomalies detected → notifications sent to coordinator via session.promptAsync
  7. Implementation sessions can notify coordinator using their parentSessionID
  8. Coordinator can message any session, or abort stuck sessions

5.7 Swarm vs Spawn

Feature swarm spawn
Task names Array of strings Array of strings
Branch prefix Configurable (default wt/) Configurable (default wt/)
Initial prompt None Template with {{task}} substitution
Agent selection None Optional agent field
Model selection Inherits coordinator Inherits coordinator or explicit model
Session type session.create with parentID session.create with parentID
Open sessions UI Optional openSessions flag Not available
Error recovery Skip on failure, continue Clean up worktree+branch on failure

6. Integration with OpenCode

6.1 Plugin API Usage

The plugin uses these OpenCode SDK/plugin interfaces:

// Plugin factory
const OpenCoordinatorPlugin: Plugin = async (ctx) => {
  // ctx: PluginInput
  //   ctx.client: OpencodeClient (session, tui, app, global APIs)
  //   ctx.worktree: string (repo root path)
  //   ctx.project: { id, path }
  //   ctx.directory: string
  //   ctx.session: { id }
  //   ctx.$: ShellExecutor (for git commands)
  //   ctx.$.cwd(path): Scoped ShellExecutor
};

6.2 OpenCode Client APIs Used

API Usage
ctx.client.session.create() Create sessions (start, open, fork, swarm, spawn)
ctx.client.session.promptAsync() Send messages/prompts to sessions (spawn, message, notify)
ctx.client.session.abort() Abort a session (abort operation)
ctx.client.session.get() Get session info (dashboard)
ctx.client.session.messages() Get session messages (resolve coordinator model)
ctx.client.session.update() Update session title
ctx.client.tui.openSessions() Open sessions UI panel
ctx.client.app.log() Log messages to OpenCode
ctx.client.global.event() SSE event stream (detection)
ctx.$ / ctx.$.cwd() Execute shell commands (git)

6.3 Plugin Hooks Registered

// index.ts
return {
  tool: createTools(ctx),              // Register worktree tool
  
  event: async ({ event }) => {        // Event handler
    // On session.deleted → remove state mappings + metrics
  },
  
  "tool.execute.before": async (input, output) => {
    // Auto-inject workdir for bash commands when session mapped to worktree
    if (input.tool === "bash" && input.sessionID) {
      const entry = await findSessionEntry(input.sessionID);
      if (entry && !output.args.workdir) {
        output.args.workdir = entry.worktreePath;
      }
    }
  },
  
  "shell.env": async (input, output) => {
    // Inject OPENCODE_WORKTREE_PATH + OPENCODE_WORKTREE_BRANCH env vars
    if (input.sessionID) {
      const entry = await findSessionEntry(input.sessionID);
      if (entry) {
        output.env.OPENCODE_WORKTREE_PATH = entry.worktreePath;
        output.env.OPENCODE_WORKTREE_BRANCH = entry.branch;
      }
    }
  },
  
  "experimental.session.compacting": async (_input, output) => {
    // Custom compaction prompt for spawned sessions
    output.prompt = `You are compacting your own session...`;
  },
};

Key hooks:

  • tool.execute.before: Intercept bash commands → auto-set workdir to worktree path
  • shell.env: Set environment variables in spawned session shells
  • experimental.session.compacting: Custom prompt for context compaction
  • event: Listen for session.deleted → cleanup state + metrics

6.4 Starting Detection on Plugin Load

// index.ts
const OpenCoordinatorPlugin: Plugin = async (ctx) => {
  const _detectionController = startDetection(ctx);
  // ... reconciliation, hooks
};

The detection module starts an SSE event stream immediately on plugin load and runs indefinitely until the AbortController is triggered.


7. Configuration

7.1 Plugin Config

Minimal configuration. The plugin accepts no runtime config — it's just added to the plugin list:

{
  "plugin": ["@alkdev/open-coordinator"]
}

There's no config schema like open-tasks' TypeBox-validated config. The only external config is:

  • State file: ~/.config/opencode/open-coordinator/state.json (or ${XDG_CONFIG_HOME}/opencode/open-coordinator/state.json)
  • Environment variable: XDG_CONFIG_HOME for config directory override

7.2 CLI Config Helper

The src/cli.ts provides a standalone installer:

bunx open-coordinator add
# → Updates opencode.json to add the plugin
# → Supports --config, --plugin, --dry-run flags

Uses jsonc-parser to safely modify the OpenCode JSONC config file.

7.3 State File Format

{
  "entries": [
    {
      "worktreePath": "/repo/.worktrees/wt-auth-setup",
      "branch": "wt/auth-setup",
      "sessionID": "ses_abc123",
      "parentSessionID": "ses_coordinator456",
      "task": "auth-setup",
      "status": "active",
      "createdAt": "2026-04-28T10:00:00.000Z",
      "completedAt": null
    }
  ]
}

State operations are all file-based with atomic write (write to temp + rename):

// state.ts: writeState
const tmpPath = path.join(tmpdir(), `open-coordinator-state-${Date.now()}-${random}.tmp`);
await writeFile(tmpPath, content);
await rename(tmpPath, statePath);

7.4 Detection Thresholds (Not Configurable)

// detection/types.ts
DEFAULT_THRESHOLDS = {
  toolErrorThreshold: 5,      // >5 tool errors → HIGH_ERROR_COUNT
  malformedToolThreshold: 1,  // Any malformed tool → MODEL_DEGRADATION
  stallThresholdMs: 60_000,  // 60s no activity while busy → SESSION_STALL
  stallCheckIntervalMs: 30_000, // Check every 30s
}

8.1 No Task Files, Dependencies, or Analysis

The open-coordinator plugin has no concept of:

  • Task files (YAML frontmatter markdown)
  • Task IDs or task metadata
  • Task dependencies (dependsOn)
  • Dependency graphs
  • Critical path analysis
  • Risk/impact/scope assessments
  • Decomposition guidance
  • Parallel group computation
  • Bottleneck detection
  • Workflow cost estimation

8.2 What It Has Instead

The closest concept to "tasks" in open-coordinator:

  1. Task names as labels: The swarm and spawn operations accept tasks: string[] which are used purely as branch name stems. Example: "auth-setup" → branch wt/auth-setup.

  2. Task labels in state: A task?: string field stored per session mapping, used for:

    • Dashboard display
    • Notify message prefixes ([auth-setup] Done!)
    • Anomaly notification formatting
  3. Prompt template substitution: The spawn operation supports prompt: "Your task: {{task}}" which substitutes the task name into the prompt text.

  4. Sequential task processing: swarm and spawn process tasks sequentially in a for loop — no parallelism, no dependency awareness, no ordering optimization.

8.3 Potential Integration Points for open-tasks

If open-tasks were to combine concepts with open-coordinator, the natural integration points would be:

  1. Task → Worktree Mapping: Read task files from tasks/ directory, use the task ID and metadata (scope, risk, dependencies) to drive worktree creation decisions

  2. Dependency-Aware Scheduling: Use @alkdev/taskgraph's parallelGroups() and criticalPath() to determine which tasks can be spawned in parallel vs. sequentially

  3. Decomposition-Guided Splits: Use shouldDecomposeTask() to decide whether a task should be split before spawning

  4. Risk-Aware Priority: Use task risk/impact levels to influence spawn order and model assignment

  5. Status Propagation: Task status (pending → in-progress → completed) could be synced with session status (active → completed/failed)


9. Detection & Monitoring System

9.1 Architecture

Plugin Load → startDetection(ctx)
                    |
                    v
            SSE Stream (ctx.client.global.event)
                    |
                    v
            handleEvent(ctx, event, thresholds)
                    |
                    ├── Extract sessionID from event
                    ├── Check if spawned session (lookup in state.json)
                    ├── Update SessionMetrics (tool errors, malformed tools, activity time)
                    ├── checkAllAnomalies(metrics, thresholds)
                    └── If anomalies → notifyCoordinator(parentSessionID, ...)
                    
            + setInterval (stall detection every 30s)
                    |
                    v
            For each session in sessionMetrics Map:
              checkAllAnomalies → detect SESSION_STALL
              If stall → notifyCoordinator

9.2 Anomaly Types

Type Detection Severity Notification Action
MODEL_DEGRADATION tool === "tool" in SSE events (malformed tool calls) High Suggests abort
HIGH_ERROR_COUNT >5 tool errors in session Medium Suggests checking session
SESSION_STALL No activity for 60s while busy Medium Suggests "please continue" message

9.3 Notification Format

⚠️ ANOMALY DETECTED [wt/auth-setup]

Session: ses_abc123
Branch: wt/auth-setup
Issue: SESSION_STALL (medium severity)

No activity detected while session is busy.
Consider sending: "There was an error, please continue."

Run: worktree({action: "message", args: {sessionID: "ses_abc123", message: "please continue"}})

9.4 Known Issues

From docs/known-issues.md:

  • SSE reconnection can cause listener accumulation (fixed with AbortController + sseMaxRetryAttempts: 15)
  • setInterval for stall detection was not cleared on shutdown (fixed with abort listener)
  • sessionMetrics Map grew unbounded (fixed with cleanup on session.deleted events)

10. Key Architectural Patterns & Design Decisions

10.1 Result Type Pattern

All operations return ToolResult = { ok: true; output: string } | { ok: false; error: string }:

// result.ts
export type ToolResult = { ok: true; output: string } | { ok: false; error: string };
export const ok = (output: string): ToolResult => ({ ok: true, output });
export const err = (error: string): ToolResult => ({ ok: false, error });

Every handler returns a string — no structured data, no JSON. This maximizes LLM readability.

10.2 Git Command Execution

All git commands go through runGit():

export const runGit = async (ctx, args, options = {}) => {
  const shell = options.cwd ? ctx.$.cwd(options.cwd) : ctx.$;
  const result = await shell`git ${args}`.nothrow().quiet();
  return { ok: result.exitCode === 0, stdout, stderr, exitCode, command };
};

This uses OpenCode's shell executor (ctx.$) which provides sandboxed execution.

10.3 Session Mapping State Machine

Sessions have an implicit state machine:

active → completed (notify with level="info")
active → failed    (notify with level="blocking")
active → aborted   (abort operation)

The state file tracks status and completedAt but these are advisory — the OpenCode session lifecycle is the real authority.

10.4 Reconciliation on Startup

// index.ts
const reconcileResult = await reconcileState();
// → Reads state.json, checks if each worktreePath still exists on disk
// → Removes orphaned entries (worktrees deleted outside the plugin)

10.5 Cleanup on Session Deletion

// index.ts event handler
event: async ({ event }) => {
  const sessionID = getDeletedSessionId(event);
  if (!sessionID) return;
  deleteSessionMetrics(sessionID);     // Remove from detection Map
  await removeSessionMappings(sessionID); // Remove from state.json
}

11. Comparison: open-coordinator vs open-tasks

Aspect open-coordinator open-tasks
Purpose Git worktree orchestration + agent session management Task graph analysis, dependency scheduling, decomposition
Core Library None (direct git + OpenCode SDK) @alkdev/taskgraph (graphology-based)
Data Source state.json (session mappings) tasks/ directory (YAML frontmatter markdown)
Single Tool worktree({action, args}) tasks({tool, args})
Registry Pattern Yes (17 operations) Yes (15 operations)
Role System Coordinator vs Implementation No role system
Task Concept Simple string labels for branches Structured task objects with metadata
Dependencies None dependsOn graph analysis
Analysis Anomaly detection (SSE) Critical path, parallel groups, bottlenecks, risk
Config None (just plugin list) TypeBox-validated config with source options
State File-based (state.json) Task files on disk
Detection Real-time SSE monitoring No monitoring

12. Recommendations for Integration

12.1 Complementary, Not Overlapping

Open-coordinator and open-tasks are complementary: one manages git worktrees + sessions, the other manages task analysis + scheduling. They don't compete — they fill different roles in the alk.dev trio.

12.2 Integration Opportunities

  1. Task-aware spawn: open-tasks could provide parallelGroups() output to open-coordinator's spawn operation, creating worktrees in dependency order rather than arbitrary order.

  2. Risk-based model assignment: Tasks with risk: high could automatically use more capable models in spawned sessions.

  3. Status propagation: When a spawned session completes, open-tasks could update the task file's status field from pending to completed.

  4. Decomposition → swarm: When shouldDecomposeTask() returns true, open-coordinator could automatically split a task into sub-tasks for parallel work.

  5. Critical path awareness: open-tasks' criticalPath() could inform open-coordinator about which tasks to prioritize for spawn ordering.

12.3 What open-coordinator Does NOT Need from open-tasks

  • Role-based access (already implemented differently)
  • Tool dispatch pattern (already shares same pattern)
  • Anomaly detection (already has its own)
  • State persistence (different domain — sessions vs. task graphs)

References

  • Source: /workspace/@alkimiadev/open-coordinator/src/ (all files read in full)
  • Architecture doc: /workspace/@alkimiadev/open-coordinator/ARCHITECTURE.md
  • AGENTS.md: /workspace/@alkimiadev/open-coordinator/AGENTS.md
  • README: /workspace/@alkimiadev/open-coordinator/README.md
  • Known issues: /workspace/@alkimiadev/open-coordinator/docs/known-issues.md
  • RESEARCH.md (historical): /workspace/@alkimiadev/open-coordinator/RESEARCH.md
  • Tests: /workspace/@alkimiadev/open-coordinator/tests/