Refine session management terminology and clarify plugin-controlled lifecycle in documentation
This commit is contained in:
85
README.md
85
README.md
@@ -2,21 +2,21 @@
|
|||||||
|
|
||||||
**Every group chat becomes an autonomous development team.**
|
**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.
|
DevClaw is the [OpenClaw](https://openclaw.ai) plugin that makes this work.
|
||||||
|
|
||||||
## Why
|
## 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).
|
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
|
## 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
|
## How it works
|
||||||
|
|
||||||
@@ -26,33 +26,33 @@ graph TB
|
|||||||
direction TB
|
direction TB
|
||||||
A_O["🎯 Orchestrator"]
|
A_O["🎯 Orchestrator"]
|
||||||
A_GL[GitLab Issues]
|
A_GL[GitLab Issues]
|
||||||
A_DEV["🔧 DEV (sub-agent session)"]
|
A_DEV["🔧 DEV (worker session)"]
|
||||||
A_QA["🔍 QA (sub-agent session)"]
|
A_QA["🔍 QA (worker session)"]
|
||||||
A_O -->|task_pickup| A_GL
|
A_O -->|task_pickup| A_GL
|
||||||
A_O -->|spawns / sends| A_DEV
|
A_O -->|task_pickup dispatches| A_DEV
|
||||||
A_O -->|spawns / sends| A_QA
|
A_O -->|task_pickup dispatches| A_QA
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Group Chat B"
|
subgraph "Group Chat B"
|
||||||
direction TB
|
direction TB
|
||||||
B_O["🎯 Orchestrator"]
|
B_O["🎯 Orchestrator"]
|
||||||
B_GL[GitLab Issues]
|
B_GL[GitLab Issues]
|
||||||
B_DEV["🔧 DEV (sub-agent session)"]
|
B_DEV["🔧 DEV (worker session)"]
|
||||||
B_QA["🔍 QA (sub-agent session)"]
|
B_QA["🔍 QA (worker session)"]
|
||||||
B_O -->|task_pickup| B_GL
|
B_O -->|task_pickup| B_GL
|
||||||
B_O -->|spawns / sends| B_DEV
|
B_O -->|task_pickup dispatches| B_DEV
|
||||||
B_O -->|spawns / sends| B_QA
|
B_O -->|task_pickup dispatches| B_QA
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Group Chat C"
|
subgraph "Group Chat C"
|
||||||
direction TB
|
direction TB
|
||||||
C_O["🎯 Orchestrator"]
|
C_O["🎯 Orchestrator"]
|
||||||
C_GL[GitLab Issues]
|
C_GL[GitLab Issues]
|
||||||
C_DEV["🔧 DEV (sub-agent session)"]
|
C_DEV["🔧 DEV (worker session)"]
|
||||||
C_QA["🔍 QA (sub-agent session)"]
|
C_QA["🔍 QA (worker session)"]
|
||||||
C_O -->|task_pickup| C_GL
|
C_O -->|task_pickup| C_GL
|
||||||
C_O -->|spawns / sends| C_DEV
|
C_O -->|task_pickup dispatches| C_DEV
|
||||||
C_O -->|spawns / sends| C_QA
|
C_O -->|task_pickup dispatches| C_QA
|
||||||
end
|
end
|
||||||
|
|
||||||
AGENT["Single OpenClaw Agent"]
|
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
|
## 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
|
```mermaid
|
||||||
stateDiagram-v2
|
stateDiagram-v2
|
||||||
@@ -88,23 +88,25 @@ stateDiagram-v2
|
|||||||
|
|
||||||
## Session reuse
|
## 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
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
participant O as Orchestrator
|
participant O as Orchestrator
|
||||||
participant DC as DevClaw Plugin
|
participant DC as DevClaw Plugin
|
||||||
participant GL as GitLab
|
participant GL as GitLab
|
||||||
participant S as Sub-agent Session
|
participant S as Worker Session
|
||||||
|
|
||||||
O->>DC: task_pickup({ issueId: 42, role: "dev" })
|
O->>DC: task_pickup({ issueId: 42, role: "dev" })
|
||||||
DC->>GL: Fetch issue, verify label
|
DC->>GL: Fetch issue, verify label
|
||||||
DC->>DC: Select model (haiku/sonnet/opus)
|
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->>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->>DC: Update projects.json, write audit log
|
||||||
DC-->>O: { sessionAction: "send", sessionId: "...", model: "sonnet" }
|
DC-->>O: { success: true, announcement: "🔧 DEV (sonnet) picking up #42" }
|
||||||
O->>S: sessions_send (task description)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Model selection
|
## Model selection
|
||||||
@@ -134,22 +136,32 @@ All project state lives in a single `memory/projects.json` file in the orchestra
|
|||||||
"baseBranch": "development",
|
"baseBranch": "development",
|
||||||
"dev": {
|
"dev": {
|
||||||
"active": false,
|
"active": false,
|
||||||
"sessionId": "agent:orchestrator:subagent:a9e4d078-...",
|
|
||||||
"issueId": null,
|
"issueId": null,
|
||||||
"model": "haiku"
|
"model": "haiku",
|
||||||
|
"sessions": {
|
||||||
|
"haiku": "agent:orchestrator:subagent:a9e4d078-...",
|
||||||
|
"sonnet": "agent:orchestrator:subagent:b3f5c912-...",
|
||||||
|
"opus": null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"qa": {
|
"qa": {
|
||||||
"active": false,
|
"active": false,
|
||||||
"sessionId": "agent:orchestrator:subagent:18707821-...",
|
|
||||||
"issueId": null,
|
"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.
|
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
|
2. Validates no active worker for this role
|
||||||
3. Fetches issue from GitLab, verifies correct label state
|
3. Fetches issue from GitLab, verifies correct label state
|
||||||
4. Selects model based on task complexity
|
4. Selects model based on task complexity
|
||||||
5. Detects session reuse opportunity
|
5. Looks up existing session for selected model (session-per-model)
|
||||||
6. Transitions GitLab label (e.g. `To Do` → `Doing`)
|
6. Creates session via Gateway RPC if new (`sessions.patch`)
|
||||||
7. Updates `projects.json` state
|
7. Dispatches task to worker session via CLI (`openclaw agent`)
|
||||||
8. Writes audit log entry
|
8. Transitions GitLab label (e.g. `To Do` → `Doing`)
|
||||||
9. Returns structured instructions for the orchestrator
|
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`
|
### `task_complete`
|
||||||
|
|
||||||
@@ -205,10 +219,13 @@ Detects and optionally fixes state inconsistencies.
|
|||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
- `autoFix` (boolean, optional) — Auto-fix zombies and stale state
|
- `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:**
|
**Checks:**
|
||||||
- Active worker with no session ID (critical)
|
- Active worker with no session key (critical)
|
||||||
- Active worker whose session is dead — zombie (critical)
|
- Active worker whose session is dead — zombie (critical)
|
||||||
- Worker active for >2 hours (warning)
|
- Worker active for >2 hours (warning)
|
||||||
- Inactive worker with lingering issue ID (warning)
|
- Inactive worker with lingering issue ID (warning)
|
||||||
|
|||||||
@@ -33,15 +33,34 @@ Why per-model instead of switching models on one session:
|
|||||||
|
|
||||||
### Plugin-controlled session lifecycle
|
### 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:
|
Plugin dispatch (inside task_pickup):
|
||||||
1. sessions.patch (Gateway RPC) → create session entry + set model
|
1. Select model, look up session, decide spawn vs send
|
||||||
2. openclaw agent (CLI) → send message to session
|
2. New session: openclaw gateway call sessions.patch → create entry + set model
|
||||||
|
openclaw agent --session-id <key> --message "task..."
|
||||||
|
3. Existing: openclaw agent --session-id <key> --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
|
## System overview
|
||||||
|
|
||||||
@@ -54,6 +73,8 @@ graph TB
|
|||||||
|
|
||||||
subgraph "OpenClaw Runtime"
|
subgraph "OpenClaw Runtime"
|
||||||
MS[Main Session<br/>orchestrator agent]
|
MS[Main Session<br/>orchestrator agent]
|
||||||
|
GW[Gateway RPC<br/>sessions.patch / sessions.list]
|
||||||
|
CLI[openclaw agent CLI]
|
||||||
DEV_H[DEV session<br/>haiku]
|
DEV_H[DEV session<br/>haiku]
|
||||||
DEV_S[DEV session<br/>sonnet]
|
DEV_S[DEV session<br/>sonnet]
|
||||||
DEV_O[DEV session<br/>opus]
|
DEV_O[DEV session<br/>opus]
|
||||||
@@ -70,12 +91,6 @@ graph TB
|
|||||||
AL[audit.log]
|
AL[audit.log]
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "OpenClaw Gateway"
|
|
||||||
SP[sessions.patch]
|
|
||||||
SL[sessions.list]
|
|
||||||
CLI[openclaw agent CLI]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "External"
|
subgraph "External"
|
||||||
GL[GitLab]
|
GL[GitLab]
|
||||||
REPO[Git Repository]
|
REPO[Git Repository]
|
||||||
@@ -94,8 +109,8 @@ graph TB
|
|||||||
TP -->|transitions labels| GL
|
TP -->|transitions labels| GL
|
||||||
TP -->|reads/writes| PJ
|
TP -->|reads/writes| PJ
|
||||||
TP -->|appends| AL
|
TP -->|appends| AL
|
||||||
TP -->|creates/patches session| SP
|
TP -->|creates session| GW
|
||||||
TP -->|sends task to session| CLI
|
TP -->|dispatches task| CLI
|
||||||
|
|
||||||
TC -->|transitions labels| GL
|
TC -->|transitions labels| GL
|
||||||
TC -->|closes/reopens| GL
|
TC -->|closes/reopens| GL
|
||||||
@@ -108,14 +123,14 @@ graph TB
|
|||||||
QS -->|appends| AL
|
QS -->|appends| AL
|
||||||
|
|
||||||
SH -->|reads/writes| PJ
|
SH -->|reads/writes| PJ
|
||||||
SH -->|checks sessions| SL
|
SH -->|checks sessions| GW
|
||||||
SH -->|reverts labels| GL
|
SH -->|reverts labels| GL
|
||||||
SH -->|appends| AL
|
SH -->|appends| AL
|
||||||
|
|
||||||
CLI -->|runs agent turn| DEV_H
|
CLI -->|sends task| DEV_H
|
||||||
CLI -->|runs agent turn| DEV_S
|
CLI -->|sends task| DEV_S
|
||||||
CLI -->|runs agent turn| DEV_O
|
CLI -->|sends task| DEV_O
|
||||||
CLI -->|runs agent turn| QA_G
|
CLI -->|sends task| QA_G
|
||||||
|
|
||||||
DEV_H -->|writes code, creates MRs| REPO
|
DEV_H -->|writes code, creates MRs| REPO
|
||||||
DEV_S -->|writes code, creates MRs| REPO
|
DEV_S -->|writes code, creates MRs| REPO
|
||||||
@@ -135,7 +150,7 @@ sequenceDiagram
|
|||||||
participant DC as DevClaw Plugin
|
participant DC as DevClaw Plugin
|
||||||
participant GW as Gateway RPC
|
participant GW as Gateway RPC
|
||||||
participant CLI as openclaw agent CLI
|
participant CLI as openclaw agent CLI
|
||||||
participant DEV as DEV Sub-agent<br/>Session (sonnet)
|
participant DEV as DEV Session<br/>(sonnet)
|
||||||
participant GL as GitLab
|
participant GL as GitLab
|
||||||
|
|
||||||
Note over H,GL: Issue exists in queue (To Do)
|
Note over H,GL: Issue exists in queue (To Do)
|
||||||
@@ -151,23 +166,19 @@ sequenceDiagram
|
|||||||
MS->>DC: task_pickup({ issueId: 42, role: "dev", ... })
|
MS->>DC: task_pickup({ issueId: 42, role: "dev", ... })
|
||||||
DC->>DC: selectModel → "sonnet"
|
DC->>DC: selectModel → "sonnet"
|
||||||
DC->>DC: lookup dev.sessions.sonnet → null (first time)
|
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->>GL: glab issue update 42 --unlabel "To Do" --label "Doing"
|
||||||
DC->>DC: update projects.json (active, issueId, model)
|
DC->>GW: sessions.patch({ key: new-session-key, model: "sonnet" })
|
||||||
DC->>GW: sessions.patch({ key: "...subagent:<uuid>", model: "anthropic/claude-sonnet-4-5" })
|
DC->>CLI: openclaw agent --session-id <key> --message "Build login page for #42..."
|
||||||
GW-->>DC: { ok: true }
|
CLI->>DEV: creates session, delivers task
|
||||||
DC->>CLI: openclaw agent --agent orchestrator --session-id <uuid> --message "Build login page for #42..."
|
DC->>DC: store session key in projects.json + append audit.log
|
||||||
CLI->>DEV: creates session, sends task
|
DC-->>MS: { success: true, announcement: "🔧 DEV (sonnet) picking up #42" }
|
||||||
DC->>DC: store UUID in dev.sessions.sonnet
|
|
||||||
DC->>DC: append audit.log
|
|
||||||
DC-->>MS: { success: true, announcement: "🔧 Spawning DEV (sonnet) for #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
|
TG->>H: sees announcement
|
||||||
|
|
||||||
Note over DEV: Works autonomously — reads code, writes code, creates MR
|
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", ... })
|
MS->>DC: task_complete({ role: "dev", result: "done", ... })
|
||||||
DC->>GL: glab issue update 42 --unlabel "Doing" --label "To Test"
|
DC->>GL: glab issue update 42 --unlabel "Doing" --label "To Test"
|
||||||
DC->>DC: deactivate worker (sessions preserved)
|
DC->>DC: deactivate worker (sessions preserved)
|
||||||
@@ -188,11 +199,11 @@ sequenceDiagram
|
|||||||
|
|
||||||
MS->>DC: task_pickup({ issueId: 57, role: "dev", ... })
|
MS->>DC: task_pickup({ issueId: 57, role: "dev", ... })
|
||||||
DC->>DC: selectModel → "sonnet"
|
DC->>DC: selectModel → "sonnet"
|
||||||
DC->>DC: lookup dev.sessions.sonnet → <uuid> (exists!)
|
DC->>DC: lookup dev.sessions.sonnet → existing key!
|
||||||
Note over DC: No sessions.patch needed — model already set
|
Note over DC: No sessions.patch needed — session already exists
|
||||||
DC->>CLI: openclaw agent --session-id <uuid> --message "Fix validation for #57..."
|
DC->>CLI: openclaw agent --session-id <key> --message "Fix validation for #57..."
|
||||||
CLI->>DEV: sends to existing session (has full codebase context)
|
CLI->>DEV: delivers task to existing session (has full codebase context)
|
||||||
DC-->>MS: { success: true, announcement: "⚡ Sending DEV (sonnet) for #57" }
|
DC-->>MS: { success: true, announcement: "⚡ DEV (sonnet) picking up #57" }
|
||||||
```
|
```
|
||||||
|
|
||||||
Session reuse saves ~50K tokens per task by not re-reading the codebase.
|
Session reuse saves ~50K tokens per task by not re-reading the codebase.
|
||||||
@@ -242,7 +253,7 @@ sequenceDiagram
|
|||||||
|
|
||||||
### Phase 3: DEV pickup
|
### 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
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
@@ -250,9 +261,9 @@ sequenceDiagram
|
|||||||
participant TP as task_pickup
|
participant TP as task_pickup
|
||||||
participant GL as GitLab
|
participant GL as GitLab
|
||||||
participant MS as Model Selector
|
participant MS as Model Selector
|
||||||
|
participant GW as Gateway RPC
|
||||||
|
participant CLI as openclaw agent CLI
|
||||||
participant PJ as projects.json
|
participant PJ as projects.json
|
||||||
participant GW as Gateway
|
|
||||||
participant CLI as openclaw agent
|
|
||||||
participant AL as audit.log
|
participant AL as audit.log
|
||||||
|
|
||||||
A->>TP: task_pickup({ issueId: 42, role: "dev", projectGroupId: "-123" })
|
A->>TP: task_pickup({ issueId: 42, role: "dev", projectGroupId: "-123" })
|
||||||
@@ -263,25 +274,21 @@ sequenceDiagram
|
|||||||
TP->>MS: selectModel("Add login page", description, "dev")
|
TP->>MS: selectModel("Add login page", description, "dev")
|
||||||
MS-->>TP: { alias: "sonnet" }
|
MS-->>TP: { alias: "sonnet" }
|
||||||
TP->>PJ: lookup dev.sessions.sonnet
|
TP->>PJ: lookup dev.sessions.sonnet
|
||||||
alt Session exists
|
|
||||||
TP->>CLI: openclaw agent --session-id <existing> --message "task..."
|
|
||||||
else New session
|
|
||||||
TP->>GW: sessions.patch({ key: new-uuid, model: "sonnet" })
|
|
||||||
TP->>CLI: openclaw agent --session-id <new-uuid> --message "task..."
|
|
||||||
TP->>PJ: store UUID in dev.sessions.sonnet
|
|
||||||
end
|
|
||||||
TP->>GL: glab issue update 42 --unlabel "To Do" --label "Doing"
|
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 <key> --message "task..."
|
||||||
|
TP->>PJ: activateWorker + store session key
|
||||||
TP->>AL: append task_pickup + model_selection
|
TP->>AL: append task_pickup + model_selection
|
||||||
TP-->>A: { success: true, announcement: "🔧 ..." }
|
TP-->>A: { success: true, announcement: "🔧 ..." }
|
||||||
```
|
```
|
||||||
|
|
||||||
**Writes:**
|
**Writes:**
|
||||||
- `GitLab`: label "To Do" → "Doing"
|
- `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)
|
- `audit.log`: 2 entries (task_pickup, model_selection)
|
||||||
- `Gateway`: session entry created/reused
|
- `Session`: task message delivered to worker session via CLI
|
||||||
- `Sub-agent`: task message delivered
|
|
||||||
|
|
||||||
### Phase 4: DEV works
|
### Phase 4: DEV works
|
||||||
|
|
||||||
@@ -385,13 +392,10 @@ sequenceDiagram
|
|||||||
participant SH as session_health
|
participant SH as session_health
|
||||||
participant QS as queue_status
|
participant QS as queue_status
|
||||||
participant TP as task_pickup
|
participant TP as task_pickup
|
||||||
participant GW as Gateway
|
|
||||||
|
|
||||||
Note over A: Heartbeat triggered
|
Note over A: Heartbeat triggered
|
||||||
|
|
||||||
A->>SH: session_health({ autoFix: true })
|
A->>SH: session_health({ autoFix: true })
|
||||||
SH->>GW: sessions.list
|
Note over SH: Checks sessions via Gateway RPC (sessions.list)
|
||||||
GW-->>SH: [alive sessions]
|
|
||||||
SH-->>A: { healthy: true }
|
SH-->>A: { healthy: true }
|
||||||
|
|
||||||
A->>QS: queue_status()
|
A->>QS: queue_status()
|
||||||
@@ -399,7 +403,7 @@ sequenceDiagram
|
|||||||
|
|
||||||
Note over A: DEV idle + To Do #43 → pick up
|
Note over A: DEV idle + To Do #43 → pick up
|
||||||
A->>TP: task_pickup({ issueId: 43, role: "dev", ... })
|
A->>TP: task_pickup({ issueId: 43, role: "dev", ... })
|
||||||
Note over TP: Plugin handles everything:<br/>model select → session lookup →<br/>gateway patch → CLI send →<br/>label transition → state update
|
Note over TP: Plugin handles everything:<br/>model select → session lookup →<br/>label transition → dispatch task →<br/>state update → audit log
|
||||||
|
|
||||||
Note over A: QA idle + To Test #44 → pick up
|
Note over A: QA idle + To Test #44 → pick up
|
||||||
A->>TP: task_pickup({ issueId: 44, role: "qa", ... })
|
A->>TP: task_pickup({ issueId: 44, role: "qa", ... })
|
||||||
@@ -423,27 +427,27 @@ Every piece of data and where it lives:
|
|||||||
┌─────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ DevClaw Plugin (orchestration logic) │
|
│ 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 │
|
│ task_complete → label transition + state update + git pull │
|
||||||
│ queue_status → read labels + read state │
|
│ 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 │
|
│ memory/projects.json │ │ OpenClaw Gateway + CLI │
|
||||||
│ │ │ │
|
│ │ │ (called by plugin, not agent)│
|
||||||
│ Per project: │ │ sessions.patch → set model │
|
│ Per project: │ │ │
|
||||||
│ dev: │ │ sessions.list → list alive │
|
│ dev: │ │ openclaw gateway call │
|
||||||
│ active, issueId, model │ │ sessions.delete → cleanup │
|
│ active, issueId, model │ │ sessions.patch → create │
|
||||||
│ sessions: │ │ │
|
│ sessions: │ │ sessions.list → health │
|
||||||
│ haiku: <uuid> │ │ openclaw agent CLI │
|
│ haiku: <key> │ │ sessions.delete → cleanup │
|
||||||
│ sonnet: <uuid> │ │ → send message to session │
|
│ sonnet: <key> │ │ │
|
||||||
│ opus: <uuid> │ │ → creates session if new │
|
│ opus: <key> │ │ openclaw agent │
|
||||||
│ qa: │ │ │
|
│ qa: │ │ --session-id <key> │
|
||||||
│ active, issueId, model │ └──────────────────────────────┘
|
│ active, issueId, model │ │ --message "task..." │
|
||||||
│ sessions: │
|
│ sessions: │ │ → dispatches to session │
|
||||||
│ grok: <uuid> │
|
│ grok: <key> │ │ │
|
||||||
└────────────────────────────────┘
|
└────────────────────────────────┘ └──────────────────────────────┘
|
||||||
↕ append-only
|
↕ append-only
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
│ memory/audit.log (observability) │
|
│ memory/audit.log (observability) │
|
||||||
@@ -485,7 +489,7 @@ graph LR
|
|||||||
L[Label transitions]
|
L[Label transitions]
|
||||||
S[Worker state]
|
S[Worker state]
|
||||||
M[Model selection]
|
M[Model selection]
|
||||||
SS[Session spawn/send]
|
SD[Session dispatch<br/>create + send via CLI]
|
||||||
A[Audit logging]
|
A[Audit logging]
|
||||||
Z[Zombie cleanup]
|
Z[Zombie cleanup]
|
||||||
end
|
end
|
||||||
@@ -513,9 +517,10 @@ graph LR
|
|||||||
|
|
||||||
| Failure | Detection | Recovery |
|
| 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). |
|
| 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 | Tool throws error, returns to agent | Agent retries or reports to Telegram group |
|
| glab command fails | Plugin 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. |
|
| `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. |
|
| 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. |
|
| 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. |
|
| Worker already active | `task_pickup` checks `active` flag | Throws error: "DEV worker already active on project". Must complete current task first. |
|
||||||
|
|||||||
@@ -42,10 +42,7 @@ In `openclaw.json`, your orchestrator agent needs access to the DevClaw tools:
|
|||||||
"task_pickup",
|
"task_pickup",
|
||||||
"task_complete",
|
"task_complete",
|
||||||
"queue_status",
|
"queue_status",
|
||||||
"session_health",
|
"session_health"
|
||||||
"sessions_spawn",
|
|
||||||
"sessions_send",
|
|
||||||
"sessions_list"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
@@ -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
|
### 3. Create GitLab labels
|
||||||
|
|
||||||
@@ -87,17 +84,23 @@ Add your project to `memory/projects.json` in the orchestrator's workspace:
|
|||||||
"deployBranch": "development",
|
"deployBranch": "development",
|
||||||
"dev": {
|
"dev": {
|
||||||
"active": false,
|
"active": false,
|
||||||
"sessionId": null,
|
|
||||||
"issueId": null,
|
"issueId": null,
|
||||||
"startTime": null,
|
"startTime": null,
|
||||||
"model": null
|
"model": null,
|
||||||
|
"sessions": {
|
||||||
|
"haiku": null,
|
||||||
|
"sonnet": null,
|
||||||
|
"opus": null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"qa": {
|
"qa": {
|
||||||
"active": false,
|
"active": false,
|
||||||
"sessionId": null,
|
|
||||||
"issueId": null,
|
"issueId": null,
|
||||||
"startTime": 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 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
|
- **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
|
### 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"
|
> "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
|
## 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` |
|
| Project registration | You (once per project) | Entry in `projects.json` |
|
||||||
| Agent definition | You (once) | Agent in `openclaw.json` with tool permissions |
|
| Agent definition | You (once) | Agent in `openclaw.json` with tool permissions |
|
||||||
| Telegram group setup | You (once per project) | Add bot to group |
|
| 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 |
|
| Label transitions | Plugin | Atomic `--unlabel` + `--label` via glab |
|
||||||
| Model selection | Plugin | Keyword-based heuristic per task |
|
| Model selection | Plugin | Keyword-based heuristic per task |
|
||||||
| State management | Plugin | Atomic read/write to `projects.json` |
|
| 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 |
|
| Audit logging | Plugin | Automatic NDJSON append per tool call |
|
||||||
| Zombie detection | Plugin | `session_health` checks active vs alive |
|
| Zombie detection | Plugin | `session_health` checks active vs alive |
|
||||||
| Queue scanning | Plugin | `queue_status` queries GitLab per project |
|
| Queue scanning | Plugin | `queue_status` queries GitLab per project |
|
||||||
|
|||||||
Reference in New Issue
Block a user