diff --git a/README.md b/README.md index c62a26c..e434208 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,21 @@ **Every group chat becomes an autonomous development team.** -Add the agent to a Telegram group, point it at a GitLab repo — that group now has an **orchestrator** managing the backlog, a **DEV** sub-agent session writing code, and a **QA** sub-agent session reviewing it. All autonomous. Add another group, get another team. Each project runs in complete isolation with its own task queue, workers, and session state. +Add the agent to a Telegram group, point it at a GitLab repo — that group now has an **orchestrator** managing the backlog, a **DEV** worker session writing code, and a **QA** worker session reviewing it. All autonomous. Add another group, get another team. Each project runs in complete isolation with its own task queue, workers, and session state. DevClaw is the [OpenClaw](https://openclaw.ai) plugin that makes this work. ## Why -[OpenClaw](https://openclaw.ai) is great at giving AI agents the ability to develop software — spawn sub-agent sessions, manage sessions, work with code. But running a real multi-project development pipeline exposes a gap: the orchestration layer between "agent can write code" and "agent reliably manages multiple projects" is brittle. Every task involves 10+ coordinated steps across GitLab labels, session state, model selection, and audit logging. Agents forget steps, corrupt state, null out session IDs they should preserve, or pick the wrong model for the job. +[OpenClaw](https://openclaw.ai) is great at giving AI agents the ability to develop software — spawn worker sessions, manage sessions, work with code. But running a real multi-project development pipeline exposes a gap: the orchestration layer between "agent can write code" and "agent reliably manages multiple projects" is brittle. Every task involves 10+ coordinated steps across GitLab labels, session state, model selection, and audit logging. Agents forget steps, corrupt state, null out session IDs they should preserve, or pick the wrong model for the job. DevClaw fills that gap with guardrails. It gives the orchestrator atomic tools that make it impossible to forget a label transition, lose a session reference, or skip an audit log entry. The complexity of multi-project orchestration moves from agent instructions (that LLMs follow imperfectly) into deterministic code (that runs the same way every time). ## The idea -One orchestrator agent manages all your projects. It reads task backlogs, creates issues, decides priorities, and delegates work. For each task, it spawns (or reuses) a **DEV** sub-agent session to write code or a **QA** sub-agent session to review it. Every Telegram group is a separate project — the orchestrator keeps them completely isolated while managing them all from a single process. +One orchestrator agent manages all your projects. It reads task backlogs, creates issues, decides priorities, and delegates work. For each task, DevClaw creates (or reuses) a **DEV** worker session to write code or a **QA** worker session to review it. Every Telegram group is a separate project — the orchestrator keeps them completely isolated while managing them all from a single process. -DevClaw gives the orchestrator four tools that replace hundreds of lines of manual orchestration logic. Instead of following a 10-step checklist per task (fetch issue, check labels, pick model, check for existing session, transition label, update state, log audit event...), it calls `task_pickup` and the plugin handles everything atomically. +DevClaw gives the orchestrator four tools that replace hundreds of lines of manual orchestration logic. Instead of following a 10-step checklist per task (fetch issue, check labels, pick model, check for existing session, transition label, dispatch task, update state, log audit event...), it calls `task_pickup` and the plugin handles everything atomically — including session dispatch. ## How it works @@ -26,33 +26,33 @@ graph TB direction TB A_O["🎯 Orchestrator"] A_GL[GitLab Issues] - A_DEV["🔧 DEV (sub-agent session)"] - A_QA["🔍 QA (sub-agent session)"] + A_DEV["🔧 DEV (worker session)"] + A_QA["🔍 QA (worker session)"] A_O -->|task_pickup| A_GL - A_O -->|spawns / sends| A_DEV - A_O -->|spawns / sends| A_QA + A_O -->|task_pickup dispatches| A_DEV + A_O -->|task_pickup dispatches| A_QA end subgraph "Group Chat B" direction TB B_O["🎯 Orchestrator"] B_GL[GitLab Issues] - B_DEV["🔧 DEV (sub-agent session)"] - B_QA["🔍 QA (sub-agent session)"] + B_DEV["🔧 DEV (worker session)"] + B_QA["🔍 QA (worker session)"] B_O -->|task_pickup| B_GL - B_O -->|spawns / sends| B_DEV - B_O -->|spawns / sends| B_QA + B_O -->|task_pickup dispatches| B_DEV + B_O -->|task_pickup dispatches| B_QA end subgraph "Group Chat C" direction TB C_O["🎯 Orchestrator"] C_GL[GitLab Issues] - C_DEV["🔧 DEV (sub-agent session)"] - C_QA["🔍 QA (sub-agent session)"] + C_DEV["🔧 DEV (worker session)"] + C_QA["🔍 QA (worker session)"] C_O -->|task_pickup| C_GL - C_O -->|spawns / sends| C_DEV - C_O -->|spawns / sends| C_QA + C_O -->|task_pickup dispatches| C_DEV + C_O -->|task_pickup dispatches| C_QA end AGENT["Single OpenClaw Agent"] @@ -65,7 +65,7 @@ It's the same agent process — but each group chat gives it a different project ## Task lifecycle -Every task (GitLab issue) moves through a fixed pipeline of label states. Issues are created by the orchestrator agent or by sub-agent sessions — not manually. DevClaw tools handle every transition atomically — label change, state update, audit log, and session management in a single call. +Every task (GitLab issue) moves through a fixed pipeline of label states. Issues are created by the orchestrator agent or by worker sessions — not manually. DevClaw tools handle every transition atomically — label change, state update, audit log, and session management in a single call. ```mermaid stateDiagram-v2 @@ -88,23 +88,25 @@ stateDiagram-v2 ## Session reuse -Sub-agent sessions are expensive to start — each new spawn requires the agent to read the full codebase (~50K tokens). DevClaw preserves session IDs across task completions. When a DEV finishes task A and picks up task B on the same project, the plugin detects the existing session and returns `"sessionAction": "send"` instead of `"spawn"` — the orchestrator sends the new task to the running session instead of creating a new one. +Worker sessions are expensive to start — each new spawn requires the session to read the full codebase (~50K tokens). DevClaw maintains **separate sessions per model per role** (session-per-model design). When a DEV finishes task A and picks up task B on the same project with the same model, the plugin detects the existing session and sends the task directly — no new session needed. + +The plugin handles session dispatch internally via OpenClaw CLI. The orchestrator agent never calls `sessions_spawn` or `sessions_send` — it just calls `task_pickup` and the plugin does the rest. ```mermaid sequenceDiagram participant O as Orchestrator participant DC as DevClaw Plugin participant GL as GitLab - participant S as Sub-agent Session + participant S as Worker Session O->>DC: task_pickup({ issueId: 42, role: "dev" }) DC->>GL: Fetch issue, verify label DC->>DC: Select model (haiku/sonnet/opus) - DC->>DC: Check existing session + DC->>DC: Check existing session for selected model DC->>GL: Transition label (To Do → Doing) + DC->>S: Dispatch task via CLI (create or reuse session) DC->>DC: Update projects.json, write audit log - DC-->>O: { sessionAction: "send", sessionId: "...", model: "sonnet" } - O->>S: sessions_send (task description) + DC-->>O: { success: true, announcement: "🔧 DEV (sonnet) picking up #42" } ``` ## Model selection @@ -134,22 +136,32 @@ All project state lives in a single `memory/projects.json` file in the orchestra "baseBranch": "development", "dev": { "active": false, - "sessionId": "agent:orchestrator:subagent:a9e4d078-...", "issueId": null, - "model": "haiku" + "model": "haiku", + "sessions": { + "haiku": "agent:orchestrator:subagent:a9e4d078-...", + "sonnet": "agent:orchestrator:subagent:b3f5c912-...", + "opus": null + } }, "qa": { "active": false, - "sessionId": "agent:orchestrator:subagent:18707821-...", "issueId": null, - "model": "grok" + "model": "grok", + "sessions": { + "grok": "agent:orchestrator:subagent:18707821-..." + } } } } } ``` -Key design decision: when a worker completes a task, `sessionId` and `model` are **preserved** (only `active` and `issueId` are cleared). This enables session reuse on the next pickup. +Key design decisions: +- **Session-per-model** — each model gets its own worker session, accumulating context independently. Model selection maps directly to a session key. +- **Sessions preserved on completion** — when a worker completes a task, `sessions` map is **preserved** (only `active` and `issueId` are cleared). This enables session reuse on the next pickup. +- **Plugin-controlled dispatch** — the plugin creates and dispatches to sessions via OpenClaw CLI (`sessions.patch` + `openclaw agent`). The orchestrator agent never calls `sessions_spawn` or `sessions_send`. +- **Sessions persist indefinitely** — no auto-cleanup. `session_health` handles manual cleanup when needed. All writes go through atomic temp-file-then-rename to prevent corruption. @@ -170,11 +182,13 @@ Pick up a task from the GitLab queue for a DEV or QA worker. 2. Validates no active worker for this role 3. Fetches issue from GitLab, verifies correct label state 4. Selects model based on task complexity -5. Detects session reuse opportunity -6. Transitions GitLab label (e.g. `To Do` → `Doing`) -7. Updates `projects.json` state -8. Writes audit log entry -9. Returns structured instructions for the orchestrator +5. Looks up existing session for selected model (session-per-model) +6. Creates session via Gateway RPC if new (`sessions.patch`) +7. Dispatches task to worker session via CLI (`openclaw agent`) +8. Transitions GitLab label (e.g. `To Do` → `Doing`) +9. Updates `projects.json` state (active, issueId, model, session key) +10. Writes audit log entry +11. Returns announcement text for the orchestrator to post ### `task_complete` @@ -205,10 +219,13 @@ Detects and optionally fixes state inconsistencies. **Parameters:** - `autoFix` (boolean, optional) — Auto-fix zombies and stale state -- `activeSessions` (string[], optional) — Live session IDs from `sessions_list` + +**What it does:** +- Queries live sessions via Gateway RPC (`sessions.list`) +- Cross-references with `projects.json` worker state **Checks:** -- Active worker with no session ID (critical) +- Active worker with no session key (critical) - Active worker whose session is dead — zombie (critical) - Worker active for >2 hours (warning) - Inactive worker with lingering issue ID (warning) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 47e2620..7f8ec09 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -33,15 +33,34 @@ Why per-model instead of switching models on one session: ### Plugin-controlled session lifecycle -DevClaw controls the full session lifecycle — the orchestrator agent does NOT call `sessions_spawn` or `sessions_send` directly. Instead, the plugin uses the OpenClaw Gateway RPC and CLI to manage sessions deterministically: +DevClaw controls the **full** session lifecycle end-to-end. The orchestrator agent never calls `sessions_spawn` or `sessions_send` — the plugin handles session creation and task dispatch internally using the OpenClaw CLI: ``` -Plugin control path: - 1. sessions.patch (Gateway RPC) → create session entry + set model - 2. openclaw agent (CLI) → send message to session +Plugin dispatch (inside task_pickup): + 1. Select model, look up session, decide spawn vs send + 2. New session: openclaw gateway call sessions.patch → create entry + set model + openclaw agent --session-id --message "task..." + 3. Existing: openclaw agent --session-id --message "task..." + 4. Return result to orchestrator (announcement text, no session instructions) ``` -This moves session management from brittle agent instructions into deterministic plugin code. +The agent's only job after `task_pickup` returns is to post the announcement to Telegram. Everything else — model selection, session creation, task dispatch, state update, audit logging — is deterministic plugin code. + +**Why this matters:** Previously the plugin returned instructions like `{ sessionAction: "spawn", model: "sonnet" }` and the agent had to correctly call `sessions_spawn` with the right params. This was the fragile handoff point where agents would forget `cleanup: "keep"`, use wrong models, or corrupt session state. Moving dispatch into the plugin eliminates that entire class of errors. + +**Session persistence:** Sessions created via `sessions.patch` persist indefinitely (no auto-cleanup). The plugin manages lifecycle explicitly through `session_health`. + +**What we trade off vs. registered sub-agents:** + +| Feature | Sub-agent system | Plugin-controlled | DevClaw equivalent | +|---|---|---|---| +| Auto-reporting | Sub-agent reports to parent | No | Heartbeat polls for completion | +| Concurrency control | `maxConcurrent` | No | `task_pickup` checks `active` flag | +| Lifecycle tracking | Parent-child registry | No | `projects.json` tracks all sessions | +| Timeout detection | `runTimeoutSeconds` | No | `session_health` flags stale >2h | +| Cleanup | Auto-archive | No | `session_health` manual cleanup | + +DevClaw provides equivalent guardrails for everything except auto-reporting, which the heartbeat handles. ## System overview @@ -54,6 +73,8 @@ graph TB subgraph "OpenClaw Runtime" MS[Main Session
orchestrator agent] + GW[Gateway RPC
sessions.patch / sessions.list] + CLI[openclaw agent CLI] DEV_H[DEV session
haiku] DEV_S[DEV session
sonnet] DEV_O[DEV session
opus] @@ -70,12 +91,6 @@ graph TB AL[audit.log] end - subgraph "OpenClaw Gateway" - SP[sessions.patch] - SL[sessions.list] - CLI[openclaw agent CLI] - end - subgraph "External" GL[GitLab] REPO[Git Repository] @@ -94,8 +109,8 @@ graph TB TP -->|transitions labels| GL TP -->|reads/writes| PJ TP -->|appends| AL - TP -->|creates/patches session| SP - TP -->|sends task to session| CLI + TP -->|creates session| GW + TP -->|dispatches task| CLI TC -->|transitions labels| GL TC -->|closes/reopens| GL @@ -108,14 +123,14 @@ graph TB QS -->|appends| AL SH -->|reads/writes| PJ - SH -->|checks sessions| SL + SH -->|checks sessions| GW SH -->|reverts labels| GL SH -->|appends| AL - CLI -->|runs agent turn| DEV_H - CLI -->|runs agent turn| DEV_S - CLI -->|runs agent turn| DEV_O - CLI -->|runs agent turn| QA_G + CLI -->|sends task| DEV_H + CLI -->|sends task| DEV_S + CLI -->|sends task| DEV_O + CLI -->|sends task| QA_G DEV_H -->|writes code, creates MRs| REPO DEV_S -->|writes code, creates MRs| REPO @@ -135,7 +150,7 @@ sequenceDiagram participant DC as DevClaw Plugin participant GW as Gateway RPC participant CLI as openclaw agent CLI - participant DEV as DEV Sub-agent
Session (sonnet) + participant DEV as DEV Session
(sonnet) participant GL as GitLab Note over H,GL: Issue exists in queue (To Do) @@ -151,23 +166,19 @@ sequenceDiagram MS->>DC: task_pickup({ issueId: 42, role: "dev", ... }) DC->>DC: selectModel → "sonnet" DC->>DC: lookup dev.sessions.sonnet → null (first time) - DC->>DC: generate new UUID DC->>GL: glab issue update 42 --unlabel "To Do" --label "Doing" - DC->>DC: update projects.json (active, issueId, model) - DC->>GW: sessions.patch({ key: "...subagent:", model: "anthropic/claude-sonnet-4-5" }) - GW-->>DC: { ok: true } - DC->>CLI: openclaw agent --agent orchestrator --session-id --message "Build login page for #42..." - CLI->>DEV: creates session, sends task - DC->>DC: store UUID in dev.sessions.sonnet - DC->>DC: append audit.log - DC-->>MS: { success: true, announcement: "🔧 Spawning DEV (sonnet) for #42" } + DC->>GW: sessions.patch({ key: new-session-key, model: "sonnet" }) + DC->>CLI: openclaw agent --session-id --message "Build login page for #42..." + CLI->>DEV: creates session, delivers task + DC->>DC: store session key in projects.json + append audit.log + DC-->>MS: { success: true, announcement: "🔧 DEV (sonnet) picking up #42" } - MS->>TG: "🔧 Spawning DEV (sonnet) for #42: Add login page" + MS->>TG: "🔧 DEV (sonnet) picking up #42: Add login page" TG->>H: sees announcement Note over DEV: Works autonomously — reads code, writes code, creates MR + Note over MS: Heartbeat detects DEV session idle → triggers task_complete - DEV-->>MS: "done, MR merged" MS->>DC: task_complete({ role: "dev", result: "done", ... }) DC->>GL: glab issue update 42 --unlabel "Doing" --label "To Test" DC->>DC: deactivate worker (sessions preserved) @@ -188,11 +199,11 @@ sequenceDiagram MS->>DC: task_pickup({ issueId: 57, role: "dev", ... }) DC->>DC: selectModel → "sonnet" - DC->>DC: lookup dev.sessions.sonnet → (exists!) - Note over DC: No sessions.patch needed — model already set - DC->>CLI: openclaw agent --session-id --message "Fix validation for #57..." - CLI->>DEV: sends to existing session (has full codebase context) - DC-->>MS: { success: true, announcement: "⚡ Sending DEV (sonnet) for #57" } + DC->>DC: lookup dev.sessions.sonnet → existing key! + Note over DC: No sessions.patch needed — session already exists + DC->>CLI: openclaw agent --session-id --message "Fix validation for #57..." + CLI->>DEV: delivers task to existing session (has full codebase context) + DC-->>MS: { success: true, announcement: "⚡ DEV (sonnet) picking up #57" } ``` Session reuse saves ~50K tokens per task by not re-reading the codebase. @@ -242,7 +253,7 @@ sequenceDiagram ### Phase 3: DEV pickup -The plugin handles everything — model selection, session management, label transition, state update, and dispatching the task to the correct sub-agent session. +The plugin handles everything end-to-end — model selection, session lookup, label transition, state update, **and** task dispatch to the worker session. The agent's only job after is to post the announcement. ```mermaid sequenceDiagram @@ -250,9 +261,9 @@ sequenceDiagram participant TP as task_pickup participant GL as GitLab participant MS as Model Selector + participant GW as Gateway RPC + participant CLI as openclaw agent CLI participant PJ as projects.json - participant GW as Gateway - participant CLI as openclaw agent participant AL as audit.log A->>TP: task_pickup({ issueId: 42, role: "dev", projectGroupId: "-123" }) @@ -263,25 +274,21 @@ sequenceDiagram TP->>MS: selectModel("Add login page", description, "dev") MS-->>TP: { alias: "sonnet" } TP->>PJ: lookup dev.sessions.sonnet - alt Session exists - TP->>CLI: openclaw agent --session-id --message "task..." - else New session - TP->>GW: sessions.patch({ key: new-uuid, model: "sonnet" }) - TP->>CLI: openclaw agent --session-id --message "task..." - TP->>PJ: store UUID in dev.sessions.sonnet - end TP->>GL: glab issue update 42 --unlabel "To Do" --label "Doing" - TP->>PJ: activateWorker (active=true, issueId=42, model=sonnet) + alt New session + TP->>GW: sessions.patch({ key: new-key, model: "sonnet" }) + end + TP->>CLI: openclaw agent --session-id --message "task..." + TP->>PJ: activateWorker + store session key TP->>AL: append task_pickup + model_selection TP-->>A: { success: true, announcement: "🔧 ..." } ``` **Writes:** - `GitLab`: label "To Do" → "Doing" -- `projects.json`: dev.active=true, dev.issueId="42", dev.model="sonnet", dev.sessions.sonnet=uuid +- `projects.json`: dev.active=true, dev.issueId="42", dev.model="sonnet", dev.sessions.sonnet=key - `audit.log`: 2 entries (task_pickup, model_selection) -- `Gateway`: session entry created/reused -- `Sub-agent`: task message delivered +- `Session`: task message delivered to worker session via CLI ### Phase 4: DEV works @@ -385,13 +392,10 @@ sequenceDiagram participant SH as session_health participant QS as queue_status participant TP as task_pickup - participant GW as Gateway - Note over A: Heartbeat triggered A->>SH: session_health({ autoFix: true }) - SH->>GW: sessions.list - GW-->>SH: [alive sessions] + Note over SH: Checks sessions via Gateway RPC (sessions.list) SH-->>A: { healthy: true } A->>QS: queue_status() @@ -399,7 +403,7 @@ sequenceDiagram Note over A: DEV idle + To Do #43 → pick up A->>TP: task_pickup({ issueId: 43, role: "dev", ... }) - Note over TP: Plugin handles everything:
model select → session lookup →
gateway patch → CLI send →
label transition → state update + Note over TP: Plugin handles everything:
model select → session lookup →
label transition → dispatch task →
state update → audit log Note over A: QA idle + To Test #44 → pick up A->>TP: task_pickup({ issueId: 44, role: "qa", ... }) @@ -423,27 +427,27 @@ Every piece of data and where it lives: ┌─────────────────────────────────────────────────────────────────┐ │ DevClaw Plugin (orchestration logic) │ │ │ -│ task_pickup → model select + session manage + label + state │ +│ task_pickup → model + label + dispatch + state (end-to-end) │ │ task_complete → label transition + state update + git pull │ │ queue_status → read labels + read state │ -│ session_health → check sessions via gateway + fix zombies │ +│ session_health → check sessions + fix zombies │ └─────────────────────────────────────────────────────────────────┘ - ↕ atomic file I/O ↕ Gateway RPC / CLI + ↕ atomic file I/O ↕ OpenClaw CLI (plugin shells out) ┌────────────────────────────────┐ ┌──────────────────────────────┐ -│ memory/projects.json │ │ OpenClaw Gateway │ -│ │ │ │ -│ Per project: │ │ sessions.patch → set model │ -│ dev: │ │ sessions.list → list alive │ -│ active, issueId, model │ │ sessions.delete → cleanup │ -│ sessions: │ │ │ -│ haiku: │ │ openclaw agent CLI │ -│ sonnet: │ │ → send message to session │ -│ opus: │ │ → creates session if new │ -│ qa: │ │ │ -│ active, issueId, model │ └──────────────────────────────┘ -│ sessions: │ -│ grok: │ -└────────────────────────────────┘ +│ memory/projects.json │ │ OpenClaw Gateway + CLI │ +│ │ │ (called by plugin, not agent)│ +│ Per project: │ │ │ +│ dev: │ │ openclaw gateway call │ +│ active, issueId, model │ │ sessions.patch → create │ +│ sessions: │ │ sessions.list → health │ +│ haiku: │ │ sessions.delete → cleanup │ +│ sonnet: │ │ │ +│ opus: │ │ openclaw agent │ +│ qa: │ │ --session-id │ +│ active, issueId, model │ │ --message "task..." │ +│ sessions: │ │ → dispatches to session │ +│ grok: │ │ │ +└────────────────────────────────┘ └──────────────────────────────┘ ↕ append-only ┌─────────────────────────────────────────────────────────────────┐ │ memory/audit.log (observability) │ @@ -485,7 +489,7 @@ graph LR L[Label transitions] S[Worker state] M[Model selection] - SS[Session spawn/send] + SD[Session dispatch
create + send via CLI] A[Audit logging] Z[Zombie cleanup] end @@ -513,9 +517,10 @@ graph LR | Failure | Detection | Recovery | |---|---|---| -| Session dies mid-task | `session_health` checks via `sessions.list` gateway RPC | `autoFix`: reverts label, clears active state, removes dead session from sessions map. Next heartbeat picks up task again (spawns fresh session for that model). | -| glab command fails | Tool throws error, returns to agent | Agent retries or reports to Telegram group | -| Gateway RPC fails | `sessions.patch` or `openclaw agent` returns error | Tool returns error to orchestrator with details. Agent can retry or report. | +| Session dies mid-task | `session_health` checks via `sessions.list` Gateway RPC | `autoFix`: reverts label, clears active state, removes dead session from sessions map. Next heartbeat picks up task again (creates fresh session for that model). | +| glab command fails | Plugin tool throws error, returns to agent | Agent retries or reports to Telegram group | +| `openclaw agent` CLI fails | Plugin catches error during dispatch | Plugin rolls back: reverts label, clears active state. Returns error to agent for reporting. | +| `sessions.patch` fails | Plugin catches error during session creation | Plugin rolls back label transition. Returns error. No orphaned state. | | projects.json corrupted | Tool can't parse JSON | Manual fix needed. Atomic writes (temp+rename) prevent partial writes. | | Label out of sync | `task_pickup` verifies label before transitioning | Throws error if label doesn't match expected state. Agent reports mismatch. | | Worker already active | `task_pickup` checks `active` flag | Throws error: "DEV worker already active on project". Must complete current task first. | diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md index 2071885..fa281f1 100644 --- a/docs/ONBOARDING.md +++ b/docs/ONBOARDING.md @@ -42,10 +42,7 @@ In `openclaw.json`, your orchestrator agent needs access to the DevClaw tools: "task_pickup", "task_complete", "queue_status", - "session_health", - "sessions_spawn", - "sessions_send", - "sessions_list" + "session_health" ] } }] @@ -53,7 +50,7 @@ In `openclaw.json`, your orchestrator agent needs access to the DevClaw tools: } ``` -The agent also needs the OpenClaw session tools (`sessions_spawn`, `sessions_send`, `sessions_list`) — DevClaw handles the orchestration logic (labels, state, model selection, audit), but the agent executes the actual session operations to spawn or communicate with DEV/QA sub-agent sessions. +The agent only needs the four DevClaw tools. Session management (`sessions_spawn`, `sessions_send`) is **not needed** — the plugin handles session creation and task dispatch internally via OpenClaw CLI. This eliminates the fragile handoff where agents had to correctly call session tools with the right parameters. ### 3. Create GitLab labels @@ -87,17 +84,23 @@ Add your project to `memory/projects.json` in the orchestrator's workspace: "deployBranch": "development", "dev": { "active": false, - "sessionId": null, "issueId": null, "startTime": null, - "model": null + "model": null, + "sessions": { + "haiku": null, + "sonnet": null, + "opus": null + } }, "qa": { "active": false, - "sessionId": null, "issueId": null, "startTime": null, - "model": null + "model": null, + "sessions": { + "grok": null + } } } } @@ -117,7 +120,7 @@ Issues can be created in multiple ways: - **Via glab CLI** — `cd ~/git/my-project && glab issue create --title "My first task" --label "To Do"` - **Via GitLab UI** — Create an issue and add the "To Do" label -The orchestrator agent and sub-agent sessions can all create and update issues via `glab` tool usage. +The orchestrator agent and worker sessions can all create and update issues via `glab` tool usage. ### 7. Test the pipeline @@ -129,7 +132,7 @@ The agent should call `queue_status` and report the "To Do" issue. Then: > "Pick up issue #1 for DEV" -The agent calls `task_pickup`, which selects a model, transitions the label to "Doing", and returns instructions to spawn or reuse a DEV sub-agent session. +The agent calls `task_pickup`, which selects a model, transitions the label to "Doing", creates or reuses a worker session, and dispatches the task — all in one call. The agent just posts the announcement. ## Adding more projects @@ -148,11 +151,11 @@ Each project is fully isolated — separate queue, separate workers, separate st | Project registration | You (once per project) | Entry in `projects.json` | | Agent definition | You (once) | Agent in `openclaw.json` with tool permissions | | Telegram group setup | You (once per project) | Add bot to group | -| Issue creation | Agent or sub-agents | Created via `glab` tool usage (or manually via GitLab UI) | +| Issue creation | Agent or worker sessions | Created via `glab` tool usage (or manually via GitLab UI) | | Label transitions | Plugin | Atomic `--unlabel` + `--label` via glab | | Model selection | Plugin | Keyword-based heuristic per task | | State management | Plugin | Atomic read/write to `projects.json` | -| Session reuse | Plugin | Detects existing sessions, returns spawn vs send | +| Session management | Plugin | Creates, reuses, and dispatches to sessions via CLI. Agent never touches session tools. | | Audit logging | Plugin | Automatic NDJSON append per tool call | | Zombie detection | Plugin | `session_health` checks active vs alive | | Queue scanning | Plugin | `queue_status` queries GitLab per project |