@honeybee-ai/incubator 1.0.0
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.
Potentially problematic release.
This version of @honeybee-ai/incubator might be problematic. Click here for more details.
- package/README.md +586 -0
- package/dashboard/dist/assets/index-CVqcfjV3.js +9 -0
- package/dashboard/dist/assets/index-CyFMIiFl.css +1 -0
- package/dashboard/dist/index.html +13 -0
- package/dist/agent/prompt.d.ts +70 -0
- package/dist/agent/prompt.js +165 -0
- package/dist/agent/prompt.js.map +1 -0
- package/dist/agent/providers.d.ts +6 -0
- package/dist/agent/providers.js +320 -0
- package/dist/agent/providers.js.map +1 -0
- package/dist/agent/runner.d.ts +22 -0
- package/dist/agent/runner.js +282 -0
- package/dist/agent/runner.js.map +1 -0
- package/dist/agent/test-helpers.d.ts +11 -0
- package/dist/agent/test-helpers.js +63 -0
- package/dist/agent/test-helpers.js.map +1 -0
- package/dist/agent/tools.d.ts +4 -0
- package/dist/agent/tools.js +545 -0
- package/dist/agent/tools.js.map +1 -0
- package/dist/agent/types.d.ts +54 -0
- package/dist/agent/types.js +2 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/bus.d.ts +24 -0
- package/dist/bus.js +79 -0
- package/dist/bus.js.map +1 -0
- package/dist/guard.d.ts +44 -0
- package/dist/guard.js +178 -0
- package/dist/guard.js.map +1 -0
- package/dist/heartbeat.d.ts +41 -0
- package/dist/heartbeat.js +104 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/honeycomb.d.ts +49 -0
- package/dist/honeycomb.js +176 -0
- package/dist/honeycomb.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +371 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/config.d.ts +14 -0
- package/dist/integrations/config.js +48 -0
- package/dist/integrations/config.js.map +1 -0
- package/dist/integrations/index.d.ts +4 -0
- package/dist/integrations/index.js +4 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/loader.d.ts +8 -0
- package/dist/integrations/loader.js +27 -0
- package/dist/integrations/loader.js.map +1 -0
- package/dist/integrations/manager.d.ts +29 -0
- package/dist/integrations/manager.js +108 -0
- package/dist/integrations/manager.js.map +1 -0
- package/dist/namespaces.d.ts +25 -0
- package/dist/namespaces.js +84 -0
- package/dist/namespaces.js.map +1 -0
- package/dist/persistence.d.ts +7 -0
- package/dist/persistence.js +62 -0
- package/dist/persistence.js.map +1 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/protocol/index.js +5 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/parser.d.ts +31 -0
- package/dist/protocol/parser.js +193 -0
- package/dist/protocol/parser.js.map +1 -0
- package/dist/protocol/renderer.d.ts +16 -0
- package/dist/protocol/renderer.js +193 -0
- package/dist/protocol/renderer.js.map +1 -0
- package/dist/protocol/schema.d.ts +341 -0
- package/dist/protocol/schema.js +285 -0
- package/dist/protocol/schema.js.map +1 -0
- package/dist/protocol/types.d.ts +144 -0
- package/dist/protocol/types.js +8 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/protocol/variables.d.ts +22 -0
- package/dist/protocol/variables.js +58 -0
- package/dist/protocol/variables.js.map +1 -0
- package/dist/rest.d.ts +3 -0
- package/dist/rest.js +794 -0
- package/dist/rest.js.map +1 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.js +410 -0
- package/dist/server.js.map +1 -0
- package/dist/stores/backend.d.ts +15 -0
- package/dist/stores/backend.js +28 -0
- package/dist/stores/backend.js.map +1 -0
- package/dist/stores/claims.d.ts +14 -0
- package/dist/stores/claims.js +77 -0
- package/dist/stores/claims.js.map +1 -0
- package/dist/stores/conflicts.d.ts +10 -0
- package/dist/stores/conflicts.js +39 -0
- package/dist/stores/conflicts.js.map +1 -0
- package/dist/stores/control.d.ts +37 -0
- package/dist/stores/control.js +105 -0
- package/dist/stores/control.js.map +1 -0
- package/dist/stores/discoveries.d.ts +11 -0
- package/dist/stores/discoveries.js +45 -0
- package/dist/stores/discoveries.js.map +1 -0
- package/dist/stores/events.d.ts +14 -0
- package/dist/stores/events.js +37 -0
- package/dist/stores/events.js.map +1 -0
- package/dist/stores/help.d.ts +11 -0
- package/dist/stores/help.js +46 -0
- package/dist/stores/help.js.map +1 -0
- package/dist/stores/interfaces.d.ts +87 -0
- package/dist/stores/interfaces.js +2 -0
- package/dist/stores/interfaces.js.map +1 -0
- package/dist/stores/messages.d.ts +8 -0
- package/dist/stores/messages.js +29 -0
- package/dist/stores/messages.js.map +1 -0
- package/dist/stores/progress.d.ts +8 -0
- package/dist/stores/progress.js +21 -0
- package/dist/stores/progress.js.map +1 -0
- package/dist/stores/proposals.d.ts +11 -0
- package/dist/stores/proposals.js +46 -0
- package/dist/stores/proposals.js.map +1 -0
- package/dist/stores/redis/claims.d.ts +15 -0
- package/dist/stores/redis/claims.js +96 -0
- package/dist/stores/redis/claims.js.map +1 -0
- package/dist/stores/redis/db.d.ts +39 -0
- package/dist/stores/redis/db.js +34 -0
- package/dist/stores/redis/db.js.map +1 -0
- package/dist/stores/redis/discoveries.d.ts +13 -0
- package/dist/stores/redis/discoveries.js +54 -0
- package/dist/stores/redis/discoveries.js.map +1 -0
- package/dist/stores/redis/events.d.ts +17 -0
- package/dist/stores/redis/events.js +57 -0
- package/dist/stores/redis/events.js.map +1 -0
- package/dist/stores/redis/index.d.ts +3 -0
- package/dist/stores/redis/index.js +29 -0
- package/dist/stores/redis/index.js.map +1 -0
- package/dist/stores/redis/state.d.ts +14 -0
- package/dist/stores/redis/state.js +83 -0
- package/dist/stores/redis/state.js.map +1 -0
- package/dist/stores/reinforcements.d.ts +11 -0
- package/dist/stores/reinforcements.js +42 -0
- package/dist/stores/reinforcements.js.map +1 -0
- package/dist/stores/roles.d.ts +9 -0
- package/dist/stores/roles.js +22 -0
- package/dist/stores/roles.js.map +1 -0
- package/dist/stores/sqlite/claims.d.ts +17 -0
- package/dist/stores/sqlite/claims.js +121 -0
- package/dist/stores/sqlite/claims.js.map +1 -0
- package/dist/stores/sqlite/db.d.ts +16 -0
- package/dist/stores/sqlite/db.js +77 -0
- package/dist/stores/sqlite/db.js.map +1 -0
- package/dist/stores/sqlite/discoveries.d.ts +14 -0
- package/dist/stores/sqlite/discoveries.js +66 -0
- package/dist/stores/sqlite/discoveries.js.map +1 -0
- package/dist/stores/sqlite/events.d.ts +16 -0
- package/dist/stores/sqlite/events.js +75 -0
- package/dist/stores/sqlite/events.js.map +1 -0
- package/dist/stores/sqlite/index.d.ts +2 -0
- package/dist/stores/sqlite/index.js +31 -0
- package/dist/stores/sqlite/index.js.map +1 -0
- package/dist/stores/sqlite/state.d.ts +15 -0
- package/dist/stores/sqlite/state.js +99 -0
- package/dist/stores/sqlite/state.js.map +1 -0
- package/dist/stores/state.d.ts +11 -0
- package/dist/stores/state.js +67 -0
- package/dist/stores/state.js.map +1 -0
- package/dist/types.d.ts +45 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +20 -0
- package/dist/utils.js.map +1 -0
- package/dist/ws.d.ts +25 -0
- package/dist/ws.js +117 -0
- package/dist/ws.js.map +1 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
# Incubator
|
|
2
|
+
|
|
3
|
+
**Give your swarm shared memory.**
|
|
4
|
+
|
|
5
|
+
When you spawn multiple AI agents to work on the same problem, they run blind. No awareness of what others are doing. They make decisions in isolation and commit to them — picking the same variable names, overwriting each other's files, duplicating work that's already done.
|
|
6
|
+
|
|
7
|
+
Incubator is an MCP server that lets agents add live context to each other while running. No orchestrator deciding who does what. No pre-scripted roles. No sequential handoffs. Agents self-organize by reading each other's state, claiming resources before touching them, and sharing discoveries as they work.
|
|
8
|
+
|
|
9
|
+
And anything that speaks HTTP can join the conversation.
|
|
10
|
+
|
|
11
|
+
## The Problem
|
|
12
|
+
|
|
13
|
+
3 agents splitting a monolith into modules, without coordination:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
agent 1: "I split the monolith into 5 modules" ← did all the work
|
|
17
|
+
agent 2: "I split the monolith into 5 modules" ← overwrote agent 1
|
|
18
|
+
agent 3: "I split the monolith into 5 modules" ← overwrote agent 2
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
All 3 did the same 5 files. 15 file writes for 6 files of actual work. Last writer wins, everyone else's output is garbage.
|
|
22
|
+
|
|
23
|
+
## The Fix
|
|
24
|
+
|
|
25
|
+
Same 3 agents, same task, with Incubator:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
agent_45af598c claim file:utils.js → approved
|
|
29
|
+
agent_45af598c claim file:cache.js → approved
|
|
30
|
+
agent_dff039a5 claim file:utils.js → rejected (owner: agent_45af598c)
|
|
31
|
+
agent_dff039a5 claim file:posts.js → approved
|
|
32
|
+
agent_dff039a5 claim file:search.js → approved
|
|
33
|
+
agent_13fa08bc claim file:utils.js → rejected (owner: agent_45af598c)
|
|
34
|
+
agent_13fa08bc claim file:posts.js → rejected (owner: agent_dff039a5)
|
|
35
|
+
...
|
|
36
|
+
agent_45af598c publishDiscovery "module:utils.js exports generateId, retry, debounce, deepClone"
|
|
37
|
+
agent_dff039a5 publishDiscovery "module:posts.js exports createPost, publishPost, addTag, removeTag"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Each agent claims what it's working on. Rejections redirect agents to unclaimed work. Discoveries give every agent context about what others decided — without anyone having to prompt them.
|
|
41
|
+
|
|
42
|
+
6 file writes for 6 files. Zero collisions. Zero wasted work.
|
|
43
|
+
|
|
44
|
+
## Bottom-Up, Not Top-Down
|
|
45
|
+
|
|
46
|
+
Most multi-agent frameworks use an orchestrator: a central brain that assigns tasks, routes messages, and sequences work. The orchestrator is a bottleneck and a single point of failure. It has to understand the problem well enough to decompose it. If it plans wrong, every agent does wrong work.
|
|
47
|
+
|
|
48
|
+
Incubator is the opposite. There's no orchestrator. Agents share a coordination layer — claims, discoveries, events, state — and figure out the division of labor themselves. The coordination primitives are simple enough that any LLM understands them from the tool descriptions alone.
|
|
49
|
+
|
|
50
|
+
| | Orchestrator (top-down) | Incubator (bottom-up) |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| **Who decides** | Central planner | Each agent |
|
|
53
|
+
| **Failure mode** | Orchestrator wrong → all agents wrong | One agent wrong → others adapt |
|
|
54
|
+
| **Scaling** | Orchestrator becomes bottleneck | Agents self-balance |
|
|
55
|
+
| **Flexibility** | Pre-planned task graph | Emergent work distribution |
|
|
56
|
+
| **Agent awareness** | Only what orchestrator tells them | Full shared context |
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
Incubator is a lightweight MCP server with a REST API. Agents connect over MCP and get coordination tools. Everything else — dashboards, scripts, webhooks, CI pipelines — talks REST.
|
|
61
|
+
|
|
62
|
+
Same stores. Same state. Three protocols.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
┌───────────┐ MCP ┌─────────────────┐ REST ┌─────────────┐
|
|
66
|
+
│ Agent 1 │◄────────────►│ │◄─────────────►│ Dashboard │
|
|
67
|
+
│ Agent 2 │◄────────────►│ Incubator │◄─────────────►│ Webhooks │
|
|
68
|
+
│ Agent 3 │◄────────────►│ │◄─────────────►│ CI / cron │
|
|
69
|
+
└───────────┘ └────────┬────────┘ └─────────────┘
|
|
70
|
+
│ WebSocket
|
|
71
|
+
┌───────▼────────┐
|
|
72
|
+
│ Live clients │
|
|
73
|
+
│ (push events) │
|
|
74
|
+
└────────────────┘
|
|
75
|
+
Memory, SQLite, or Redis
|
|
76
|
+
Single process or distributed
|
|
77
|
+
Zero to minimal infrastructure
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### MCP Tools
|
|
81
|
+
|
|
82
|
+
**Claims** — resource locking, first-come-first-served
|
|
83
|
+
- `incubator_claim` / `incubator_releaseClaim` / `incubator_checkClaim` / `incubator_listClaims`
|
|
84
|
+
|
|
85
|
+
**Discoveries** — shared knowledge with text search
|
|
86
|
+
- `incubator_publishDiscovery` / `incubator_searchDiscoveries`
|
|
87
|
+
|
|
88
|
+
**Events** — agent-to-agent signaling with cursor-based polling
|
|
89
|
+
- `incubator_publishEvent` / `incubator_getEvents`
|
|
90
|
+
|
|
91
|
+
**State** — shared key-value store, last-writer-wins
|
|
92
|
+
- `incubator_getState` / `incubator_setState` / `incubator_queryState` / `incubator_deleteState`
|
|
93
|
+
|
|
94
|
+
**Protocol** — coordination spec bootstrap
|
|
95
|
+
- `incubator_getProtocol` — returns phase-aware instructions, rules, and resource conventions from a loaded spec
|
|
96
|
+
|
|
97
|
+
The tool descriptions themselves are the coordination protocol. Agents read them, understand the pattern, and self-organize. No plugin, no separate instructions, no injected system prompts. For structured workflows, load a [coordination spec](#coordination-specs) and agents call `getProtocol` to get phase-specific instructions.
|
|
98
|
+
|
|
99
|
+
### REST API
|
|
100
|
+
|
|
101
|
+
Every MCP operation is also available over plain HTTP. Prefix with `/api/`:
|
|
102
|
+
|
|
103
|
+
| Method | Route | Description |
|
|
104
|
+
|--------|-------|-------------|
|
|
105
|
+
| `GET` | `/api/health` | Server status, agent count, claim/event counts |
|
|
106
|
+
| `GET` | `/api/state/:key` | Read a state value |
|
|
107
|
+
| `PUT` | `/api/state/:key` | Write a state value |
|
|
108
|
+
| `DELETE` | `/api/state/:key` | Delete a state value |
|
|
109
|
+
| `GET` | `/api/state?pattern=&category=` | Query state entries |
|
|
110
|
+
| `POST` | `/api/claims` | Claim a resource |
|
|
111
|
+
| `DELETE` | `/api/claims/:resource` | Release a claim |
|
|
112
|
+
| `GET` | `/api/claims/:resource` | Check who owns a resource |
|
|
113
|
+
| `GET` | `/api/claims?pattern=` | List active claims |
|
|
114
|
+
| `POST` | `/api/events` | Publish an event |
|
|
115
|
+
| `GET` | `/api/events?since=&type=` | Poll events by cursor |
|
|
116
|
+
| `POST` | `/api/discoveries` | Share a discovery |
|
|
117
|
+
| `GET` | `/api/discoveries?query=&category=` | Search discoveries |
|
|
118
|
+
| `GET` | `/api/protocol` | Get loaded coordination spec |
|
|
119
|
+
| `PUT` | `/api/protocol` | Load a coordination spec |
|
|
120
|
+
|
|
121
|
+
Agent identity is passed via `agentId` in the JSON body or `X-Agent-Id` header. CORS is enabled by default. Namespace isolation via `X-Namespace` header (default: `default`).
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Check server health
|
|
125
|
+
curl http://localhost:3100/api/health
|
|
126
|
+
|
|
127
|
+
# Set shared state
|
|
128
|
+
curl -X PUT http://localhost:3100/api/state/config \
|
|
129
|
+
-H "Content-Type: application/json" \
|
|
130
|
+
-d '{"value": {"maxRetries": 3}, "agentId": "dashboard"}'
|
|
131
|
+
|
|
132
|
+
# Watch active claims
|
|
133
|
+
curl http://localhost:3100/api/claims
|
|
134
|
+
|
|
135
|
+
# Search what agents have found
|
|
136
|
+
curl "http://localhost:3100/api/discoveries?query=naming+convention"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### WebSocket (Real-Time Push)
|
|
140
|
+
|
|
141
|
+
Events are pushed to WebSocket clients the instant they're published. No polling. Connect to `/ws` on the same port:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
ws://localhost:3100/ws?namespace=default&since=0&types=conflict,completed
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| Parameter | Description |
|
|
148
|
+
|-----------|-------------|
|
|
149
|
+
| `namespace` | Namespace to subscribe to (default: `default`) |
|
|
150
|
+
| `since` | Replay events after this cursor, then stream live |
|
|
151
|
+
| `types` | Comma-separated event type filter (omit for all) |
|
|
152
|
+
|
|
153
|
+
On connect, the server replays any missed events (from `since`), sends a `replay_done` marker, then streams live events as they happen:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{"type": "event", "event": {"id": 5, "type": "conflict", "data": {...}, ...}}
|
|
157
|
+
{"type": "event", "event": {"id": 6, "type": "completed", "data": {...}, ...}}
|
|
158
|
+
{"type": "replay_done", "cursor": 6}
|
|
159
|
+
{"type": "event", "event": {"id": 7, "type": "conflict", "data": {...}, ...}}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Clients can update their type filter without reconnecting:
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{"type": "subscribe", "types": ["conflict"]}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Send `null` types to receive everything again. A `ping` message is sent every 30 seconds as a keepalive.
|
|
169
|
+
|
|
170
|
+
WebSocket auto-enables in HTTP mode when the `ws` package is installed. If not installed, everything else works normally — agents poll via MCP, REST clients poll via `GET /api/events?since=`.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# Install ws to enable WebSocket push
|
|
174
|
+
pnpm add ws
|
|
175
|
+
|
|
176
|
+
# Quick test with wscat
|
|
177
|
+
wscat -c ws://localhost:3100/ws
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**With Redis backend**, WebSocket push works across multiple Incubator instances. Events published to one instance are pushed to WebSocket clients connected to any instance via Redis Pub/Sub.
|
|
181
|
+
|
|
182
|
+
## The STOP Button
|
|
183
|
+
|
|
184
|
+
The REST API enables something orchestrators can't do cleanly: external control over running agents.
|
|
185
|
+
|
|
186
|
+
Agents check a shared state key before each operation. An external system sets that key via REST. Next time any agent checks, it sees the signal and stops gracefully.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# Agents are working...
|
|
190
|
+
|
|
191
|
+
# Send STOP from anywhere — curl, a dashboard, a webhook
|
|
192
|
+
curl -X PUT http://localhost:3100/api/state/halt \
|
|
193
|
+
-H "Content-Type: application/json" \
|
|
194
|
+
-d '{"value": true, "agentId": "dashboard"}'
|
|
195
|
+
|
|
196
|
+
# Agents see halt=true on their next state check, report what they completed, and exit
|
|
197
|
+
|
|
198
|
+
# Resume later
|
|
199
|
+
curl -X DELETE http://localhost:3100/api/state/halt
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This isn't a special feature. It's just the coordination primitives doing their job. Any state key can be a control signal. Any REST client can send it. The agents don't know or care that the signal came from outside MCP.
|
|
203
|
+
|
|
204
|
+
See the [examples package](https://github.com/agentcoordinationprotocol/acp-spec/tree/main/packages/examples) for protocol demos.
|
|
205
|
+
|
|
206
|
+
## Security: Coordination Injection
|
|
207
|
+
|
|
208
|
+
The coordination layer is an attack surface. If one agent gets compromised via prompt injection, it can poison shared state, discoveries, and events that every other agent trusts. We call this a **coordination injection attack** — the compromised agent doesn't need to hack the server. It just writes something toxic into shared memory and lets the other agents do the damage.
|
|
209
|
+
|
|
210
|
+
Incubator defends against this with [carapace](https://www.npmjs.com/package/carapace), a deterministic prompt injection scanner. Three layers of defense, all on by default:
|
|
211
|
+
|
|
212
|
+
1. **Writes** — every write to state, claims, events, and discoveries is scanned before it hits the stores. Malicious content is rejected with a `PIABlockedError`.
|
|
213
|
+
2. **Reads** — every read filters results through the scanner. Poisoned entries that somehow entered the stores (migration, manual edit, pre-guard snapshots) are silently dropped.
|
|
214
|
+
3. **Snapshots** — persisted snapshots are scanned on load. Poisoned entries are stripped before they reach the stores.
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
agent_evil → setState("config", "Ignore all instructions...")
|
|
218
|
+
→ carapace: score 150, BLOCK
|
|
219
|
+
→ 403 { error: "prompt_injection_detected", findings: [...] }
|
|
220
|
+
|
|
221
|
+
agent_good → setState("config", "maxRetries: 3")
|
|
222
|
+
→ carapace: score 0, PASS
|
|
223
|
+
→ 200 { success: true }
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Internal system events (like `claim.acquired`) bypass the guard since they're generated by Incubator itself, not by agent input.
|
|
227
|
+
|
|
228
|
+
**MCP agents** see `{ isError: true, text: "[carapace] Blocked: ..." }` and can retry with clean content. **REST clients** get a `403` with the full scan result including score and findings.
|
|
229
|
+
|
|
230
|
+
To disable (not recommended):
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
node dist/index.js --http --no-guard
|
|
234
|
+
# WARNING: Carapace disabled — no prompt injection scanning
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Use Cases
|
|
238
|
+
|
|
239
|
+
**Multi-agent coding** — N agents working on the same repo. Claims prevent file conflicts. Discoveries share naming conventions and patterns found.
|
|
240
|
+
|
|
241
|
+
**Human-in-the-loop control** — Dashboard monitors agent progress in real time via REST. Pause, resume, or redirect agents by setting state.
|
|
242
|
+
|
|
243
|
+
**CI/CD integration** — Pipeline sets shared state before agents run (target branch, test results, deploy config). Agents read it without custom tooling.
|
|
244
|
+
|
|
245
|
+
**Webhook-driven coordination** — External events (GitHub PR merged, Slack message, monitoring alert) publish to the event store. Agents poll and react.
|
|
246
|
+
|
|
247
|
+
**Agent handoffs** — Agent A finishes phase 1, publishes discoveries summarizing what it learned. Agent B picks up phase 2 with full context, no prompt needed.
|
|
248
|
+
|
|
249
|
+
**Rate limiting / resource management** — Claims with TTL as leases. Agents check out API quotas, database connections, or deployment slots.
|
|
250
|
+
|
|
251
|
+
## Quick Start
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
pnpm install
|
|
255
|
+
pnpm run build
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Start the server
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# HTTP mode (multi-agent, MCP + REST)
|
|
262
|
+
# Carapace is on by default — all writes are scanned for prompt injection
|
|
263
|
+
node dist/index.js --http --port=3100 --verbose
|
|
264
|
+
|
|
265
|
+
# With SQLite persistence (survives restarts)
|
|
266
|
+
node dist/index.js --http --backend=sqlite --db=./incubator.db
|
|
267
|
+
|
|
268
|
+
# With a coordination spec (agents call getProtocol for instructions)
|
|
269
|
+
node dist/index.js --http --protocol=path/to/spec.acp.yml
|
|
270
|
+
|
|
271
|
+
# With Redis (multi-process scaling, shared across instances)
|
|
272
|
+
node dist/index.js --http --backend=redis
|
|
273
|
+
node dist/index.js --http --backend=redis --redis-url=redis://10.0.0.5:6379
|
|
274
|
+
|
|
275
|
+
# With JSON snapshot persistence (memory backend only)
|
|
276
|
+
node dist/index.js --http --persist=./data/snapshot
|
|
277
|
+
|
|
278
|
+
# Stdio mode (single agent / testing)
|
|
279
|
+
node dist/index.js --agent-id=agent_1
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Connect from Claude Code
|
|
283
|
+
|
|
284
|
+
Create an MCP config file:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"mcpServers": {
|
|
289
|
+
"incubator": {
|
|
290
|
+
"type": "http",
|
|
291
|
+
"url": "http://localhost:3100/mcp"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Then pass it when launching agents:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
claude -p "your task here" --mcp-config mcp.json --model haiku
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Or add it permanently:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
claude mcp add --transport http incubator http://localhost:3100/mcp
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Tell agents how to coordinate
|
|
310
|
+
|
|
311
|
+
Add coordination instructions to your prompt:
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
You are one of N agents working on the same directory. Use the incubator tools:
|
|
315
|
+
|
|
316
|
+
1. Call incubator_searchDiscoveries first to see what others have done.
|
|
317
|
+
2. Call incubator_claim before touching any file. If rejected, skip it.
|
|
318
|
+
3. Call incubator_publishDiscovery after completing work so others know.
|
|
319
|
+
4. Call incubator_releaseClaim when done with each resource.
|
|
320
|
+
5. Only touch resources you successfully claimed.
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
That's it. The agents figure out the rest.
|
|
324
|
+
|
|
325
|
+
For structured workflows, use a [coordination spec](#coordination-specs) instead — agents call `getProtocol` and get phase-aware instructions automatically.
|
|
326
|
+
|
|
327
|
+
## Coordination Specs
|
|
328
|
+
|
|
329
|
+
For repeatable workflows, define the coordination pattern once in a `.acp.json` file. Agents call `getProtocol` on startup and get phase-aware instructions — no copy-paste prompts, no hardcoded coordination rules.
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
# Start with a coordination spec
|
|
333
|
+
node dist/index.js --http --protocol=path/to/spec.acp.yml
|
|
334
|
+
|
|
335
|
+
# Agents call getProtocol to get their instructions
|
|
336
|
+
# No prompt injection of coordination rules needed
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
A spec defines **roles** (types of agents), **phases** (workflow stages), **rules** (what each role does per phase), and **resources** (naming conventions for claims, discoveries, events, state keys).
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"acp": "1.0",
|
|
344
|
+
"name": "monolith-split",
|
|
345
|
+
"title": "Monolith Module Split",
|
|
346
|
+
"roles": {
|
|
347
|
+
"worker": { "description": "Extracts modules from the monolith", "count": "2+" },
|
|
348
|
+
"coordinator": { "description": "Monitors progress, manages phases", "count": "0-1" }
|
|
349
|
+
},
|
|
350
|
+
"phases": {
|
|
351
|
+
"init": { "description": "Orient and check existing state" },
|
|
352
|
+
"work": { "description": "Claim and process resources" },
|
|
353
|
+
"done": { "description": "All work complete", "terminal": true }
|
|
354
|
+
},
|
|
355
|
+
"rules": {
|
|
356
|
+
"worker": {
|
|
357
|
+
"work": {
|
|
358
|
+
"loop": true,
|
|
359
|
+
"steps": [
|
|
360
|
+
{ "action": "claim", "params": { "resource": "$next_unclaimed" }, "on_rejected": { "skip": true } },
|
|
361
|
+
{ "action": "external", "hint": "Read source, extract module, write output" },
|
|
362
|
+
{ "action": "publishDiscovery", "params": { "topic": "module:$resource", "category": "exports" } },
|
|
363
|
+
{ "action": "releaseClaim" }
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
When an agent calls `incubator_getProtocol`, it gets back:
|
|
372
|
+
|
|
373
|
+
- **Rendered instructions** — natural language prose the LLM follows, generated from the spec + current runtime state
|
|
374
|
+
- **Structured rules** — the steps for its role in the current phase, with resolved `$variables`
|
|
375
|
+
- **Resource conventions** — how to name claims, discoveries, events, and state keys
|
|
376
|
+
- **Phase overview** — all phases and which one is current
|
|
377
|
+
- **Error handling** — what to do on claim rejection, expiry, or halt
|
|
378
|
+
|
|
379
|
+
The agent calls `getProtocol` once on startup, follows the instructions, and calls it again when it observes a phase change. Phases advance via shared state (`setState({ key: 'phase', value: 'work' })`).
|
|
380
|
+
|
|
381
|
+
### Runtime variables
|
|
382
|
+
|
|
383
|
+
Specs can use `$variables` that resolve against live store state:
|
|
384
|
+
|
|
385
|
+
| Variable | Source | Description |
|
|
386
|
+
|----------|--------|-------------|
|
|
387
|
+
| `$self` | Agent ID | The calling agent's identifier |
|
|
388
|
+
| `$cursor` | Event store | Current event cursor for polling |
|
|
389
|
+
| `$next_unclaimed` | Claims + instances | First resource not yet claimed or completed |
|
|
390
|
+
| `$active_claims` | Claims store | Currently active claim resources |
|
|
391
|
+
| `$completed_resources` | Discoveries | Resources with published discoveries |
|
|
392
|
+
|
|
393
|
+
### Loading specs
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
# Via CLI flag (loaded on startup)
|
|
397
|
+
node dist/index.js --http --protocol=path/to/spec.acp.json
|
|
398
|
+
|
|
399
|
+
# Via REST API (loaded at runtime)
|
|
400
|
+
curl -X PUT http://localhost:3100/api/protocol \
|
|
401
|
+
-H "Content-Type: application/json" \
|
|
402
|
+
-d '{"spec": "{\"acp\":\"1.0\", ...}"}'
|
|
403
|
+
|
|
404
|
+
# Check what's loaded
|
|
405
|
+
curl http://localhost:3100/api/protocol
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
See the [ACP examples package](https://github.com/agentcoordinationprotocol/acp-spec/tree/main/packages/examples) for protocol specs.
|
|
409
|
+
|
|
410
|
+
## Technical Details
|
|
411
|
+
|
|
412
|
+
- **Near-zero infrastructure** — Default is pure in-memory. Optional SQLite for single-process persistence. Optional Redis for multi-process scaling.
|
|
413
|
+
- **Sub-millisecond startup** — `node dist/index.js` and it's ready. SQLite auto-creates its schema on first run. Redis connects before the server starts.
|
|
414
|
+
- **Single process or distributed** — Memory and SQLite are single-threaded with no race conditions. Redis lets multiple Incubator instances share one coordination layer.
|
|
415
|
+
- **Pluggable backends** — Memory (default), SQLite, or Redis. Same async interfaces, same behavior. Swap with a CLI flag.
|
|
416
|
+
- **Minimal dependencies** — `@agentcoordinationprotocol/spec`, `@modelcontextprotocol/sdk`, `zod`, and `carapace`. `better-sqlite3`, `ioredis`, `ws` are optional (loaded on demand).
|
|
417
|
+
- **Three protocols** — MCP for agents, REST for external systems, WebSocket for real-time push. Same stores, same state.
|
|
418
|
+
- **Three persistence modes** — JSON snapshots (`--persist`) for memory backend. SQLite and Redis are inherently persistent.
|
|
419
|
+
- **Optional TTL** — State entries and claims can auto-expire. Lazy cleanup on read (consistent across all backends).
|
|
420
|
+
- **Cursor-based events** — Agents call `getEvents(since: cursor)` to catch up via MCP/REST. WebSocket clients get events pushed in real time with replay-then-live (no missed events, no duplicates).
|
|
421
|
+
- **Text search for discoveries** — Substring matching, not vector similarity. At agent scale (dozens of discoveries, not millions), it's instant and deterministic.
|
|
422
|
+
- **Namespace isolation** — Multiple teams/projects share one server. SQLite isolates by namespace column; Redis by key prefix. No data leakage.
|
|
423
|
+
|
|
424
|
+
## Architecture
|
|
425
|
+
|
|
426
|
+
```
|
|
427
|
+
incubator/
|
|
428
|
+
src/
|
|
429
|
+
index.ts # CLI entry, dual transport (stdio + HTTP)
|
|
430
|
+
server.ts # McpServer, 13 tool registrations
|
|
431
|
+
rest.ts # REST API route handlers
|
|
432
|
+
bus.ts # NotificationBus (LocalBus + RedisBus)
|
|
433
|
+
ws.ts # WebSocket manager (upgrade, replay, live push)
|
|
434
|
+
guard.ts # Carapace integration (coordination injection defense)
|
|
435
|
+
stores/
|
|
436
|
+
interfaces.ts # Store interfaces (IStateStore, IEventStore, etc.)
|
|
437
|
+
backend.ts # Backend factory (memory, sqlite, or redis)
|
|
438
|
+
state.ts # Memory: key-value store (last-writer-wins)
|
|
439
|
+
claims.ts # Memory: resource claims (first-come-first-served)
|
|
440
|
+
events.ts # Memory: append-only event log with cursors
|
|
441
|
+
discoveries.ts # Memory: shared findings with text search
|
|
442
|
+
sqlite/
|
|
443
|
+
db.ts # Schema init, DB handle caching
|
|
444
|
+
state.ts # SQLite state store
|
|
445
|
+
claims.ts # SQLite claim store
|
|
446
|
+
events.ts # SQLite event store
|
|
447
|
+
discoveries.ts # SQLite discovery store
|
|
448
|
+
index.ts # createSqliteStores factory
|
|
449
|
+
redis/
|
|
450
|
+
db.ts # Connection management, client caching
|
|
451
|
+
state.ts # Redis state store (Hash)
|
|
452
|
+
claims.ts # Redis claim store (Hash + WATCH)
|
|
453
|
+
events.ts # Redis event store (Sorted Set + MULTI/EXEC)
|
|
454
|
+
discoveries.ts # Redis discovery store (List)
|
|
455
|
+
index.ts # createRedisStores factory
|
|
456
|
+
types.ts # TypeScript types
|
|
457
|
+
persistence.ts # JSON snapshot save/load (memory backend)
|
|
458
|
+
namespaces.ts # Namespace registry with backend config
|
|
459
|
+
utils.ts # matchGlob, generateId, isExpired
|
|
460
|
+
e2e/
|
|
461
|
+
run.ts # Simulated E2E (4 workers × 10 rounds)
|
|
462
|
+
stop-button.ts # STOP button integration test
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Design Decisions
|
|
466
|
+
|
|
467
|
+
- **Bottom-up coordination** — No orchestrator. Agents self-organize through shared primitives. The tool descriptions are the protocol.
|
|
468
|
+
- **Claims are first-come-first-served** — Rejected claims return the current owner so agents can adapt, not just fail.
|
|
469
|
+
- **Last-writer-wins for state** — State is shared knowledge, not a lock. Use claims for mutual exclusion.
|
|
470
|
+
- **Polling for agents, push for dashboards** — MCP agents poll with `getEvents(since: cursor)` when they're ready. WebSocket clients get real-time push with replay-then-live. Both share the same event store and cursors.
|
|
471
|
+
- **REST mirrors MCP** — Same operations, same stores. Agents don't need to know REST exists. External systems don't need to speak MCP.
|
|
472
|
+
- **No vector DB** — Substring search for discoveries. Deterministic, instant, and you can `grep` the results in your head.
|
|
473
|
+
- **Guard by default** — Carapace scans all writes. Opt out with `--no-guard`, not opt in. Shared memory is a trust boundary; it should be defended unless you explicitly choose otherwise.
|
|
474
|
+
- **Pluggable, not coupled** — Async store interfaces let you swap memory for SQLite or Redis without touching any consumer code. Same tests run against all three backends.
|
|
475
|
+
|
|
476
|
+
## Testing
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
# Unit + integration tests (stores, guard, WebSocket, namespaces, protocol)
|
|
480
|
+
# Redis tests skip gracefully if no Redis is running
|
|
481
|
+
pnpm test
|
|
482
|
+
|
|
483
|
+
# Run with Redis for full coverage
|
|
484
|
+
docker run -d --name redis-test -p 6379:6379 redis:alpine
|
|
485
|
+
pnpm test
|
|
486
|
+
docker stop redis-test && docker rm redis-test
|
|
487
|
+
|
|
488
|
+
# Simulated E2E (4 workers × 10 rounds, no LLM)
|
|
489
|
+
npx tsx e2e/run.ts
|
|
490
|
+
|
|
491
|
+
# STOP button integration test (3 MCP workers + REST halt signal)
|
|
492
|
+
npx tsx e2e/stop-button.ts
|
|
493
|
+
|
|
494
|
+
# Real agent E2E (3 Claude agents, costs ~$0.15)
|
|
495
|
+
bash e2e/run-real.sh
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
## Agent Runner (Built-in LLM Agents)
|
|
499
|
+
|
|
500
|
+
Incubator can spawn LLM-backed agents that coordinate through ACP primitives autonomously. Load a protocol spec and watch agents self-organize — no external agent framework needed.
|
|
501
|
+
|
|
502
|
+
**Any provider that speaks OpenAI chat completions works.** Ollama (local, free), OpenAI, Anthropic, Groq, Together, Fireworks, Mistral — one code path covers them all.
|
|
503
|
+
|
|
504
|
+
### Quick Start
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# Ollama (local, free — install from ollama.com, then pull a model)
|
|
508
|
+
ollama pull qwen3:32b
|
|
509
|
+
node dist/index.js --http --protocol=protocol.acp.yml \
|
|
510
|
+
--spawn --provider=ollama/qwen3:32b --verbose
|
|
511
|
+
|
|
512
|
+
# OpenAI
|
|
513
|
+
OPENAI_API_KEY=sk-... node dist/index.js --http \
|
|
514
|
+
--protocol=protocol.acp.yml \
|
|
515
|
+
--spawn --provider=openai/gpt-4o --verbose
|
|
516
|
+
|
|
517
|
+
# Anthropic
|
|
518
|
+
ANTHROPIC_API_KEY=sk-ant-... node dist/index.js --http \
|
|
519
|
+
--protocol=protocol.acp.yml \
|
|
520
|
+
--spawn --provider=anthropic/claude-sonnet-4-5-20250929 --verbose
|
|
521
|
+
|
|
522
|
+
# Groq (or any OpenAI-compatible API)
|
|
523
|
+
OPENAI_API_KEY=gsk-... node dist/index.js --http \
|
|
524
|
+
--protocol=protocol.acp.yml \
|
|
525
|
+
--spawn --provider=groq/llama-3.3-70b-versatile --verbose
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Provider Shorthand
|
|
529
|
+
|
|
530
|
+
Format: `provider/model`
|
|
531
|
+
|
|
532
|
+
| Provider | Example | API Key Env Var |
|
|
533
|
+
|----------|---------|-----------------|
|
|
534
|
+
| Ollama | `ollama/qwen3:32b` | None (local) |
|
|
535
|
+
| OpenAI | `openai/gpt-4o` | `OPENAI_API_KEY` |
|
|
536
|
+
| Anthropic | `anthropic/claude-sonnet-4-5-20250929` | `ANTHROPIC_API_KEY` |
|
|
537
|
+
| Groq | `groq/llama-3.3-70b-versatile` | `OPENAI_API_KEY` |
|
|
538
|
+
| Together | `together/meta-llama/Llama-3-70b` | `OPENAI_API_KEY` |
|
|
539
|
+
|
|
540
|
+
Unknown provider names are treated as OpenAI-compatible (same chat completions format).
|
|
541
|
+
|
|
542
|
+
### Configuration
|
|
543
|
+
|
|
544
|
+
| Flag | Default | Description |
|
|
545
|
+
|------|---------|-------------|
|
|
546
|
+
| `--spawn` | — | Enable agent spawning (requires `--protocol`) |
|
|
547
|
+
| `--provider=<shorthand>` | `ollama/qwen3:8b` | Default provider/model for all agents |
|
|
548
|
+
| `--max-iterations=<n>` | `50` | Per-agent iteration limit |
|
|
549
|
+
| `--spawn-roles=<roles>` | all roles | Comma-separated role filter |
|
|
550
|
+
|
|
551
|
+
### How It Works
|
|
552
|
+
|
|
553
|
+
1. Server starts in HTTP mode with a protocol spec loaded
|
|
554
|
+
2. `--spawn` reads the spec's `roles` section and spawns the right number of agents
|
|
555
|
+
3. Each agent gets a system prompt generated from the protocol spec + their role
|
|
556
|
+
4. Agents run a ReAct loop: call LLM → execute tool calls via REST API → feed results back
|
|
557
|
+
5. Tool calls map to the 13 Incubator tools (`incubator_getState`, `incubator_claim`, etc.)
|
|
558
|
+
6. Every 10 iterations, agents re-fetch their protocol to detect phase transitions
|
|
559
|
+
7. Agents finish when they stop making tool calls, say "DONE", or hit `--max-iterations`
|
|
560
|
+
8. Ctrl+C gracefully stops all agents between iterations
|
|
561
|
+
|
|
562
|
+
Each agent gets color-coded console output (with `--verbose`), so you can follow multi-agent coordination in real time.
|
|
563
|
+
|
|
564
|
+
### Programmatic Usage
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { AgentRunner } from './agent/runner.js';
|
|
568
|
+
import { resolveProvider, preflight } from './agent/providers.js';
|
|
569
|
+
|
|
570
|
+
const provider = resolveProvider('ollama/qwen3:32b');
|
|
571
|
+
await preflight(provider, true);
|
|
572
|
+
|
|
573
|
+
const runner = new AgentRunner({
|
|
574
|
+
defaultProvider: provider,
|
|
575
|
+
serverUrl: 'http://localhost:3100',
|
|
576
|
+
maxIterations: 50,
|
|
577
|
+
verbose: true,
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const results = await runner.spawnFromProtocol('protocol.acp.yml');
|
|
581
|
+
// results: [{ agentId, role, status, iterations, error? }, ...]
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## Why "Incubator"
|
|
585
|
+
|
|
586
|
+
Agents hatch ideas in isolation. Give them a shared space and they become a swarm.
|