agent-relay 1.0.7 → 1.0.9
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/README.md +176 -6
- package/dist/bridge/config.d.ts +41 -0
- package/dist/bridge/config.d.ts.map +1 -0
- package/dist/bridge/config.js +143 -0
- package/dist/bridge/config.js.map +1 -0
- package/dist/bridge/index.d.ts +10 -0
- package/dist/bridge/index.d.ts.map +1 -0
- package/dist/bridge/index.js +10 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/multi-project-client.d.ts +99 -0
- package/dist/bridge/multi-project-client.d.ts.map +1 -0
- package/dist/bridge/multi-project-client.js +386 -0
- package/dist/bridge/multi-project-client.js.map +1 -0
- package/dist/bridge/spawner.d.ts +46 -0
- package/dist/bridge/spawner.d.ts.map +1 -0
- package/dist/bridge/spawner.js +223 -0
- package/dist/bridge/spawner.js.map +1 -0
- package/dist/bridge/types.d.ts +55 -0
- package/dist/bridge/types.d.ts.map +1 -0
- package/dist/bridge/types.js +6 -0
- package/dist/bridge/types.js.map +1 -0
- package/dist/bridge/utils.d.ts +30 -0
- package/dist/bridge/utils.d.ts.map +1 -0
- package/dist/bridge/utils.js +54 -0
- package/dist/bridge/utils.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +906 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/daemon/agent-registry.d.ts +60 -0
- package/dist/daemon/agent-registry.d.ts.map +1 -0
- package/dist/daemon/agent-registry.js +163 -0
- package/dist/daemon/agent-registry.js.map +1 -0
- package/dist/daemon/connection.d.ts +33 -1
- package/dist/daemon/connection.d.ts.map +1 -1
- package/dist/daemon/connection.js +86 -11
- package/dist/daemon/connection.js.map +1 -1
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +2 -0
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/registry.d.ts +9 -0
- package/dist/daemon/registry.d.ts.map +1 -0
- package/dist/daemon/registry.js +9 -0
- package/dist/daemon/registry.js.map +1 -0
- package/dist/daemon/router.d.ts +61 -2
- package/dist/daemon/router.d.ts.map +1 -1
- package/dist/daemon/router.js +219 -4
- package/dist/daemon/router.js.map +1 -1
- package/dist/daemon/server.d.ts +9 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +135 -16
- package/dist/daemon/server.js.map +1 -1
- package/dist/dashboard/metrics.d.ts +105 -0
- package/dist/dashboard/metrics.d.ts.map +1 -0
- package/dist/dashboard/metrics.js +192 -0
- package/dist/dashboard/metrics.js.map +1 -0
- package/dist/dashboard/needs-attention.d.ts +24 -0
- package/dist/dashboard/needs-attention.d.ts.map +1 -0
- package/dist/dashboard/needs-attention.js +78 -0
- package/dist/dashboard/needs-attention.js.map +1 -0
- package/dist/dashboard/public/bridge.html +1272 -0
- package/dist/dashboard/public/index.html +2094 -347
- package/dist/dashboard/public/js/app.js +184 -0
- package/dist/dashboard/public/js/app.js.map +7 -0
- package/dist/dashboard/public/metrics.html +999 -0
- package/dist/dashboard/server.d.ts +14 -1
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +689 -16
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/start.js +1 -1
- package/dist/dashboard/start.js.map +1 -1
- package/dist/dashboard-v2/index.d.ts +10 -0
- package/dist/dashboard-v2/index.d.ts.map +1 -0
- package/dist/dashboard-v2/index.js +54 -0
- package/dist/dashboard-v2/index.js.map +1 -0
- package/dist/dashboard-v2/lib/api.d.ts +95 -0
- package/dist/dashboard-v2/lib/api.d.ts.map +1 -0
- package/dist/dashboard-v2/lib/api.js +270 -0
- package/dist/dashboard-v2/lib/api.js.map +1 -0
- package/dist/dashboard-v2/lib/colors.d.ts +61 -0
- package/dist/dashboard-v2/lib/colors.d.ts.map +1 -0
- package/dist/dashboard-v2/lib/colors.js +198 -0
- package/dist/dashboard-v2/lib/colors.js.map +1 -0
- package/dist/dashboard-v2/lib/hierarchy.d.ts +74 -0
- package/dist/dashboard-v2/lib/hierarchy.d.ts.map +1 -0
- package/dist/dashboard-v2/lib/hierarchy.js +196 -0
- package/dist/dashboard-v2/lib/hierarchy.js.map +1 -0
- package/dist/dashboard-v2/types/index.d.ts +154 -0
- package/dist/dashboard-v2/types/index.d.ts.map +1 -0
- package/dist/dashboard-v2/types/index.js +6 -0
- package/dist/dashboard-v2/types/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/protocol/types.d.ts +15 -1
- package/dist/protocol/types.d.ts.map +1 -1
- package/dist/storage/adapter.d.ts +74 -1
- package/dist/storage/adapter.d.ts.map +1 -1
- package/dist/storage/adapter.js +39 -0
- package/dist/storage/adapter.js.map +1 -1
- package/dist/storage/sqlite-adapter.d.ts +92 -1
- package/dist/storage/sqlite-adapter.d.ts.map +1 -1
- package/dist/storage/sqlite-adapter.js +615 -47
- package/dist/storage/sqlite-adapter.js.map +1 -1
- package/dist/utils/agent-config.d.ts +45 -0
- package/dist/utils/agent-config.d.ts.map +1 -0
- package/dist/utils/agent-config.js +118 -0
- package/dist/utils/agent-config.js.map +1 -0
- package/dist/utils/project-namespace.d.ts.map +1 -1
- package/dist/utils/project-namespace.js +22 -1
- package/dist/utils/project-namespace.js.map +1 -1
- package/dist/wrapper/client.d.ts +30 -3
- package/dist/wrapper/client.d.ts.map +1 -1
- package/dist/wrapper/client.js +85 -9
- package/dist/wrapper/client.js.map +1 -1
- package/dist/wrapper/parser.d.ts +127 -4
- package/dist/wrapper/parser.d.ts.map +1 -1
- package/dist/wrapper/parser.js +622 -86
- package/dist/wrapper/parser.js.map +1 -1
- package/dist/wrapper/tmux-wrapper.d.ts +136 -10
- package/dist/wrapper/tmux-wrapper.d.ts.map +1 -1
- package/dist/wrapper/tmux-wrapper.js +599 -79
- package/dist/wrapper/tmux-wrapper.js.map +1 -1
- package/docs/AGENTS.md +132 -27
- package/docs/ARCHITECTURE_DECISIONS.md +175 -0
- package/docs/CHANGELOG.md +1 -1
- package/docs/COMPETITIVE_ANALYSIS.md +897 -0
- package/docs/DESIGN_BRIDGE_STAFFING.md +878 -0
- package/docs/DESIGN_V2.md +1079 -0
- package/docs/INTEGRATION-GUIDE.md +926 -0
- package/docs/MONETIZATION.md +1679 -0
- package/docs/PROPOSAL-trajectories.md +1582 -0
- package/docs/PROTOCOL.md +3 -3
- package/docs/SCALING_ANALYSIS.md +280 -0
- package/docs/TMUX_IMPLEMENTATION_NOTES.md +9 -9
- package/docs/TMUX_IMPROVEMENTS.md +968 -0
- package/docs/agent-relay-snippet.md +61 -0
- package/docs/competitive-analysis-mcp-agent-mail.md +389 -0
- package/docs/dashboard-v2-plan.md +179 -0
- package/package.json +10 -3
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
# Bridge & Staffing: Multi-Project Agent Orchestration
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document describes the **Bridge** feature - a multi-project orchestration layer that allows a single agent (the **Architect** or **Principal**) to coordinate work across multiple projects, each managed by a **Lead** who can dynamically **staff** worker agents.
|
|
6
|
+
|
|
7
|
+
This builds on the existing agent-relay infrastructure while adding:
|
|
8
|
+
1. Cross-project communication via socket bridging
|
|
9
|
+
2. SE role hierarchy (Architect → Lead → Engineer)
|
|
10
|
+
3. Dynamic agent spawning by Leads
|
|
11
|
+
4. Multi-project dashboard visibility
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Terminology
|
|
16
|
+
|
|
17
|
+
| Term | Description |
|
|
18
|
+
|------|-------------|
|
|
19
|
+
| **Bridge** | CLI command and mode for multi-project orchestration |
|
|
20
|
+
| **Architect** / **Principal** | SE role for the bridging agent (cross-project coordinator) |
|
|
21
|
+
| **Lead** | Project leader (Tech Lead) - one per project, can spawn workers |
|
|
22
|
+
| **Engineer** / **Worker** | Agents spawned by Leads to execute specific tasks |
|
|
23
|
+
| **Spawn** | Action of creating a new worker agent |
|
|
24
|
+
| **Standup** | Morning coordination where Architect assigns work to Leads |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌─────────────────────────────────────┐
|
|
32
|
+
│ ARCHITECT (bridge agent) │
|
|
33
|
+
│ agent-relay bridge --as architect │
|
|
34
|
+
│ │
|
|
35
|
+
│ ┌─────────────────────────────┐ │
|
|
36
|
+
│ │ MultiProjectClient │ │
|
|
37
|
+
│ │ - projectSockets: Map │ │
|
|
38
|
+
│ │ - projectLeads: Map │ │
|
|
39
|
+
│ └─────────────────────────────┘ │
|
|
40
|
+
└──────────────┬──────────────────────┘
|
|
41
|
+
│
|
|
42
|
+
┌───────────────────────┼───────────────────────┐
|
|
43
|
+
│ │ │
|
|
44
|
+
▼ ▼ ▼
|
|
45
|
+
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
|
|
46
|
+
│ auth-service │ │ frontend │ │ api-service │
|
|
47
|
+
│ Project Daemon │ │ Project Daemon │ │ Project Daemon │
|
|
48
|
+
│ │ │ │ │ │
|
|
49
|
+
│ Socket: │ │ Socket: │ │ Socket: │
|
|
50
|
+
│ /tmp/.../auth/ │ │ /tmp/.../fe/ │ │ /tmp/.../api/ │
|
|
51
|
+
└───────┬────────┘ └───────┬────────┘ └───────┬────────┘
|
|
52
|
+
│ │ │
|
|
53
|
+
▼ ▼ ▼
|
|
54
|
+
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
|
|
55
|
+
│ Lead: Alice │ │ Lead: Bob │ │ Lead: Carol │
|
|
56
|
+
│ (can spawn) │ │ (can spawn) │ │ (can spawn) │
|
|
57
|
+
└───────┬────────┘ └───────┬────────┘ └───────┬────────┘
|
|
58
|
+
│ │ │
|
|
59
|
+
┌─────┴─────┐ ┌─────┴─────┐ │
|
|
60
|
+
▼ ▼ ▼ ▼ ▼
|
|
61
|
+
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
|
|
62
|
+
│ Dev1 │ │ QA1 │ │ Dev1 │ │ Rev1 │ │ SRE1 │
|
|
63
|
+
└───────┘ └───────┘ └───────┘ └───────┘ └───────┘
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Key Design Decisions
|
|
67
|
+
|
|
68
|
+
1. **Keep Daemons Project-Local**: Each project maintains its own daemon and socket. The Architect connects to multiple daemons simultaneously. This preserves security isolation.
|
|
69
|
+
|
|
70
|
+
2. **Coordination at Client Layer**: The `MultiProjectClient` handles cross-project routing, not the daemons. Daemons remain simple and unchanged.
|
|
71
|
+
|
|
72
|
+
3. **Leads Own Staffing**: Leads decide how many workers and what types to spawn based on work assigned by the Architect.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## CLI Commands
|
|
77
|
+
|
|
78
|
+
### Design Principle
|
|
79
|
+
|
|
80
|
+
> **Tight surface area. Simple interfaces hiding complexity.**
|
|
81
|
+
|
|
82
|
+
### Start Bridge (Architect)
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Bridge multiple projects - just list the paths
|
|
86
|
+
agent-relay bridge ~/auth ~/frontend ~/api
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
That's it. The system handles:
|
|
90
|
+
- Socket discovery for each project
|
|
91
|
+
- Multi-socket connections
|
|
92
|
+
- Lead auto-detection (first registered agent per project)
|
|
93
|
+
- Cross-project message routing
|
|
94
|
+
|
|
95
|
+
### Start Lead
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Be a lead - just your name
|
|
99
|
+
agent-relay lead Alice
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
That's it. The system handles:
|
|
103
|
+
- Project detection from current directory
|
|
104
|
+
- Spawn capability (automatic for leads)
|
|
105
|
+
- Daemon registration
|
|
106
|
+
- Worker management
|
|
107
|
+
|
|
108
|
+
### Workers (spawned by Leads)
|
|
109
|
+
|
|
110
|
+
Workers are created via relay messages - not CLI commands:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
->relay:spawn Dev1 claude "Build the login page"
|
|
114
|
+
->relay:release Dev1
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### What's Hidden
|
|
118
|
+
|
|
119
|
+
| User types | System handles |
|
|
120
|
+
|------------|----------------|
|
|
121
|
+
| `bridge ~/auth ~/frontend` | Socket discovery, multi-connection, lead detection |
|
|
122
|
+
| `lead Alice` | Spawn capability, project detection, daemon registration |
|
|
123
|
+
| `->relay:spawn Dev1 claude "task"` | Tmux window, agent launch, task injection |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Message Patterns
|
|
128
|
+
|
|
129
|
+
### Cross-Project Addressing
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Architect → specific project lead
|
|
133
|
+
@relay:auth-service:lead Implement OAuth flow by EOD
|
|
134
|
+
|
|
135
|
+
# Architect → all leads
|
|
136
|
+
@relay:*:lead Standup time - report status and blockers
|
|
137
|
+
|
|
138
|
+
# Architect → specific agent in a project
|
|
139
|
+
@relay:frontend:Dev1 Focus on the login form first
|
|
140
|
+
|
|
141
|
+
# Architect → broadcast to entire project
|
|
142
|
+
@relay:api-service:* Code freeze starts in 1 hour
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Intra-Project (existing patterns still work)
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Lead → worker (same project)
|
|
149
|
+
@relay:Dev1 Start on the user endpoint
|
|
150
|
+
|
|
151
|
+
# Worker → Lead
|
|
152
|
+
@relay:lead Task complete, ready for next assignment
|
|
153
|
+
|
|
154
|
+
# Worker → Worker
|
|
155
|
+
@relay:QA1 My PR is ready for testing
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Spawn Messages
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# Lead spawns a worker
|
|
162
|
+
->relay:spawn Dev1 claude "Implement login endpoint"
|
|
163
|
+
|
|
164
|
+
# Lead spawns with specific model
|
|
165
|
+
->relay:spawn SeniorDev claude:opus "Design the auth architecture"
|
|
166
|
+
->relay:spawn QuickFix claude:haiku "Fix typos in error messages"
|
|
167
|
+
|
|
168
|
+
# Lead releases a worker
|
|
169
|
+
->relay:release Dev1
|
|
170
|
+
|
|
171
|
+
# Lead releases all workers
|
|
172
|
+
->relay:release *
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Spawn Protocol
|
|
178
|
+
|
|
179
|
+
### Spawn Message Format
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
->relay:spawn <name> <cli>[:<model>] "<initial_task>"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
| Field | Required | Description |
|
|
186
|
+
|-------|----------|-------------|
|
|
187
|
+
| `name` | Yes | Worker agent name (unique within project) |
|
|
188
|
+
| `cli` | Yes | Agent CLI: `claude`, `codex`, `gemini` |
|
|
189
|
+
| `model` | No | Model variant: `opus`, `sonnet`, `haiku` |
|
|
190
|
+
| `initial_task` | Yes | Task injected into worker on startup |
|
|
191
|
+
|
|
192
|
+
### Spawn Sequence
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
196
|
+
│ Lead │ │ Daemon │ │ Spawner │
|
|
197
|
+
└────┬────┘ └────┬────┘ └────┬────┘
|
|
198
|
+
│ │ │
|
|
199
|
+
│ ->relay:spawn Dev1│ │
|
|
200
|
+
│ claude "task" │ │
|
|
201
|
+
│──────────────────>│ │
|
|
202
|
+
│ │ │
|
|
203
|
+
│ │ SPAWN request │
|
|
204
|
+
│ │──────────────────>│
|
|
205
|
+
│ │ │
|
|
206
|
+
│ │ Create tmux │
|
|
207
|
+
│ │ window "Dev1" │
|
|
208
|
+
│ │ │
|
|
209
|
+
│ │ Launch: │
|
|
210
|
+
│ │ agent-relay │
|
|
211
|
+
│ │ -n Dev1 claude │
|
|
212
|
+
│ │ │
|
|
213
|
+
│ │ Inject task │
|
|
214
|
+
│ │ │
|
|
215
|
+
│ │ SPAWN_ACK │
|
|
216
|
+
│ │<──────────────────│
|
|
217
|
+
│ │ │
|
|
218
|
+
│ SPAWN_COMPLETE │ │
|
|
219
|
+
│ Dev1 ready │ │
|
|
220
|
+
│<──────────────────│ │
|
|
221
|
+
│ │ │
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Spawn Implementation
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
// src/daemon/spawner.ts
|
|
228
|
+
|
|
229
|
+
interface SpawnRequest {
|
|
230
|
+
name: string;
|
|
231
|
+
cli: string;
|
|
232
|
+
model?: string;
|
|
233
|
+
task: string;
|
|
234
|
+
requestedBy: string; // Lead's name
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export class AgentSpawner {
|
|
238
|
+
constructor(private projectRoot: string) {}
|
|
239
|
+
|
|
240
|
+
async spawn(request: SpawnRequest): Promise<SpawnResult> {
|
|
241
|
+
const { name, cli, model, task, requestedBy } = request;
|
|
242
|
+
|
|
243
|
+
// 1. Create tmux window
|
|
244
|
+
const sessionName = `relay-${name}`;
|
|
245
|
+
await exec(`tmux new-window -t relay -n ${name}`);
|
|
246
|
+
|
|
247
|
+
// 2. Build command
|
|
248
|
+
let cmd = `agent-relay -n ${name}`;
|
|
249
|
+
if (model) {
|
|
250
|
+
cmd += ` ${cli}:${model}`;
|
|
251
|
+
} else {
|
|
252
|
+
cmd += ` ${cli}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 3. Launch agent
|
|
256
|
+
await exec(`tmux send-keys -t relay:${name} '${cmd}' Enter`);
|
|
257
|
+
|
|
258
|
+
// 4. Wait for agent to register
|
|
259
|
+
await this.waitForAgent(name, 30_000);
|
|
260
|
+
|
|
261
|
+
// 5. Inject initial task
|
|
262
|
+
await exec(`tmux send-keys -t relay:${name} '${escapeTask(task)}' Enter`);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
success: true,
|
|
266
|
+
name,
|
|
267
|
+
window: `relay:${name}`,
|
|
268
|
+
spawnedBy: requestedBy,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async release(name: string): Promise<void> {
|
|
273
|
+
// Send graceful shutdown
|
|
274
|
+
await exec(`tmux send-keys -t relay:${name} '/exit' Enter`);
|
|
275
|
+
// Kill window after delay
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
exec(`tmux kill-window -t relay:${name}`).catch(() => {});
|
|
278
|
+
}, 5000);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Daily Workflow: Standup Protocol
|
|
286
|
+
|
|
287
|
+
### Morning Standup Sequence
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
┌────────────────────────────────────────────────────────────────────────┐
|
|
291
|
+
│ MORNING STANDUP │
|
|
292
|
+
├────────────────────────────────────────────────────────────────────────┤
|
|
293
|
+
│ │
|
|
294
|
+
│ 1. ARCHITECT INITIATES │
|
|
295
|
+
│ @relay:*:lead Standup - report status, blockers, and staffing needs│
|
|
296
|
+
│ │
|
|
297
|
+
│ 2. LEADS REPORT │
|
|
298
|
+
│ Alice (auth): STATUS: OAuth 60% complete │
|
|
299
|
+
│ BLOCKED: Need API spec from api-service │
|
|
300
|
+
│ STAFF: Need 2 devs + 1 QA once unblocked │
|
|
301
|
+
│ │
|
|
302
|
+
│ Bob (frontend): STATUS: Dashboard ready for review │
|
|
303
|
+
│ BLOCKED: None │
|
|
304
|
+
│ STAFF: Need 1 reviewer │
|
|
305
|
+
│ │
|
|
306
|
+
│ Carol (api): STATUS: Spec complete │
|
|
307
|
+
│ BLOCKED: None │
|
|
308
|
+
│ STAFF: Ready for 3 parallel endpoint devs │
|
|
309
|
+
│ │
|
|
310
|
+
│ 3. ARCHITECT COORDINATES CROSS-PROJECT │
|
|
311
|
+
│ @relay:api-service:lead Alice needs your API spec - priority │
|
|
312
|
+
│ @relay:auth-service:lead Unblocked - Carol sending spec now │
|
|
313
|
+
│ │
|
|
314
|
+
│ 4. ARCHITECT ASSIGNS WORK │
|
|
315
|
+
│ @relay:auth-service:lead APPROVED: Staff 2 devs + 1 QA. Pri: HIGH │
|
|
316
|
+
│ @relay:frontend:lead APPROVED: Staff 1 reviewer for dashboard │
|
|
317
|
+
│ @relay:api-service:lead APPROVED: Staff 3 devs for endpoints │
|
|
318
|
+
│ │
|
|
319
|
+
│ 5. LEADS STAFF THEIR TEAMS │
|
|
320
|
+
│ Alice: ->relay:spawn Dev1 claude "Implement OAuth token flow" │
|
|
321
|
+
│ ->relay:spawn Dev2 claude "Implement OAuth callback" │
|
|
322
|
+
│ ->relay:spawn QA1 claude "Write OAuth integration tests" │
|
|
323
|
+
│ │
|
|
324
|
+
│ Bob: ->relay:spawn Reviewer claude "Review dashboard PR #42" │
|
|
325
|
+
│ │
|
|
326
|
+
│ Carol: ->relay:spawn Dev1 claude "POST /users endpoint" │
|
|
327
|
+
│ ->relay:spawn Dev2 claude "GET /users/:id endpoint" │
|
|
328
|
+
│ ->relay:spawn Dev3 claude "DELETE /users/:id endpoint" │
|
|
329
|
+
│ │
|
|
330
|
+
└────────────────────────────────────────────────────────────────────────┘
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Staffing Strategy by Work Type
|
|
334
|
+
|
|
335
|
+
| Work Type | Recommended Agent | Rationale |
|
|
336
|
+
|-----------|-------------------|-----------|
|
|
337
|
+
| Architecture/Design | `claude:opus` | Complex reasoning needed |
|
|
338
|
+
| Feature Implementation | `claude:sonnet` | Balance speed/quality |
|
|
339
|
+
| Code Review | `claude:sonnet` | Good judgment required |
|
|
340
|
+
| Testing/QA | `claude:sonnet` | Thoroughness matters |
|
|
341
|
+
| Bug Fixes | `claude:haiku` | Fast, focused |
|
|
342
|
+
| Documentation | `claude:haiku` | Straightforward |
|
|
343
|
+
| Refactoring | `claude:sonnet` | Context-aware changes |
|
|
344
|
+
|
|
345
|
+
### End of Day Protocol
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
# Architect requests EOD status
|
|
349
|
+
@relay:*:lead EOD status report
|
|
350
|
+
|
|
351
|
+
# Leads report and release workers
|
|
352
|
+
@relay:architect STATUS: OAuth complete, PR #123 ready
|
|
353
|
+
RELEASING: Dev1, Dev2, QA1
|
|
354
|
+
->relay:release Dev1
|
|
355
|
+
->relay:release Dev2
|
|
356
|
+
->relay:release QA1
|
|
357
|
+
|
|
358
|
+
# Architect summarizes for dashboard/records
|
|
359
|
+
@relay:*:lead Good work today. Tomorrow: auth integration testing
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Multi-Project Dashboard
|
|
365
|
+
|
|
366
|
+
### Dashboard URL
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
http://localhost:3888/bridge?projects=auth-service,frontend,api-service
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Dashboard Layout
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
376
|
+
│ AGENT RELAY - BRIDGE DASHBOARD │
|
|
377
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
378
|
+
│ Orchestrator: Architect (Principal) [Refresh] │
|
|
379
|
+
├───────────────┬──────────────┬──────────────┬──────────────────────────┤
|
|
380
|
+
│ PROJECT │ LEAD │ TEAM │ STATUS │
|
|
381
|
+
├───────────────┼──────────────┼──────────────┼──────────────────────────┤
|
|
382
|
+
│ auth-service │ Alice ● │ 3 workers │ ✓ OAuth implementation │
|
|
383
|
+
│ │ │ Dev1 ● │ │
|
|
384
|
+
│ │ │ Dev2 ● │ │
|
|
385
|
+
│ │ │ QA1 ○ │ │
|
|
386
|
+
├───────────────┼──────────────┼──────────────┼──────────────────────────┤
|
|
387
|
+
│ frontend │ Bob ● │ 1 worker │ ⚠ Blocked on API spec │
|
|
388
|
+
│ │ │ Reviewer ● │ │
|
|
389
|
+
├───────────────┼──────────────┼──────────────┼──────────────────────────┤
|
|
390
|
+
│ api-service │ Carol ● │ 3 workers │ ✓ Parallel endpoints │
|
|
391
|
+
│ │ │ Dev1 ● │ │
|
|
392
|
+
│ │ │ Dev2 ● │ │
|
|
393
|
+
│ │ │ Dev3 ● │ │
|
|
394
|
+
└───────────────┴──────────────┴──────────────┴──────────────────────────┘
|
|
395
|
+
|
|
396
|
+
│ Cross-Project Message Flow [Filter: All] │
|
|
397
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
398
|
+
│ 09:00 Architect → *:lead: Standup time │
|
|
399
|
+
│ 09:01 auth:Alice → Architect: STATUS: OAuth 60%... │
|
|
400
|
+
│ 09:01 frontend:Bob → Architect: STATUS: Dashboard ready... │
|
|
401
|
+
│ 09:02 api:Carol → Architect: STATUS: Spec complete... │
|
|
402
|
+
│ 09:05 Architect → api:lead: Send spec to Alice │
|
|
403
|
+
│ 09:06 api:Carol → auth:Alice: Here's the spec... │
|
|
404
|
+
│ 09:10 auth:Alice: ->relay:spawn Dev1 claude "OAuth token..." │
|
|
405
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
406
|
+
|
|
407
|
+
│ [Click project to drill down] [Click agent to attach] │
|
|
408
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Drill-Down View (Single Project)
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
415
|
+
│ auth-service [← Back to Overview] │
|
|
416
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
417
|
+
│ Lead: Alice │
|
|
418
|
+
│ Status: Active - OAuth implementation │
|
|
419
|
+
│ Workers: 3/3 active │
|
|
420
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
421
|
+
│ AGENT │ STATUS │ TASK │ MESSAGES │
|
|
422
|
+
├──────────────┼──────────┼─────────────────────────────┼─────────────────┤
|
|
423
|
+
│ Alice (Lead) │ ● active │ Coordinating OAuth impl │ 12↑ 8↓ │
|
|
424
|
+
│ Dev1 │ ● active │ OAuth token flow │ 5↑ 14↓ │
|
|
425
|
+
│ Dev2 │ ● typing │ OAuth callback handler │ 3↑ 6↓ │
|
|
426
|
+
│ QA1 │ ○ idle │ Waiting for impl complete │ 1↑ 2↓ │
|
|
427
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
428
|
+
│ Project Messages │
|
|
429
|
+
├─────────────────────────────────────────────────────────────────────────┤
|
|
430
|
+
│ 09:15 Alice → Dev1: Start on token flow │
|
|
431
|
+
│ 09:16 Alice → Dev2: Handle the callback endpoint │
|
|
432
|
+
│ 09:30 Dev1 → Alice: Token generation done, testing... │
|
|
433
|
+
│ 09:45 Dev1 → QA1: Ready for your test suite │
|
|
434
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Implementation Plan
|
|
440
|
+
|
|
441
|
+
### Phase 1: Multi-Socket Client
|
|
442
|
+
|
|
443
|
+
**File: `src/wrapper/multi-project-client.ts`**
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
export class MultiProjectClient {
|
|
447
|
+
private sockets: Map<string, net.Socket> = new Map();
|
|
448
|
+
private projectLeads: Map<string, string> = new Map();
|
|
449
|
+
|
|
450
|
+
constructor(private projects: ProjectConfig[]) {}
|
|
451
|
+
|
|
452
|
+
async connect(): Promise<void> {
|
|
453
|
+
for (const project of this.projects) {
|
|
454
|
+
const socket = await this.connectToProject(project);
|
|
455
|
+
this.sockets.set(project.id, socket);
|
|
456
|
+
if (project.lead) {
|
|
457
|
+
this.projectLeads.set(project.id, project.lead);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async send(target: string, message: string): Promise<void> {
|
|
463
|
+
// Parse target: "project:agent" or "project:*" or "*:lead"
|
|
464
|
+
const { projectId, agentName } = this.parseTarget(target);
|
|
465
|
+
|
|
466
|
+
if (projectId === '*') {
|
|
467
|
+
// Broadcast to all projects
|
|
468
|
+
for (const [pid, socket] of this.sockets) {
|
|
469
|
+
await this.sendToSocket(socket, agentName, message);
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
const socket = this.sockets.get(projectId);
|
|
473
|
+
if (socket) {
|
|
474
|
+
await this.sendToSocket(socket, agentName, message);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private parseTarget(target: string): { projectId: string; agentName: string } {
|
|
480
|
+
const [projectId, agentName] = target.split(':');
|
|
481
|
+
return {
|
|
482
|
+
projectId: projectId || '*',
|
|
483
|
+
agentName: agentName === 'lead'
|
|
484
|
+
? this.projectLeads.get(projectId) || 'lead'
|
|
485
|
+
: agentName,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Phase 2: Extended Parser
|
|
492
|
+
|
|
493
|
+
**File: `src/wrapper/parser.ts`** (modifications)
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
// Extended pattern for cross-project messaging
|
|
497
|
+
const CROSS_PROJECT_PATTERN = /^@relay:([a-zA-Z0-9_-]+):([a-zA-Z0-9_*]+)\s+(.+)$/;
|
|
498
|
+
const SPAWN_PATTERN = /^->relay:spawn\s+(\S+)\s+(\S+)\s+"([^"]+)"$/;
|
|
499
|
+
const RELEASE_PATTERN = /^->relay:release\s+(\S+)$/;
|
|
500
|
+
|
|
501
|
+
export function parseRelayCommand(line: string): RelayCommand | null {
|
|
502
|
+
// Check for spawn command
|
|
503
|
+
const spawnMatch = line.match(SPAWN_PATTERN);
|
|
504
|
+
if (spawnMatch) {
|
|
505
|
+
return {
|
|
506
|
+
type: 'spawn',
|
|
507
|
+
name: spawnMatch[1],
|
|
508
|
+
cli: spawnMatch[2],
|
|
509
|
+
task: spawnMatch[3],
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Check for release command
|
|
514
|
+
const releaseMatch = line.match(RELEASE_PATTERN);
|
|
515
|
+
if (releaseMatch) {
|
|
516
|
+
return {
|
|
517
|
+
type: 'release',
|
|
518
|
+
name: releaseMatch[1],
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Check for cross-project message
|
|
523
|
+
const crossMatch = line.match(CROSS_PROJECT_PATTERN);
|
|
524
|
+
if (crossMatch) {
|
|
525
|
+
return {
|
|
526
|
+
type: 'message',
|
|
527
|
+
project: crossMatch[1],
|
|
528
|
+
target: crossMatch[2],
|
|
529
|
+
body: crossMatch[3],
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Fall back to existing single-project pattern
|
|
534
|
+
// ...existing code...
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Phase 3: Bridge CLI Command
|
|
539
|
+
|
|
540
|
+
**File: `src/cli/bridge.ts`**
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
import { Command } from 'commander';
|
|
544
|
+
import { MultiProjectClient } from '../wrapper/multi-project-client.js';
|
|
545
|
+
|
|
546
|
+
export function bridgeCommand(program: Command): void {
|
|
547
|
+
program
|
|
548
|
+
.command('bridge')
|
|
549
|
+
.description('Bridge multiple projects as orchestrator')
|
|
550
|
+
.argument('<projects...>', 'Project paths to bridge')
|
|
551
|
+
.action(async (projectPaths: string[]) => {
|
|
552
|
+
// Resolve and validate paths
|
|
553
|
+
const projects = await resolveProjects(projectPaths);
|
|
554
|
+
|
|
555
|
+
// Connect to all project sockets
|
|
556
|
+
const client = new MultiProjectClient(projects);
|
|
557
|
+
await client.connect();
|
|
558
|
+
|
|
559
|
+
// Auto-detect leads (first registered agent per project)
|
|
560
|
+
await client.discoverLeads();
|
|
561
|
+
|
|
562
|
+
// Start wrapper with multi-project client
|
|
563
|
+
const wrapper = new TmuxWrapper({
|
|
564
|
+
agentName: 'Architect',
|
|
565
|
+
client,
|
|
566
|
+
isOrchestrator: true,
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
await wrapper.start();
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**File: `src/cli/lead.ts`**
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
export function leadCommand(program: Command): void {
|
|
578
|
+
program
|
|
579
|
+
.command('lead')
|
|
580
|
+
.description('Start as project lead with spawn capability')
|
|
581
|
+
.argument('<name>', 'Your name')
|
|
582
|
+
.action(async (name: string) => {
|
|
583
|
+
// Detect project from cwd
|
|
584
|
+
const project = await detectProject(process.cwd());
|
|
585
|
+
|
|
586
|
+
// Start as lead with spawn capability
|
|
587
|
+
const wrapper = new TmuxWrapper({
|
|
588
|
+
agentName: name,
|
|
589
|
+
projectRoot: project.root,
|
|
590
|
+
isLead: true,
|
|
591
|
+
canSpawn: true,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
await wrapper.start();
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Phase 4: Spawner Service
|
|
600
|
+
|
|
601
|
+
**File: `src/daemon/spawner.ts`**
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
export class AgentSpawner {
|
|
605
|
+
private activeWorkers: Map<string, WorkerInfo> = new Map();
|
|
606
|
+
|
|
607
|
+
constructor(
|
|
608
|
+
private projectRoot: string,
|
|
609
|
+
private tmuxSession: string,
|
|
610
|
+
) {}
|
|
611
|
+
|
|
612
|
+
async spawn(request: SpawnRequest): Promise<SpawnResult> {
|
|
613
|
+
const { name, cli, model, task, requestedBy } = request;
|
|
614
|
+
|
|
615
|
+
// Validate
|
|
616
|
+
if (this.activeWorkers.has(name)) {
|
|
617
|
+
throw new Error(`Worker ${name} already exists`);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Create tmux window
|
|
621
|
+
const windowName = `${name}`;
|
|
622
|
+
await execAsync(`tmux new-window -t ${this.tmuxSession} -n ${windowName}`);
|
|
623
|
+
|
|
624
|
+
// Build and launch command
|
|
625
|
+
const cliCmd = model ? `${cli}:${model}` : cli;
|
|
626
|
+
const cmd = `agent-relay -n ${name} ${cliCmd}`;
|
|
627
|
+
await execAsync(`tmux send-keys -t ${this.tmuxSession}:${windowName} '${cmd}' Enter`);
|
|
628
|
+
|
|
629
|
+
// Wait for registration
|
|
630
|
+
const registered = await this.waitForAgent(name, 30_000);
|
|
631
|
+
if (!registered) {
|
|
632
|
+
await this.cleanup(windowName);
|
|
633
|
+
throw new Error(`Worker ${name} failed to register`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Inject initial task
|
|
637
|
+
await sleep(2000); // Wait for agent to be ready
|
|
638
|
+
await execAsync(
|
|
639
|
+
`tmux send-keys -t ${this.tmuxSession}:${windowName} ${escapeForTmux(task)} Enter`
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
// Track worker
|
|
643
|
+
this.activeWorkers.set(name, {
|
|
644
|
+
name,
|
|
645
|
+
cli: cliCmd,
|
|
646
|
+
task,
|
|
647
|
+
spawnedBy: requestedBy,
|
|
648
|
+
spawnedAt: Date.now(),
|
|
649
|
+
window: `${this.tmuxSession}:${windowName}`,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
return { success: true, name, window: windowName };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async release(name: string): Promise<void> {
|
|
656
|
+
const worker = this.activeWorkers.get(name);
|
|
657
|
+
if (!worker) return;
|
|
658
|
+
|
|
659
|
+
// Send exit command
|
|
660
|
+
try {
|
|
661
|
+
await execAsync(`tmux send-keys -t ${worker.window} '/exit' Enter`);
|
|
662
|
+
await sleep(3000);
|
|
663
|
+
} catch {
|
|
664
|
+
// Window may already be gone
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Kill window
|
|
668
|
+
try {
|
|
669
|
+
await execAsync(`tmux kill-window -t ${worker.window}`);
|
|
670
|
+
} catch {
|
|
671
|
+
// Ignore
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
this.activeWorkers.delete(name);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
async releaseAll(): Promise<void> {
|
|
678
|
+
for (const name of this.activeWorkers.keys()) {
|
|
679
|
+
await this.release(name);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
getActiveWorkers(): WorkerInfo[] {
|
|
684
|
+
return Array.from(this.activeWorkers.values());
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Phase 5: Dashboard Enhancement
|
|
690
|
+
|
|
691
|
+
**File: `src/dashboard/bridge-view.ts`**
|
|
692
|
+
|
|
693
|
+
```typescript
|
|
694
|
+
export function createBridgeRouter(projects: ProjectConnection[]): Router {
|
|
695
|
+
const router = Router();
|
|
696
|
+
|
|
697
|
+
router.get('/bridge', async (req, res) => {
|
|
698
|
+
const projectData = await Promise.all(
|
|
699
|
+
projects.map(async (project) => ({
|
|
700
|
+
id: project.id,
|
|
701
|
+
name: project.name,
|
|
702
|
+
lead: await project.getLead(),
|
|
703
|
+
agents: await project.getAgents(),
|
|
704
|
+
status: await project.getStatus(),
|
|
705
|
+
recentMessages: await project.getRecentMessages(10),
|
|
706
|
+
}))
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
res.render('bridge', {
|
|
710
|
+
projects: projectData,
|
|
711
|
+
orchestrator: req.query.as || 'Architect',
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
router.get('/bridge/:projectId', async (req, res) => {
|
|
716
|
+
const project = projects.find(p => p.id === req.params.projectId);
|
|
717
|
+
if (!project) {
|
|
718
|
+
return res.status(404).send('Project not found');
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const data = {
|
|
722
|
+
...project,
|
|
723
|
+
agents: await project.getAgents(),
|
|
724
|
+
messages: await project.getMessages(50),
|
|
725
|
+
workers: await project.getWorkers(),
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
res.render('bridge-project', data);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
return router;
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## Protocol Extensions
|
|
738
|
+
|
|
739
|
+
### New Message Types
|
|
740
|
+
|
|
741
|
+
```typescript
|
|
742
|
+
// src/protocol/types.ts additions
|
|
743
|
+
|
|
744
|
+
export interface SpawnPayload {
|
|
745
|
+
name: string;
|
|
746
|
+
cli: string;
|
|
747
|
+
model?: string;
|
|
748
|
+
task: string;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export interface ReleasePayload {
|
|
752
|
+
name: string;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
export interface SpawnResultPayload {
|
|
756
|
+
success: boolean;
|
|
757
|
+
name: string;
|
|
758
|
+
window?: string;
|
|
759
|
+
error?: string;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Extended envelope for cross-project
|
|
763
|
+
export interface CrossProjectEnvelope<T> extends Envelope<T> {
|
|
764
|
+
sourceProject?: string;
|
|
765
|
+
targetProject?: string;
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
### Wire Format Examples
|
|
770
|
+
|
|
771
|
+
```json
|
|
772
|
+
// Spawn request
|
|
773
|
+
{
|
|
774
|
+
"v": 1,
|
|
775
|
+
"type": "SPAWN",
|
|
776
|
+
"id": "spawn-123",
|
|
777
|
+
"payload": {
|
|
778
|
+
"name": "Dev1",
|
|
779
|
+
"cli": "claude",
|
|
780
|
+
"model": "sonnet",
|
|
781
|
+
"task": "Implement the login endpoint"
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Cross-project message
|
|
786
|
+
{
|
|
787
|
+
"v": 1,
|
|
788
|
+
"type": "SEND",
|
|
789
|
+
"id": "msg-456",
|
|
790
|
+
"to": "Alice",
|
|
791
|
+
"sourceProject": "api-service",
|
|
792
|
+
"targetProject": "auth-service",
|
|
793
|
+
"payload": {
|
|
794
|
+
"kind": "message",
|
|
795
|
+
"body": "Here's the API spec you requested"
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
---
|
|
801
|
+
|
|
802
|
+
## Security Considerations
|
|
803
|
+
|
|
804
|
+
1. **Socket Permissions**: Each project daemon maintains 0o600 permissions. The Architect must have filesystem access to all project socket paths.
|
|
805
|
+
|
|
806
|
+
2. **Spawn Validation**: Only agents with `--can-spawn` flag can spawn workers. Daemon validates the request source.
|
|
807
|
+
|
|
808
|
+
3. **Cross-Project Isolation**: Messages are routed through the Architect's client, not directly between daemons. This provides an audit point.
|
|
809
|
+
|
|
810
|
+
4. **Worker Limits**: Consider adding `--max-workers` flag to prevent runaway spawning.
|
|
811
|
+
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
## Configuration
|
|
815
|
+
|
|
816
|
+
### Project Config File (optional)
|
|
817
|
+
|
|
818
|
+
```json
|
|
819
|
+
// ~/auth-service/.agent-relay/config.json
|
|
820
|
+
{
|
|
821
|
+
"projectName": "auth-service",
|
|
822
|
+
"defaultLead": "Alice",
|
|
823
|
+
"maxWorkers": 5,
|
|
824
|
+
"allowedClis": ["claude", "codex"],
|
|
825
|
+
"spawnDefaults": {
|
|
826
|
+
"model": "sonnet",
|
|
827
|
+
"timeout": 3600
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
### Bridge Config File (optional)
|
|
833
|
+
|
|
834
|
+
```json
|
|
835
|
+
// ~/.config/agent-relay/bridge.json
|
|
836
|
+
{
|
|
837
|
+
"defaultRole": "architect",
|
|
838
|
+
"projects": {
|
|
839
|
+
"auth-service": {
|
|
840
|
+
"path": "~/auth-service",
|
|
841
|
+
"lead": "Alice"
|
|
842
|
+
},
|
|
843
|
+
"frontend": {
|
|
844
|
+
"path": "~/frontend",
|
|
845
|
+
"lead": "Bob"
|
|
846
|
+
}
|
|
847
|
+
},
|
|
848
|
+
"standup": {
|
|
849
|
+
"autoStart": true,
|
|
850
|
+
"time": "09:00"
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
---
|
|
856
|
+
|
|
857
|
+
## Future Enhancements
|
|
858
|
+
|
|
859
|
+
1. **Scheduled Standups**: Auto-trigger standup at configured time
|
|
860
|
+
2. **Worker Health Checks**: Monitor spawned workers for crashes
|
|
861
|
+
3. **Load Balancing**: Auto-distribute work across projects
|
|
862
|
+
4. **Metrics/Analytics**: Track productivity across projects
|
|
863
|
+
5. **Notification Integration**: Slack/Discord alerts for blockers
|
|
864
|
+
6. **Replay Mode**: Re-run standups from message history
|
|
865
|
+
|
|
866
|
+
---
|
|
867
|
+
|
|
868
|
+
## Summary
|
|
869
|
+
|
|
870
|
+
The Bridge & Staffing feature enables:
|
|
871
|
+
|
|
872
|
+
- **Architect/Principal** as cross-project orchestrator via `agent-relay bridge`
|
|
873
|
+
- **Leads** who can dynamically spawn worker agents via `->relay:spawn`
|
|
874
|
+
- **Standup protocol** for daily work coordination
|
|
875
|
+
- **Multi-project dashboard** for visibility across all projects
|
|
876
|
+
- **SE vernacular** with familiar role hierarchy
|
|
877
|
+
|
|
878
|
+
This maintains the simplicity of `@relay:` patterns while enabling sophisticated multi-project orchestration.
|