@opengate/openclaw 0.1.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.
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Formats OpenGate events into human-readable messages with MCP tool instructions.
3
+ */
4
+ interface OpenGateEvent {
5
+ type: string;
6
+ task_id?: string;
7
+ task_title?: string;
8
+ project_id?: string;
9
+ from_agent?: string;
10
+ reason?: string;
11
+ summary?: string;
12
+ content?: string;
13
+ priority?: string;
14
+ tags?: string[];
15
+ [key: string]: unknown;
16
+ }
17
+ interface Notification {
18
+ id: string;
19
+ event_type: string;
20
+ payload: OpenGateEvent;
21
+ read: boolean;
22
+ created_at: string;
23
+ }
24
+ /**
25
+ * Format a single OpenGate event into a readable message for the agent.
26
+ */
27
+ declare function formatEvent(event: OpenGateEvent): string;
28
+ /**
29
+ * Format a list of notifications into a summary message.
30
+ */
31
+ declare function formatNotificationSummary(notifications: Notification[]): string;
32
+
33
+ /**
34
+ * HTTP polling client for OpenGate notifications.
35
+ * Polls GET /api/agents/me/notifications?unread=true at a configurable interval.
36
+ */
37
+
38
+ interface PollerConfig {
39
+ url: string;
40
+ apiKey: string;
41
+ pollIntervalMs: number;
42
+ projectId?: string;
43
+ }
44
+
45
+ /**
46
+ * WebSocket client for OpenGate real-time notifications.
47
+ * Connects to /api/ws, authenticates, and subscribes to agent events.
48
+ * Features auto-reconnect with exponential backoff (1s -> 2s -> 4s -> ... -> max 60s).
49
+ */
50
+
51
+ interface WsClientConfig {
52
+ url: string;
53
+ apiKey: string;
54
+ projectId?: string;
55
+ }
56
+
57
+ /**
58
+ * @opengate/openclaw — OpenClaw plugin for OpenGate agent notifications.
59
+ *
60
+ * Provides real-time push notifications from OpenGate to agents via
61
+ * HTTP polling (default) or WebSocket.
62
+ *
63
+ * Registers a service "opengate-bridge" that:
64
+ * - Connects to an OpenGate instance
65
+ * - Listens for agent events (task assignments, comments, unblocks, etc.)
66
+ * - Injects formatted messages into the agent's session via sessions.send()
67
+ */
68
+ interface PluginConfig {
69
+ url: string;
70
+ apiKey: string;
71
+ mode?: "polling" | "websocket";
72
+ pollIntervalMs?: number;
73
+ projectId?: string;
74
+ }
75
+ /**
76
+ * OpenClaw Plugin API surface (subset relevant to this plugin).
77
+ * These types represent what OpenClaw provides to plugins.
78
+ */
79
+ interface OpenClawPluginApi {
80
+ registerService(service: {
81
+ id: string;
82
+ start: () => void | Promise<void>;
83
+ stop: () => void | Promise<void>;
84
+ }): void;
85
+ gateway: {
86
+ rpc(method: string, params: Record<string, unknown>): Promise<unknown>;
87
+ };
88
+ }
89
+ /**
90
+ * Plugin entry point. Called by OpenClaw when the plugin is loaded.
91
+ */
92
+ declare function register(api: OpenClawPluginApi, config: PluginConfig): void;
93
+
94
+ export { type Notification, type OpenClawPluginApi, type OpenGateEvent, type PluginConfig, type PollerConfig, type WsClientConfig, register as default, formatEvent, formatNotificationSummary };
package/dist/index.js ADDED
@@ -0,0 +1,361 @@
1
+ // src/poller.ts
2
+ var Poller = class {
3
+ config;
4
+ handler;
5
+ timer = null;
6
+ running = false;
7
+ constructor(config, handler) {
8
+ this.config = config;
9
+ this.handler = handler;
10
+ }
11
+ start() {
12
+ if (this.running) return;
13
+ this.running = true;
14
+ void this.poll();
15
+ this.timer = setInterval(() => {
16
+ void this.poll();
17
+ }, this.config.pollIntervalMs);
18
+ }
19
+ stop() {
20
+ this.running = false;
21
+ if (this.timer) {
22
+ clearInterval(this.timer);
23
+ this.timer = null;
24
+ }
25
+ }
26
+ async poll() {
27
+ if (!this.running) return;
28
+ try {
29
+ const baseUrl = this.config.url.replace(/\/$/, "");
30
+ const params = new URLSearchParams({ unread: "true" });
31
+ if (this.config.projectId) {
32
+ params.set("project_id", this.config.projectId);
33
+ }
34
+ const response = await fetch(
35
+ `${baseUrl}/api/agents/me/notifications?${params.toString()}`,
36
+ {
37
+ headers: {
38
+ Authorization: `Bearer ${this.config.apiKey}`,
39
+ "Content-Type": "application/json"
40
+ }
41
+ }
42
+ );
43
+ if (!response.ok) {
44
+ console.error(
45
+ `[opengate-bridge] Poll failed: ${response.status} ${response.statusText}`
46
+ );
47
+ return;
48
+ }
49
+ const notifications = await response.json();
50
+ if (notifications.length > 0) {
51
+ this.handler(notifications);
52
+ await this.acknowledgeNotifications(
53
+ baseUrl,
54
+ notifications.map((n) => n.id)
55
+ );
56
+ }
57
+ } catch (err) {
58
+ console.error(`[opengate-bridge] Poll error:`, err);
59
+ }
60
+ }
61
+ async acknowledgeNotifications(baseUrl, ids) {
62
+ try {
63
+ for (const id of ids) {
64
+ await fetch(`${baseUrl}/api/agents/me/notifications/${id}/ack`, {
65
+ method: "POST",
66
+ headers: {
67
+ Authorization: `Bearer ${this.config.apiKey}`,
68
+ "Content-Type": "application/json"
69
+ }
70
+ });
71
+ }
72
+ } catch (err) {
73
+ console.error(`[opengate-bridge] Failed to acknowledge notifications:`, err);
74
+ }
75
+ }
76
+ };
77
+
78
+ // src/ws-client.ts
79
+ import WebSocket from "ws";
80
+ var INITIAL_BACKOFF_MS = 1e3;
81
+ var MAX_BACKOFF_MS = 6e4;
82
+ var BACKOFF_MULTIPLIER = 2;
83
+ var WsClient = class {
84
+ config;
85
+ handler;
86
+ ws = null;
87
+ running = false;
88
+ backoffMs = INITIAL_BACKOFF_MS;
89
+ reconnectTimer = null;
90
+ pingTimer = null;
91
+ constructor(config, handler) {
92
+ this.config = config;
93
+ this.handler = handler;
94
+ }
95
+ start() {
96
+ if (this.running) return;
97
+ this.running = true;
98
+ this.connect();
99
+ }
100
+ stop() {
101
+ this.running = false;
102
+ if (this.reconnectTimer) {
103
+ clearTimeout(this.reconnectTimer);
104
+ this.reconnectTimer = null;
105
+ }
106
+ if (this.pingTimer) {
107
+ clearInterval(this.pingTimer);
108
+ this.pingTimer = null;
109
+ }
110
+ if (this.ws) {
111
+ this.ws.close(1e3, "Plugin stopped");
112
+ this.ws = null;
113
+ }
114
+ }
115
+ connect() {
116
+ if (!this.running) return;
117
+ const baseUrl = this.config.url.replace(/\/$/, "").replace(/^http/, "ws");
118
+ const wsUrl = `${baseUrl}/api/ws`;
119
+ try {
120
+ this.ws = new WebSocket(wsUrl, {
121
+ headers: {
122
+ Authorization: `Bearer ${this.config.apiKey}`
123
+ }
124
+ });
125
+ this.ws.on("open", () => {
126
+ console.log("[opengate-bridge] WebSocket connected");
127
+ this.backoffMs = INITIAL_BACKOFF_MS;
128
+ const subscribe = JSON.stringify({
129
+ type: "subscribe",
130
+ channels: ["agent.notifications"],
131
+ project_id: this.config.projectId
132
+ });
133
+ this.ws?.send(subscribe);
134
+ this.pingTimer = setInterval(() => {
135
+ if (this.ws?.readyState === WebSocket.OPEN) {
136
+ this.ws.ping();
137
+ }
138
+ }, 3e4);
139
+ });
140
+ this.ws.on("message", (data) => {
141
+ try {
142
+ const event = JSON.parse(data.toString());
143
+ if (event.type && event.type !== "pong") {
144
+ this.handler(event);
145
+ }
146
+ } catch {
147
+ console.error(
148
+ "[opengate-bridge] Failed to parse WebSocket message"
149
+ );
150
+ }
151
+ });
152
+ this.ws.on("close", (code, reason) => {
153
+ console.log(
154
+ `[opengate-bridge] WebSocket closed: ${code} ${reason.toString()}`
155
+ );
156
+ this.cleanup();
157
+ this.scheduleReconnect();
158
+ });
159
+ this.ws.on("error", (err) => {
160
+ console.error(`[opengate-bridge] WebSocket error:`, err.message);
161
+ this.cleanup();
162
+ this.scheduleReconnect();
163
+ });
164
+ } catch (err) {
165
+ console.error(`[opengate-bridge] WebSocket connection failed:`, err);
166
+ this.scheduleReconnect();
167
+ }
168
+ }
169
+ cleanup() {
170
+ if (this.pingTimer) {
171
+ clearInterval(this.pingTimer);
172
+ this.pingTimer = null;
173
+ }
174
+ this.ws = null;
175
+ }
176
+ scheduleReconnect() {
177
+ if (!this.running) return;
178
+ if (this.reconnectTimer) return;
179
+ console.log(
180
+ `[opengate-bridge] Reconnecting in ${this.backoffMs / 1e3}s...`
181
+ );
182
+ this.reconnectTimer = setTimeout(() => {
183
+ this.reconnectTimer = null;
184
+ this.backoffMs = Math.min(
185
+ this.backoffMs * BACKOFF_MULTIPLIER,
186
+ MAX_BACKOFF_MS
187
+ );
188
+ this.connect();
189
+ }, this.backoffMs);
190
+ }
191
+ };
192
+
193
+ // src/message-formatter.ts
194
+ function formatEvent(event) {
195
+ const taskRef = event.task_id ? ` (task: ${event.task_title ?? event.task_id})` : "";
196
+ switch (event.type) {
197
+ case "task.assigned":
198
+ return [
199
+ `New task assigned to you${taskRef}`,
200
+ event.priority ? `Priority: ${event.priority}` : null,
201
+ event.tags?.length ? `Tags: ${event.tags.join(", ")}` : null,
202
+ "",
203
+ "Next steps:",
204
+ "1. Use `get_task` to read the full task details",
205
+ "2. Use `search_knowledge` to check for relevant project knowledge",
206
+ "3. Use `claim_task` to start working on it",
207
+ "4. Use `post_comment` to share your planned approach"
208
+ ].filter((line) => line !== null).join("\n");
209
+ case "task.comment":
210
+ return [
211
+ `New comment on task${taskRef}`,
212
+ event.from_agent ? `From: ${event.from_agent}` : null,
213
+ event.content ? `> ${event.content}` : null,
214
+ "",
215
+ "Use `get_task` to see the full task context."
216
+ ].filter((line) => line !== null).join("\n");
217
+ case "task.dependency_ready":
218
+ return [
219
+ `Dependency resolved for task${taskRef}`,
220
+ "A blocking dependency has been completed. This task may now be ready to claim.",
221
+ "",
222
+ "Next steps:",
223
+ "1. Use `get_task` to review the task",
224
+ "2. Use `list_dependencies` to verify all dependencies are met",
225
+ "3. Use `claim_task` to start working if ready"
226
+ ].join("\n");
227
+ case "task.review_requested":
228
+ return [
229
+ `Review requested for task${taskRef}`,
230
+ event.from_agent ? `From: ${event.from_agent}` : null,
231
+ event.summary ? `Summary: ${event.summary}` : null,
232
+ "",
233
+ "Next steps:",
234
+ "1. Use `get_task` to review the task and its output",
235
+ "2. Approve with `approve_task` or request changes with `request_changes`"
236
+ ].filter((line) => line !== null).join("\n");
237
+ case "task.handoff":
238
+ return [
239
+ `Task handed off to you${taskRef}`,
240
+ event.from_agent ? `From: ${event.from_agent}` : null,
241
+ event.summary ? `Context: ${event.summary}` : null,
242
+ "",
243
+ "Next steps:",
244
+ "1. Use `get_task` to read the full task and handoff context",
245
+ "2. Use `claim_task` to accept the handoff",
246
+ "3. Use `post_comment` to acknowledge and share your plan"
247
+ ].filter((line) => line !== null).join("\n");
248
+ case "task.unblocked":
249
+ return [
250
+ `Task unblocked${taskRef}`,
251
+ event.reason ? `Reason: ${event.reason}` : null,
252
+ "",
253
+ "The task has been unblocked and moved back to your queue.",
254
+ "Use `get_task` to review and continue working on it."
255
+ ].filter((line) => line !== null).join("\n");
256
+ case "task.changes_requested":
257
+ return [
258
+ `Changes requested on task${taskRef}`,
259
+ event.from_agent ? `From: ${event.from_agent}` : null,
260
+ event.content ? `Feedback: ${event.content}` : null,
261
+ "",
262
+ "Next steps:",
263
+ "1. Use `get_task` to read the review feedback",
264
+ "2. Address the requested changes",
265
+ "3. Use `complete_task` to resubmit when ready"
266
+ ].filter((line) => line !== null).join("\n");
267
+ default:
268
+ return [
269
+ `OpenGate event: ${event.type}${taskRef}`,
270
+ event.summary ?? event.content ?? "",
271
+ "",
272
+ "Use `check_inbox` to see your current task queue."
273
+ ].filter((line) => line !== "").join("\n");
274
+ }
275
+ }
276
+ function formatNotificationSummary(notifications) {
277
+ if (notifications.length === 0) {
278
+ return "";
279
+ }
280
+ const lines = [
281
+ `You have ${notifications.length} unread notification${notifications.length === 1 ? "" : "s"} from OpenGate:`,
282
+ ""
283
+ ];
284
+ for (const notification of notifications) {
285
+ const formatted = formatEvent(notification.payload);
286
+ lines.push(`--- ${notification.event_type} ---`);
287
+ lines.push(formatted);
288
+ lines.push("");
289
+ }
290
+ lines.push(
291
+ "Use `check_inbox` for a full overview of your task queue."
292
+ );
293
+ return lines.join("\n");
294
+ }
295
+
296
+ // src/index.ts
297
+ var DEFAULT_POLL_INTERVAL_MS = 6e5;
298
+ var SESSION_KEY = "opengate:inbox";
299
+ function register(api, config) {
300
+ const mode = config.mode ?? "polling";
301
+ const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
302
+ let poller = null;
303
+ let wsClient = null;
304
+ function sendMessage(message) {
305
+ if (!message) return;
306
+ void api.gateway.rpc("sessions.send", {
307
+ sessionKey: SESSION_KEY,
308
+ message
309
+ });
310
+ }
311
+ function handleNotifications(notifications) {
312
+ const summary = formatNotificationSummary(notifications);
313
+ sendMessage(summary);
314
+ }
315
+ function handleWsEvent(event) {
316
+ const message = formatEvent(event);
317
+ sendMessage(message);
318
+ }
319
+ api.registerService({
320
+ id: "opengate-bridge",
321
+ start() {
322
+ console.log(
323
+ `[opengate-bridge] Starting in ${mode} mode (url: ${config.url})`
324
+ );
325
+ if (mode === "websocket") {
326
+ wsClient = new WsClient(
327
+ {
328
+ url: config.url,
329
+ apiKey: config.apiKey,
330
+ projectId: config.projectId
331
+ },
332
+ handleWsEvent
333
+ );
334
+ wsClient.start();
335
+ } else {
336
+ poller = new Poller(
337
+ {
338
+ url: config.url,
339
+ apiKey: config.apiKey,
340
+ pollIntervalMs,
341
+ projectId: config.projectId
342
+ },
343
+ handleNotifications
344
+ );
345
+ poller.start();
346
+ }
347
+ },
348
+ stop() {
349
+ console.log("[opengate-bridge] Stopping");
350
+ poller?.stop();
351
+ wsClient?.stop();
352
+ poller = null;
353
+ wsClient = null;
354
+ }
355
+ });
356
+ }
357
+ export {
358
+ register as default,
359
+ formatEvent,
360
+ formatNotificationSummary
361
+ };
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://openclaw.dev/schemas/plugin.json",
3
+ "name": "opengate",
4
+ "displayName": "OpenGate Bridge",
5
+ "description": "Real-time push notifications from OpenGate to agents via HTTP polling or WebSocket",
6
+ "version": "0.1.0",
7
+ "config": {
8
+ "url": {
9
+ "type": "string",
10
+ "description": "OpenGate server URL",
11
+ "required": true,
12
+ "default": "https://opengate.sh"
13
+ },
14
+ "apiKey": {
15
+ "type": "string",
16
+ "description": "Agent API key (tf_... format)",
17
+ "required": true
18
+ },
19
+ "mode": {
20
+ "type": "string",
21
+ "description": "Notification mode: 'polling' (default) or 'websocket'",
22
+ "enum": ["polling", "websocket"],
23
+ "default": "polling"
24
+ },
25
+ "pollIntervalMs": {
26
+ "type": "number",
27
+ "description": "Polling interval in milliseconds (only used in polling mode)",
28
+ "default": 600000
29
+ },
30
+ "projectId": {
31
+ "type": "string",
32
+ "description": "Optional project ID to filter notifications",
33
+ "required": false
34
+ }
35
+ }
36
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@opengate/openclaw",
3
+ "version": "0.1.1",
4
+ "description": "OpenClaw plugin for OpenGate — real-time agent notifications via HTTP polling or WebSocket",
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",
10
+ "dev": "tsup src/index.ts --format esm --dts --watch"
11
+ },
12
+ "dependencies": {
13
+ "ws": "^8.18.0"
14
+ },
15
+ "devDependencies": {
16
+ "tsup": "^8.4.0",
17
+ "typescript": "^5.7.0",
18
+ "@types/node": "^22.0.0",
19
+ "@types/ws": "^8.5.0"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "skills",
24
+ "openclaw.plugin.json"
25
+ ],
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }
@@ -0,0 +1,212 @@
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
+ ### 3. Fetch Project Knowledge
65
+
66
+ **Always search the knowledge base before starting work.** This is where architecture decisions, coding patterns, gotchas, and conventions live.
67
+
68
+ `search_knowledge(project_id, query)` → Search by keywords related to the task
69
+ `list_knowledge(project_id, prefix)` → Browse entries by key prefix
70
+
71
+ Specifically look for:
72
+ - Entries tagged with the same tags as your task
73
+ - Entries in the `architecture` category for structural decisions
74
+ - Entries in the `gotcha` category for known pitfalls
75
+ - Entries in the `convention` category for coding standards
76
+
77
+ ### 4. Claim the Task
78
+
79
+ `claim_task(task_id)` → Moves the task to `in_progress` and assigns it to you
80
+
81
+ 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.
82
+
83
+ ### 5. Comment: Starting Work
84
+
85
+ `post_comment(task_id, content)` → Post a comment before you start
86
+
87
+ Your starting comment should include:
88
+ - What you understand the task requires
89
+ - Your planned approach
90
+ - Any concerns or assumptions
91
+
92
+ ### 6. Do the Work
93
+
94
+ Execute the actual task — write code, fix bugs, create artifacts, etc.
95
+
96
+ ### 7. Comment: Progress Updates
97
+
98
+ For long-running tasks, post progress comments:
99
+
100
+ `post_comment(task_id, content)` → Share intermediate results, decisions made, or blockers encountered
101
+
102
+ ### 8. Store Work Artifacts
103
+
104
+ `update_context(task_id, context)` → Shallow-merge structured data into the task context
105
+
106
+ Store useful artifacts like:
107
+ - File paths created or modified
108
+ - Key decisions and their rationale
109
+ - Configuration values or environment details
110
+ - Links to PRs, commits, or external resources
111
+
112
+ ### 9. Complete the Task
113
+
114
+ `complete_task(task_id, summary, output)` → Finish the task with a summary and structured output
115
+
116
+ - **summary** — Human-readable description of what was done
117
+ - **output** — Structured data: PR URLs, file paths, metrics, artifacts
118
+
119
+ Completing a task moves it to `done` and automatically unblocks dependent tasks.
120
+
121
+ ## When You're Stuck
122
+
123
+ - `block_task(task_id, reason)` → Move to `blocked` status with a clear reason explaining what you need to proceed
124
+ - `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
125
+
126
+ ## Managing Dependencies
127
+
128
+ Dependencies define task ordering — a task cannot start until all its dependencies are complete.
129
+
130
+ ### Adding Dependencies
131
+
132
+ When creating or planning multi-step work, link tasks:
133
+
134
+ `add_dependencies(task_id, depends_on)` → Link one or more dependency tasks
135
+
136
+ Example: Task B depends on Task A completing first:
137
+ `add_dependencies(task_b_id, [task_a_id])`
138
+
139
+ ### Checking Dependencies
140
+
141
+ Before claiming a task, verify its dependencies are met:
142
+
143
+ `list_dependencies(task_id)` → See what must complete first
144
+ `list_dependents(task_id)` → See what this task blocks
145
+
146
+ ### Removing Dependencies
147
+
148
+ If a dependency is no longer needed:
149
+
150
+ `remove_dependency(task_id, dependency_id)` → Unlink a dependency
151
+
152
+ ### Dependency Tips
153
+ - Claiming a task with unmet dependencies will fail — check first
154
+ - Completing a task automatically notifies dependent tasks via `task.dependency_ready`
155
+ - Use dependencies to break large features into ordered subtasks
156
+
157
+ ## Knowledge Base Integration
158
+
159
+ ### Reading Knowledge
160
+
161
+ **Always** search knowledge before starting a task. Knowledge entries contain:
162
+ - **Architecture decisions** — system design, data flow, component boundaries
163
+ - **Conventions** — naming, file structure, patterns the team follows
164
+ - **Gotchas** — known pitfalls, workarounds, things that aren't obvious
165
+ - **References** — links, docs, external resources
166
+ - **General** — anything else worth knowing
167
+
168
+ ### Writing Knowledge
169
+
170
+ When you discover something important during work, **write it back**:
171
+
172
+ `set_knowledge(project_id, key, title, content, tags, category)`
173
+
174
+ Write knowledge when you:
175
+ - Discover a non-obvious pattern or constraint
176
+ - Make an architecture decision that affects future work
177
+ - Find a gotcha that would trip up other agents
178
+ - Establish a convention through your implementation
179
+
180
+ Categories: `architecture`, `convention`, `gotcha`, `reference`, `general`
181
+
182
+ ## Agent Profile
183
+
184
+ On your first session, register your capabilities:
185
+
186
+ `update_agent_profile(description, skills, max_concurrent_tasks)`
187
+
188
+ This helps OpenGate route tasks to the right agent.
189
+
190
+ ## API Quick Reference
191
+
192
+ | Action | Method | Path |
193
+ |--------|--------|------|
194
+ | Check inbox | GET | /api/agents/me/inbox |
195
+ | Get task | GET | /api/tasks/:id |
196
+ | My tasks | GET | /api/tasks/mine |
197
+ | Next task | GET | /api/tasks/next?skills= |
198
+ | Claim task | POST | /api/tasks/:id/claim |
199
+ | Complete task | POST | /api/tasks/:id/complete |
200
+ | Block task | POST | /api/tasks/:id/block |
201
+ | Handoff task | POST | /api/tasks/:id/handoff |
202
+ | Post comment | POST | /api/tasks/:id/activity |
203
+ | Update context | PATCH | /api/tasks/:id/context |
204
+ | Search knowledge | GET | /api/projects/:id/knowledge/search |
205
+ | List knowledge | GET | /api/projects/:id/knowledge |
206
+ | Set knowledge | PUT | /api/projects/:id/knowledge/:key |
207
+ | Add dependencies | POST | /api/tasks/:id/dependencies |
208
+ | Remove dependency | DELETE | /api/tasks/:id/dependencies/:dep_id |
209
+ | List dependencies | GET | /api/tasks/:id/dependencies |
210
+ | List dependents | GET | /api/tasks/:id/dependents |
211
+ | Update profile | PATCH | /api/auth/me |
212
+ | Heartbeat | POST | /api/agents/heartbeat |