opengate 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -0
- package/dist/index.js +293 -0
- package/openclaw.plugin.json +46 -0
- package/package.json +34 -0
- package/skills/opengate/SKILL.md +228 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
function resolveConfig(raw) {
|
|
3
|
+
const url = typeof raw.url === "string" ? raw.url.replace(/\/$/, "") : "";
|
|
4
|
+
const apiKey = typeof raw.apiKey === "string" ? raw.apiKey : "";
|
|
5
|
+
if (!url) throw new Error("[opengate] plugin config missing: url");
|
|
6
|
+
if (!apiKey) throw new Error("[opengate] plugin config missing: apiKey");
|
|
7
|
+
return {
|
|
8
|
+
url,
|
|
9
|
+
apiKey,
|
|
10
|
+
agentId: typeof raw.agentId === "string" ? raw.agentId : "main",
|
|
11
|
+
model: typeof raw.model === "string" ? raw.model : void 0,
|
|
12
|
+
pollIntervalMs: typeof raw.pollIntervalMs === "number" ? raw.pollIntervalMs : 3e4,
|
|
13
|
+
maxConcurrent: typeof raw.maxConcurrent === "number" ? raw.maxConcurrent : 3
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/bootstrap.ts
|
|
18
|
+
function buildBootstrapPrompt(task, openGateUrl, apiKey) {
|
|
19
|
+
const tags = Array.isArray(task.tags) && task.tags.length > 0 ? task.tags.join(", ") : "none";
|
|
20
|
+
const contextBlock = task.context && Object.keys(task.context).length > 0 ? `
|
|
21
|
+
## Task Context
|
|
22
|
+
\`\`\`json
|
|
23
|
+
${JSON.stringify(task.context, null, 2)}
|
|
24
|
+
\`\`\`
|
|
25
|
+
` : "";
|
|
26
|
+
return `You are an autonomous coding agent assigned a task via OpenGate.
|
|
27
|
+
|
|
28
|
+
## Your Task
|
|
29
|
+
**ID:** ${task.id}
|
|
30
|
+
**Title:** ${task.title}
|
|
31
|
+
**Priority:** ${task.priority ?? "medium"}
|
|
32
|
+
**Tags:** ${tags}
|
|
33
|
+
**Project ID:** ${task.project_id ?? "unknown"}
|
|
34
|
+
|
|
35
|
+
**Description:**
|
|
36
|
+
${task.description ?? "(no description provided)"}
|
|
37
|
+
${contextBlock}
|
|
38
|
+
## OpenGate API
|
|
39
|
+
- **Base URL:** ${openGateUrl}
|
|
40
|
+
- **Auth:** Bearer ${apiKey}
|
|
41
|
+
|
|
42
|
+
## Protocol \u2014 Follow This Exactly
|
|
43
|
+
You MUST follow these steps in order. Skipping any step is not acceptable.
|
|
44
|
+
|
|
45
|
+
1. **Claim** \u2014 \`POST /api/tasks/${task.id}/claim\`
|
|
46
|
+
2. **Post starting comment** \u2014 \`POST /api/tasks/${task.id}/activity\` with body: \`{"content": "Starting: <your plan in 1-2 sentences>"}\`
|
|
47
|
+
3. **Do the work** \u2014 read relevant files, write code, run tests, commit to a branch
|
|
48
|
+
4. **Post results comment** \u2014 \`POST /api/tasks/${task.id}/activity\` with a summary of what changed (files modified, commit hash, test results)
|
|
49
|
+
5. **Complete** \u2014 \`POST /api/tasks/${task.id}/complete\` with body: \`{"summary": "<what was done>", "output": {"branch": "...", "commits": [...]}}\`
|
|
50
|
+
|
|
51
|
+
If you encounter a blocker that requires human input:
|
|
52
|
+
- Post a question: \`POST /api/tasks/${task.id}/activity\` with \`{"content": "BLOCKED: <question>"}\`
|
|
53
|
+
- Block the task: \`POST /api/tasks/${task.id}/block\` with \`{"reason": "<reason>"}\`
|
|
54
|
+
- Then stop \u2014 do NOT mark it complete.
|
|
55
|
+
|
|
56
|
+
## Notes
|
|
57
|
+
- Always work on a feature branch, never commit directly to main
|
|
58
|
+
- Run tests before completing
|
|
59
|
+
- If you discover something worth remembering (pattern, gotcha, decision), write it to a file in your workspace
|
|
60
|
+
|
|
61
|
+
Now begin. Start by claiming the task.`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/spawner.ts
|
|
65
|
+
async function spawnTaskSession(taskId, message, pluginCfg, openclawCfg) {
|
|
66
|
+
const port = openclawCfg?.gateway?.port ?? 18789;
|
|
67
|
+
const hooksToken = openclawCfg?.hooks?.token;
|
|
68
|
+
if (!hooksToken) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
error: "[opengate] hooks.token not configured in OpenClaw config. Add hooks.enabled=true and hooks.token=<secret> to enable task spawning."
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const sessionKey = `opengate-task:${taskId}`;
|
|
75
|
+
const agentId = pluginCfg.agentId ?? "main";
|
|
76
|
+
const payload = {
|
|
77
|
+
message,
|
|
78
|
+
agentId,
|
|
79
|
+
sessionKey,
|
|
80
|
+
wakeMode: "now",
|
|
81
|
+
deliver: false,
|
|
82
|
+
name: "OpenGate"
|
|
83
|
+
};
|
|
84
|
+
if (pluginCfg.model) {
|
|
85
|
+
payload.model = pluginCfg.model;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const resp = await fetch(`http://127.0.0.1:${port}/hooks/agent`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: {
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
Authorization: `Bearer ${hooksToken}`
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(payload)
|
|
95
|
+
});
|
|
96
|
+
if (resp.status === 202 || resp.status === 200) {
|
|
97
|
+
return { ok: true, sessionKey };
|
|
98
|
+
}
|
|
99
|
+
const body = await resp.text().catch(() => "(no body)");
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
error: `[opengate] hooks/agent returned HTTP ${resp.status}: ${body}`
|
|
103
|
+
};
|
|
104
|
+
} catch (e) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
error: `[opengate] failed to reach hooks/agent: ${e instanceof Error ? e.message : String(e)}`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/state.ts
|
|
113
|
+
import fs from "fs";
|
|
114
|
+
import path from "path";
|
|
115
|
+
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
116
|
+
var TaskState = class {
|
|
117
|
+
filePath;
|
|
118
|
+
data;
|
|
119
|
+
constructor(stateDir) {
|
|
120
|
+
this.filePath = path.join(stateDir, "opengate-spawned.json");
|
|
121
|
+
this.data = this.load();
|
|
122
|
+
this.cleanup();
|
|
123
|
+
}
|
|
124
|
+
load() {
|
|
125
|
+
try {
|
|
126
|
+
const raw = fs.readFileSync(this.filePath, "utf-8");
|
|
127
|
+
return JSON.parse(raw);
|
|
128
|
+
} catch {
|
|
129
|
+
return { spawned: {} };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
save() {
|
|
133
|
+
try {
|
|
134
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
135
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
136
|
+
} catch (e) {
|
|
137
|
+
console.error("[opengate] failed to save state:", e);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
cleanup() {
|
|
141
|
+
const cutoff = Date.now() - TTL_MS;
|
|
142
|
+
let changed = false;
|
|
143
|
+
for (const [id, entry] of Object.entries(this.data.spawned)) {
|
|
144
|
+
if (entry.spawnedAt < cutoff) {
|
|
145
|
+
delete this.data.spawned[id];
|
|
146
|
+
changed = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (changed) this.save();
|
|
150
|
+
}
|
|
151
|
+
isSpawned(taskId) {
|
|
152
|
+
return taskId in this.data.spawned;
|
|
153
|
+
}
|
|
154
|
+
markSpawned(taskId, sessionKey) {
|
|
155
|
+
this.data.spawned[taskId] = { taskId, sessionKey, spawnedAt: Date.now() };
|
|
156
|
+
this.save();
|
|
157
|
+
}
|
|
158
|
+
remove(taskId) {
|
|
159
|
+
delete this.data.spawned[taskId];
|
|
160
|
+
this.save();
|
|
161
|
+
}
|
|
162
|
+
activeCount() {
|
|
163
|
+
return Object.keys(this.data.spawned).length;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/poller.ts
|
|
168
|
+
async function fetchInbox(url, apiKey) {
|
|
169
|
+
const resp = await fetch(`${url}/api/agents/me/inbox`, {
|
|
170
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
171
|
+
signal: AbortSignal.timeout(1e4)
|
|
172
|
+
});
|
|
173
|
+
if (!resp.ok) {
|
|
174
|
+
throw new Error(`OpenGate inbox returned HTTP ${resp.status}`);
|
|
175
|
+
}
|
|
176
|
+
const body = await resp.json();
|
|
177
|
+
return body.todo ?? [];
|
|
178
|
+
}
|
|
179
|
+
var OpenGatePoller = class {
|
|
180
|
+
constructor(pluginCfg, openclawCfg, logger, stateDir) {
|
|
181
|
+
this.pluginCfg = pluginCfg;
|
|
182
|
+
this.openclawCfg = openclawCfg;
|
|
183
|
+
this.logger = logger;
|
|
184
|
+
this.state = new TaskState(stateDir);
|
|
185
|
+
}
|
|
186
|
+
timer = null;
|
|
187
|
+
state;
|
|
188
|
+
running = false;
|
|
189
|
+
start() {
|
|
190
|
+
if (this.running) return;
|
|
191
|
+
this.running = true;
|
|
192
|
+
const intervalMs = this.pluginCfg.pollIntervalMs ?? 3e4;
|
|
193
|
+
this.logger.info(`[opengate] Starting poller \u2014 interval: ${intervalMs}ms`);
|
|
194
|
+
void this.poll();
|
|
195
|
+
this.timer = setInterval(() => void this.poll(), intervalMs);
|
|
196
|
+
}
|
|
197
|
+
stop() {
|
|
198
|
+
this.running = false;
|
|
199
|
+
if (this.timer) {
|
|
200
|
+
clearInterval(this.timer);
|
|
201
|
+
this.timer = null;
|
|
202
|
+
}
|
|
203
|
+
this.logger.info("[opengate] Poller stopped");
|
|
204
|
+
}
|
|
205
|
+
async poll() {
|
|
206
|
+
if (!this.running) return;
|
|
207
|
+
const maxConcurrent = this.pluginCfg.maxConcurrent ?? 3;
|
|
208
|
+
const active = this.state.activeCount();
|
|
209
|
+
if (active >= maxConcurrent) {
|
|
210
|
+
this.logger.info(
|
|
211
|
+
`[opengate] At capacity (${active}/${maxConcurrent} active) \u2014 skipping poll`
|
|
212
|
+
);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
let tasks;
|
|
216
|
+
try {
|
|
217
|
+
tasks = await fetchInbox(this.pluginCfg.url, this.pluginCfg.apiKey);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
this.logger.warn(
|
|
220
|
+
`[opengate] Failed to fetch inbox: ${e instanceof Error ? e.message : String(e)}`
|
|
221
|
+
);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (tasks.length === 0) return;
|
|
225
|
+
this.logger.info(`[opengate] Found ${tasks.length} todo task(s)`);
|
|
226
|
+
for (const task of tasks) {
|
|
227
|
+
if (!this.running) break;
|
|
228
|
+
const currentActive = this.state.activeCount();
|
|
229
|
+
if (currentActive >= maxConcurrent) {
|
|
230
|
+
this.logger.info(
|
|
231
|
+
`[opengate] Reached capacity (${currentActive}/${maxConcurrent}) \u2014 deferring remaining tasks`
|
|
232
|
+
);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
if (this.state.isSpawned(task.id)) {
|
|
236
|
+
this.logger.info(`[opengate] Task ${task.id} already spawned \u2014 skipping`);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
await this.spawnTask(task);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async spawnTask(task) {
|
|
243
|
+
this.logger.info(`[opengate] Spawning session for task: "${task.title}" (${task.id})`);
|
|
244
|
+
const prompt = buildBootstrapPrompt(task, this.pluginCfg.url, this.pluginCfg.apiKey);
|
|
245
|
+
const result = await spawnTaskSession(
|
|
246
|
+
task.id,
|
|
247
|
+
prompt,
|
|
248
|
+
this.pluginCfg,
|
|
249
|
+
this.openclawCfg
|
|
250
|
+
);
|
|
251
|
+
if (!result.ok) {
|
|
252
|
+
this.logger.error(result.error);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.state.markSpawned(task.id, result.sessionKey);
|
|
256
|
+
this.logger.info(
|
|
257
|
+
`[opengate] Session spawned for task ${task.id} \u2192 session key: ${result.sessionKey}`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// src/index.ts
|
|
263
|
+
function register(api) {
|
|
264
|
+
let poller = null;
|
|
265
|
+
let pluginCfg;
|
|
266
|
+
try {
|
|
267
|
+
pluginCfg = resolveConfig(api.pluginConfig ?? {});
|
|
268
|
+
} catch (e) {
|
|
269
|
+
api.logger.error(e instanceof Error ? e.message : String(e));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const hooksToken = api.config?.hooks?.token;
|
|
273
|
+
if (!hooksToken) {
|
|
274
|
+
api.logger.error(
|
|
275
|
+
'[opengate] hooks.token is not configured. Add the following to your OpenClaw config to enable task spawning:\n "hooks": { "enabled": true, "token": "<your-secret>", "allowRequestSessionKey": true, "allowedSessionKeyPrefixes": ["opengate-task:"] }'
|
|
276
|
+
);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
api.registerService({
|
|
280
|
+
id: "opengate-poller",
|
|
281
|
+
start(ctx) {
|
|
282
|
+
poller = new OpenGatePoller(pluginCfg, api.config, ctx.logger, ctx.stateDir);
|
|
283
|
+
poller.start();
|
|
284
|
+
},
|
|
285
|
+
stop(ctx) {
|
|
286
|
+
poller?.stop();
|
|
287
|
+
poller = null;
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
export {
|
|
292
|
+
register as default
|
|
293
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "opengate",
|
|
3
|
+
"name": "OpenGate",
|
|
4
|
+
"description": "Polls OpenGate for assigned tasks and spawns isolated OpenClaw sessions to execute them. Turns OpenGate into the orchestrator.",
|
|
5
|
+
"version": "0.2.1",
|
|
6
|
+
"skills": ["./skills/opengate"],
|
|
7
|
+
"configSchema": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"additionalProperties": false,
|
|
10
|
+
"required": ["url", "apiKey"],
|
|
11
|
+
"properties": {
|
|
12
|
+
"url": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "OpenGate base URL (e.g. https://opengate.sh)"
|
|
15
|
+
},
|
|
16
|
+
"apiKey": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Agent API key for OpenGate"
|
|
19
|
+
},
|
|
20
|
+
"agentId": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "OpenClaw agent ID to spawn sessions as (default: 'main')"
|
|
23
|
+
},
|
|
24
|
+
"model": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Model override for spawned task sessions (e.g. anthropic/claude-sonnet-4-6)"
|
|
27
|
+
},
|
|
28
|
+
"pollIntervalMs": {
|
|
29
|
+
"type": "number",
|
|
30
|
+
"description": "How often to poll OpenGate for tasks in milliseconds (default: 30000)"
|
|
31
|
+
},
|
|
32
|
+
"maxConcurrent": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"description": "Max concurrent task sessions (default: 3)"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"uiHints": {
|
|
39
|
+
"url": { "label": "OpenGate URL", "placeholder": "https://opengate.sh" },
|
|
40
|
+
"apiKey": { "label": "Agent API Key", "sensitive": true },
|
|
41
|
+
"agentId": { "label": "OpenClaw Agent ID", "placeholder": "main" },
|
|
42
|
+
"model": { "label": "Model Override", "placeholder": "anthropic/claude-sonnet-4-6" },
|
|
43
|
+
"pollIntervalMs": { "label": "Poll Interval (ms)", "advanced": true },
|
|
44
|
+
"maxConcurrent": { "label": "Max Concurrent Tasks", "advanced": true }
|
|
45
|
+
}
|
|
46
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opengate",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "OpenGate task executor plugin for OpenClaw — polls assigned tasks and spawns isolated agent sessions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup src/index.ts --format esm --dts --external openclaw",
|
|
10
|
+
"dev": "tsup src/index.ts --format esm --dts --external openclaw --watch"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"tsup": "^8.4.0",
|
|
15
|
+
"typescript": "^5.7.0",
|
|
16
|
+
"@types/node": "^22.0.0"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"skills",
|
|
21
|
+
"openclaw.plugin.json"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"openclaw": {
|
|
27
|
+
"extensions": [
|
|
28
|
+
"./dist/index.js"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: opengate
|
|
3
|
+
user-invocable: false
|
|
4
|
+
metadata:
|
|
5
|
+
always: true
|
|
6
|
+
description: "Interact with OpenGate — the team's agent-first task management platform. Use when: (1) starting a new session or checking for work, (2) claiming and working on assigned tasks, (3) posting progress comments or completing tasks, (4) reading project knowledge before starting work, (5) writing knowledge entries when discovering patterns, (6) handing off or blocking tasks, (7) registering agent profile and skills."
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# OpenGate Task Workflow
|
|
10
|
+
|
|
11
|
+
OpenGate is the team's task management platform. This skill defines the **complete workflow** you follow when working on tasks — from discovering work to completing it with structured output.
|
|
12
|
+
|
|
13
|
+
## When to Activate
|
|
14
|
+
|
|
15
|
+
Use this skill when:
|
|
16
|
+
- You start a new session and need to check for assigned work
|
|
17
|
+
- You are asked to work on a task from OpenGate
|
|
18
|
+
- You need to discover available tasks matching your skills
|
|
19
|
+
- You want to share knowledge or read project context
|
|
20
|
+
|
|
21
|
+
## Ownership: You Own Your Task Lifecycle
|
|
22
|
+
|
|
23
|
+
**When you claim a task, you are the sole owner of its lifecycle.** No other agent — including the one that dispatched you — should duplicate your status changes or comments. OpenGate is the platform; it handles routing and coordination. You handle execution and reporting.
|
|
24
|
+
|
|
25
|
+
This means:
|
|
26
|
+
- **You** claim the task, post comments, update context, and complete it
|
|
27
|
+
- If another agent dispatched you with a task ID, you still follow this full protocol
|
|
28
|
+
- There should be exactly **one starting comment** and **one results comment** per task — both from you
|
|
29
|
+
|
|
30
|
+
## MANDATORY: Status Updates & Comments
|
|
31
|
+
|
|
32
|
+
**Every task you work on MUST have:**
|
|
33
|
+
1. **Status transitions** — move the task through its lifecycle (claim → in_progress → complete/review)
|
|
34
|
+
2. **Starting comment** — post what you plan to do before starting work
|
|
35
|
+
3. **Progress comments** — post updates during work, especially for long tasks
|
|
36
|
+
4. **Results comment** — post a final comment with files changed, commits, and test results
|
|
37
|
+
5. **Completion** — complete the task or submit for review with a structured summary
|
|
38
|
+
|
|
39
|
+
Skipping any of these steps is not acceptable. Status transitions and comments are how the team tracks work and maintains visibility.
|
|
40
|
+
|
|
41
|
+
## Task Lifecycle Protocol
|
|
42
|
+
|
|
43
|
+
Follow these steps **in order** for every task:
|
|
44
|
+
|
|
45
|
+
### 1. Discover Work
|
|
46
|
+
|
|
47
|
+
Check your inbox for assigned and available tasks:
|
|
48
|
+
|
|
49
|
+
`check_inbox` → Returns your inbox with sections: `todo`, `in_progress`, `review`, `blocked`
|
|
50
|
+
|
|
51
|
+
If no tasks are assigned, use `next_task` to find work matching your skills.
|
|
52
|
+
|
|
53
|
+
### 2. Read Task Context
|
|
54
|
+
|
|
55
|
+
Before starting any work, fully understand the task:
|
|
56
|
+
|
|
57
|
+
`get_task(task_id)` → Read the full task: description, tags, priority, existing context, dependencies
|
|
58
|
+
|
|
59
|
+
Pay attention to:
|
|
60
|
+
- **Tags** — they indicate the domain and relevant knowledge areas
|
|
61
|
+
- **Context** — structured data from previous work or the task creator
|
|
62
|
+
- **Dependencies** — tasks that must complete before this one
|
|
63
|
+
|
|
64
|
+
### 2.5 Set Up Project Workspace
|
|
65
|
+
|
|
66
|
+
If the task's context contains `repo_url` (auto-enriched from project settings):
|
|
67
|
+
|
|
68
|
+
1. Call `get_workspace_info(project_id)` for repo URL and suggested path
|
|
69
|
+
2. If repo not yet cloned: `git clone <repo_url> <workspace_path>`
|
|
70
|
+
3. If already cloned: `cd <workspace_path> && git fetch origin`
|
|
71
|
+
4. Create feature branch: `git checkout -b task/<task_id_short> <default_branch>`
|
|
72
|
+
|
|
73
|
+
**Isolation rules:**
|
|
74
|
+
- Each project gets its own directory under `~/.opengate/workspaces/`
|
|
75
|
+
- NEVER work on files outside the active project's workspace
|
|
76
|
+
- NEVER mix changes from different projects
|
|
77
|
+
- If `repo_url` is null, the project is not code-related — skip this step
|
|
78
|
+
|
|
79
|
+
### 3. Fetch Project Knowledge
|
|
80
|
+
|
|
81
|
+
**Always search the knowledge base before starting work.** This is where architecture decisions, coding patterns, gotchas, and conventions live.
|
|
82
|
+
|
|
83
|
+
`search_knowledge(project_id, query)` → Search by keywords related to the task
|
|
84
|
+
`list_knowledge(project_id, prefix)` → Browse entries by key prefix
|
|
85
|
+
|
|
86
|
+
Specifically look for:
|
|
87
|
+
- Entries tagged with the same tags as your task
|
|
88
|
+
- Entries in the `architecture` category for structural decisions
|
|
89
|
+
- Entries in the `gotcha` category for known pitfalls
|
|
90
|
+
- Entries in the `convention` category for coding standards
|
|
91
|
+
|
|
92
|
+
### 4. Claim the Task
|
|
93
|
+
|
|
94
|
+
`claim_task(task_id)` → Moves the task to `in_progress` and assigns it to you
|
|
95
|
+
|
|
96
|
+
This enforces capacity limits and dependency checks. If claiming fails, read the error — you may have too many tasks in progress or a dependency is incomplete.
|
|
97
|
+
|
|
98
|
+
### 5. Comment: Starting Work
|
|
99
|
+
|
|
100
|
+
`post_comment(task_id, content)` → Post a comment before you start
|
|
101
|
+
|
|
102
|
+
Your starting comment should include:
|
|
103
|
+
- What you understand the task requires
|
|
104
|
+
- Your planned approach
|
|
105
|
+
- Any concerns or assumptions
|
|
106
|
+
|
|
107
|
+
### 6. Do the Work
|
|
108
|
+
|
|
109
|
+
Execute the actual task — write code, fix bugs, create artifacts, etc.
|
|
110
|
+
|
|
111
|
+
### 7. Comment: Progress Updates
|
|
112
|
+
|
|
113
|
+
For long-running tasks, post progress comments:
|
|
114
|
+
|
|
115
|
+
`post_comment(task_id, content)` → Share intermediate results, decisions made, or blockers encountered
|
|
116
|
+
|
|
117
|
+
### 8. Store Work Artifacts
|
|
118
|
+
|
|
119
|
+
`update_context(task_id, context)` → Shallow-merge structured data into the task context
|
|
120
|
+
|
|
121
|
+
Store useful artifacts like:
|
|
122
|
+
- File paths created or modified
|
|
123
|
+
- Key decisions and their rationale
|
|
124
|
+
- Configuration values or environment details
|
|
125
|
+
- Links to PRs, commits, or external resources
|
|
126
|
+
- Feature branch name (e.g. `task/<task_id_short>`) for workspace continuity
|
|
127
|
+
|
|
128
|
+
### 9. Complete the Task
|
|
129
|
+
|
|
130
|
+
`complete_task(task_id, summary, output)` → Finish the task with a summary and structured output
|
|
131
|
+
|
|
132
|
+
- **summary** — Human-readable description of what was done
|
|
133
|
+
- **output** — Structured data: PR URLs, file paths, metrics, artifacts
|
|
134
|
+
|
|
135
|
+
Completing a task moves it to `done` and automatically unblocks dependent tasks.
|
|
136
|
+
|
|
137
|
+
## When You're Stuck
|
|
138
|
+
|
|
139
|
+
- `block_task(task_id, reason)` → Move to `blocked` status with a clear reason explaining what you need to proceed
|
|
140
|
+
- `handoff_task(task_id, to_agent_id, summary)` → Transfer to another agent who is better suited, with context about what you've done so far
|
|
141
|
+
|
|
142
|
+
## Managing Dependencies
|
|
143
|
+
|
|
144
|
+
Dependencies define task ordering — a task cannot start until all its dependencies are complete.
|
|
145
|
+
|
|
146
|
+
### Adding Dependencies
|
|
147
|
+
|
|
148
|
+
When creating or planning multi-step work, link tasks:
|
|
149
|
+
|
|
150
|
+
`add_dependencies(task_id, depends_on)` → Link one or more dependency tasks
|
|
151
|
+
|
|
152
|
+
Example: Task B depends on Task A completing first:
|
|
153
|
+
`add_dependencies(task_b_id, [task_a_id])`
|
|
154
|
+
|
|
155
|
+
### Checking Dependencies
|
|
156
|
+
|
|
157
|
+
Before claiming a task, verify its dependencies are met:
|
|
158
|
+
|
|
159
|
+
`list_dependencies(task_id)` → See what must complete first
|
|
160
|
+
`list_dependents(task_id)` → See what this task blocks
|
|
161
|
+
|
|
162
|
+
### Removing Dependencies
|
|
163
|
+
|
|
164
|
+
If a dependency is no longer needed:
|
|
165
|
+
|
|
166
|
+
`remove_dependency(task_id, dependency_id)` → Unlink a dependency
|
|
167
|
+
|
|
168
|
+
### Dependency Tips
|
|
169
|
+
- Claiming a task with unmet dependencies will fail — check first
|
|
170
|
+
- Completing a task automatically notifies dependent tasks via `task.dependency_ready`
|
|
171
|
+
- Use dependencies to break large features into ordered subtasks
|
|
172
|
+
|
|
173
|
+
## Knowledge Base Integration
|
|
174
|
+
|
|
175
|
+
### Reading Knowledge
|
|
176
|
+
|
|
177
|
+
**Always** search knowledge before starting a task. Knowledge entries contain:
|
|
178
|
+
- **Architecture decisions** — system design, data flow, component boundaries
|
|
179
|
+
- **Conventions** — naming, file structure, patterns the team follows
|
|
180
|
+
- **Gotchas** — known pitfalls, workarounds, things that aren't obvious
|
|
181
|
+
- **References** — links, docs, external resources
|
|
182
|
+
- **General** — anything else worth knowing
|
|
183
|
+
|
|
184
|
+
### Writing Knowledge
|
|
185
|
+
|
|
186
|
+
When you discover something important during work, **write it back**:
|
|
187
|
+
|
|
188
|
+
`set_knowledge(project_id, key, title, content, tags, category)`
|
|
189
|
+
|
|
190
|
+
Write knowledge when you:
|
|
191
|
+
- Discover a non-obvious pattern or constraint
|
|
192
|
+
- Make an architecture decision that affects future work
|
|
193
|
+
- Find a gotcha that would trip up other agents
|
|
194
|
+
- Establish a convention through your implementation
|
|
195
|
+
|
|
196
|
+
Categories: `architecture`, `convention`, `gotcha`, `reference`, `general`
|
|
197
|
+
|
|
198
|
+
## Agent Profile
|
|
199
|
+
|
|
200
|
+
On your first session, register your capabilities:
|
|
201
|
+
|
|
202
|
+
`update_agent_profile(description, skills, max_concurrent_tasks)`
|
|
203
|
+
|
|
204
|
+
This helps OpenGate route tasks to the right agent.
|
|
205
|
+
|
|
206
|
+
## API Quick Reference
|
|
207
|
+
|
|
208
|
+
| Action | Method | Path |
|
|
209
|
+
|--------|--------|------|
|
|
210
|
+
| Check inbox | GET | /api/agents/me/inbox |
|
|
211
|
+
| Get task | GET | /api/tasks/:id |
|
|
212
|
+
| My tasks | GET | /api/tasks/mine |
|
|
213
|
+
| Next task | GET | /api/tasks/next?skills= |
|
|
214
|
+
| Claim task | POST | /api/tasks/:id/claim |
|
|
215
|
+
| Complete task | POST | /api/tasks/:id/complete |
|
|
216
|
+
| Block task | POST | /api/tasks/:id/block |
|
|
217
|
+
| Handoff task | POST | /api/tasks/:id/handoff |
|
|
218
|
+
| Post comment | POST | /api/tasks/:id/activity |
|
|
219
|
+
| Update context | PATCH | /api/tasks/:id/context |
|
|
220
|
+
| Search knowledge | GET | /api/projects/:id/knowledge/search |
|
|
221
|
+
| List knowledge | GET | /api/projects/:id/knowledge |
|
|
222
|
+
| Set knowledge | PUT | /api/projects/:id/knowledge/:key |
|
|
223
|
+
| Add dependencies | POST | /api/tasks/:id/dependencies |
|
|
224
|
+
| Remove dependency | DELETE | /api/tasks/:id/dependencies/:dep_id |
|
|
225
|
+
| List dependencies | GET | /api/tasks/:id/dependencies |
|
|
226
|
+
| List dependents | GET | /api/tasks/:id/dependents |
|
|
227
|
+
| Update profile | PATCH | /api/auth/me |
|
|
228
|
+
| Heartbeat | POST | /api/agents/heartbeat |
|