bharatcode 0.1.0__py3-none-any.whl
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.
- bharatcode/__init__.py +13 -0
- bharatcode/agent.py +2088 -0
- bharatcode/commands.py +1072 -0
- bharatcode/config.py +93 -0
- bharatcode/coordinator.py +670 -0
- bharatcode/cost.py +77 -0
- bharatcode/diff.py +113 -0
- bharatcode/hooks.py +75 -0
- bharatcode/index.py +155 -0
- bharatcode/main.py +846 -0
- bharatcode/memory.py +286 -0
- bharatcode/permissions.py +99 -0
- bharatcode/project.py +179 -0
- bharatcode/session_storage.py +108 -0
- bharatcode/skills.py +1746 -0
- bharatcode/subagent.py +363 -0
- bharatcode/tools.py +1021 -0
- bharatcode/ui.py +72 -0
- bharatcode-0.1.0.dist-info/METADATA +150 -0
- bharatcode-0.1.0.dist-info/RECORD +23 -0
- bharatcode-0.1.0.dist-info/WHEEL +5 -0
- bharatcode-0.1.0.dist-info/entry_points.txt +3 -0
- bharatcode-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sylithe Code Coordinator Mode — multi-worker orchestration engine.
|
|
3
|
+
|
|
4
|
+
The coordinator is the main agent in a special mode. It spawns async workers,
|
|
5
|
+
receives <task-notification> XML when they finish, synthesizes results, and
|
|
6
|
+
directs the next phase of work — all without blocking.
|
|
7
|
+
|
|
8
|
+
Internal API:
|
|
9
|
+
WorkerPool.spawn() → fires a background thread, returns worker_id instantly
|
|
10
|
+
WorkerPool.send_message() → continues an existing worker (reuses its context)
|
|
11
|
+
WorkerPool.stop() → sends abort signal to a running worker
|
|
12
|
+
WorkerPool.drain() → returns pending notifications as history messages
|
|
13
|
+
|
|
14
|
+
Notification flow:
|
|
15
|
+
worker thread completes
|
|
16
|
+
→ _notify() builds <task-notification> XML
|
|
17
|
+
→ pushed to notification_queue (thread-safe Queue)
|
|
18
|
+
coordinator next turn
|
|
19
|
+
→ drain_notifications() called before API call
|
|
20
|
+
→ notifications injected as role=user messages into history
|
|
21
|
+
→ model sees completions as if users sent them
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import time
|
|
25
|
+
import uuid
|
|
26
|
+
import threading
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from typing import Optional
|
|
29
|
+
from queue import Queue, Empty
|
|
30
|
+
|
|
31
|
+
from rich.console import Console
|
|
32
|
+
from rich.panel import Panel
|
|
33
|
+
from rich.table import Table
|
|
34
|
+
from rich import box
|
|
35
|
+
|
|
36
|
+
console = Console()
|
|
37
|
+
|
|
38
|
+
# ── Worker States ──────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
PENDING = "pending"
|
|
41
|
+
RUNNING = "running"
|
|
42
|
+
DONE = "completed"
|
|
43
|
+
FAILED = "failed"
|
|
44
|
+
STOPPED = "stopped"
|
|
45
|
+
|
|
46
|
+
_STATUS_ICON = {
|
|
47
|
+
PENDING: "🔵",
|
|
48
|
+
RUNNING: "⏳",
|
|
49
|
+
DONE: "✅",
|
|
50
|
+
FAILED: "❌",
|
|
51
|
+
STOPPED: "🛑",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# ── Worker ─────────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class Worker:
|
|
58
|
+
worker_id: str
|
|
59
|
+
description: str
|
|
60
|
+
agent_type: str
|
|
61
|
+
task: str
|
|
62
|
+
status: str = PENDING
|
|
63
|
+
result: str = ""
|
|
64
|
+
error: str = ""
|
|
65
|
+
started_at: float = field(default_factory=time.time)
|
|
66
|
+
duration_ms: int = 0
|
|
67
|
+
tool_uses: int = 0
|
|
68
|
+
history: list = field(default_factory=list)
|
|
69
|
+
# Preserved so send_message continuations run with identical context
|
|
70
|
+
_project_path: str = field(default=".", repr=False)
|
|
71
|
+
_system: str = field(default="", repr=False)
|
|
72
|
+
_cache: dict = field(default_factory=dict, repr=False)
|
|
73
|
+
_abort: threading.Event = field(default_factory=threading.Event, repr=False)
|
|
74
|
+
_thread: Optional[threading.Thread] = field(default=None, repr=False)
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def elapsed_s(self) -> float:
|
|
78
|
+
return (time.time() - self.started_at)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def is_alive(self) -> bool:
|
|
82
|
+
return self._thread is not None and self._thread.is_alive()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ── Worker Pool ─────────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
class WorkerPool:
|
|
88
|
+
"""
|
|
89
|
+
Session-scoped pool that manages all coordinator workers.
|
|
90
|
+
|
|
91
|
+
Thread-safety:
|
|
92
|
+
_workers dict is protected by _lock.
|
|
93
|
+
notification_queue is a stdlib Queue (already thread-safe).
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self):
|
|
97
|
+
self._workers: dict[str, Worker] = {}
|
|
98
|
+
self._lock: threading.Lock = threading.Lock()
|
|
99
|
+
self.notification_queue: Queue = Queue()
|
|
100
|
+
self._session_start: float = time.time()
|
|
101
|
+
|
|
102
|
+
# ── Internal API ──────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
def spawn(
|
|
105
|
+
self,
|
|
106
|
+
task: str,
|
|
107
|
+
agent_type: str = "general",
|
|
108
|
+
description: str = "",
|
|
109
|
+
project_path: str = ".",
|
|
110
|
+
system: str = None,
|
|
111
|
+
file_cache: dict = None,
|
|
112
|
+
) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Spawn a worker in a background thread.
|
|
115
|
+
Returns worker_id immediately — does NOT block.
|
|
116
|
+
|
|
117
|
+
The worker runs run_agent() with its own isolated history and a
|
|
118
|
+
copy of the coordinator's file_cache (read-only sharing — writes
|
|
119
|
+
go to the worker's own copy to prevent cache corruption).
|
|
120
|
+
"""
|
|
121
|
+
from .subagent import AGENT_TYPES
|
|
122
|
+
from .agent import run_agent, _build_system
|
|
123
|
+
|
|
124
|
+
worker_id = f"w-{uuid.uuid4().hex[:6]}"
|
|
125
|
+
info = AGENT_TYPES.get(agent_type, AGENT_TYPES["general"])
|
|
126
|
+
allowed = info["allowed_tools"]
|
|
127
|
+
base_system = system or _build_system(project_path)
|
|
128
|
+
agent_sys = base_system + info["system_suffix"] + _WORKER_INSTRUCTIONS
|
|
129
|
+
|
|
130
|
+
# Workers share a read-only COPY of the coordinator's file_cache.
|
|
131
|
+
# This means they benefit from already-read files without polluting
|
|
132
|
+
# the coordinator's cache with their own writes.
|
|
133
|
+
from .agent import _cache_copy
|
|
134
|
+
worker_cache = _cache_copy(file_cache)
|
|
135
|
+
|
|
136
|
+
worker = Worker(
|
|
137
|
+
worker_id=worker_id,
|
|
138
|
+
description=description or task[:55],
|
|
139
|
+
agent_type=agent_type,
|
|
140
|
+
task=task,
|
|
141
|
+
_project_path=project_path,
|
|
142
|
+
_system=agent_sys,
|
|
143
|
+
_cache=worker_cache,
|
|
144
|
+
)
|
|
145
|
+
worker.status = RUNNING
|
|
146
|
+
|
|
147
|
+
with self._lock:
|
|
148
|
+
self._workers[worker_id] = worker
|
|
149
|
+
|
|
150
|
+
_banner_spawn(worker_id, info, worker.description)
|
|
151
|
+
|
|
152
|
+
def _run():
|
|
153
|
+
t0 = time.time()
|
|
154
|
+
try:
|
|
155
|
+
output = run_agent(
|
|
156
|
+
task=task,
|
|
157
|
+
project_path=project_path,
|
|
158
|
+
auto_approve=True,
|
|
159
|
+
history=worker.history,
|
|
160
|
+
system_content=agent_sys,
|
|
161
|
+
file_cache=worker_cache,
|
|
162
|
+
allowed_tools=allowed,
|
|
163
|
+
silent=True, # no Live display — parallel threads share one console
|
|
164
|
+
) or ""
|
|
165
|
+
|
|
166
|
+
if worker._abort.is_set():
|
|
167
|
+
worker.status = STOPPED
|
|
168
|
+
self._push_notification(worker)
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
worker.status = DONE
|
|
172
|
+
worker.result = output
|
|
173
|
+
worker.tool_uses = sum(
|
|
174
|
+
1 for m in worker.history if m.get("role") == "tool"
|
|
175
|
+
)
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
worker.status = FAILED
|
|
178
|
+
worker.error = str(exc)
|
|
179
|
+
finally:
|
|
180
|
+
worker.duration_ms = int((time.time() - t0) * 1000)
|
|
181
|
+
|
|
182
|
+
self._push_notification(worker)
|
|
183
|
+
|
|
184
|
+
t = threading.Thread(target=_run, daemon=True, name=f"bc-worker-{worker_id}")
|
|
185
|
+
worker._thread = t
|
|
186
|
+
t.start()
|
|
187
|
+
return worker_id
|
|
188
|
+
|
|
189
|
+
def send_message(self, worker_id: str, message: str) -> str:
|
|
190
|
+
"""
|
|
191
|
+
Continue an existing worker with a new instruction.
|
|
192
|
+
|
|
193
|
+
Two cases:
|
|
194
|
+
RUNNING → append user message to its live history. The worker's
|
|
195
|
+
run_agent() loop picks it up on the next iteration.
|
|
196
|
+
DONE/FAILED/STOPPED → re-start the worker with accumulated history
|
|
197
|
+
so it keeps all the context it built up previously.
|
|
198
|
+
|
|
199
|
+
This is the coordinator's primary tool for directing work without
|
|
200
|
+
losing a worker's hard-won context (files it read, code it wrote, etc.)
|
|
201
|
+
"""
|
|
202
|
+
from .subagent import AGENT_TYPES
|
|
203
|
+
from .agent import run_agent
|
|
204
|
+
|
|
205
|
+
worker = self._get(worker_id)
|
|
206
|
+
if worker is None:
|
|
207
|
+
return f"[Error] No worker with id '{worker_id}' in this session."
|
|
208
|
+
|
|
209
|
+
if worker.status == RUNNING:
|
|
210
|
+
worker.history.append({"role": "user", "content": message})
|
|
211
|
+
console.print(
|
|
212
|
+
f" 📨 [dim]Message injected into running worker [cyan]{worker_id}[/cyan][/dim]"
|
|
213
|
+
)
|
|
214
|
+
return f"Message sent to running worker {worker_id}."
|
|
215
|
+
|
|
216
|
+
if worker.status in (DONE, FAILED, STOPPED):
|
|
217
|
+
worker.status = RUNNING
|
|
218
|
+
worker._abort.clear()
|
|
219
|
+
|
|
220
|
+
def _continue():
|
|
221
|
+
t0 = time.time()
|
|
222
|
+
try:
|
|
223
|
+
info = AGENT_TYPES.get(worker.agent_type, AGENT_TYPES["general"])
|
|
224
|
+
allowed = info["allowed_tools"]
|
|
225
|
+
output = run_agent(
|
|
226
|
+
task=message,
|
|
227
|
+
project_path=worker._project_path,
|
|
228
|
+
auto_approve=True,
|
|
229
|
+
history=worker.history,
|
|
230
|
+
system_content=worker._system,
|
|
231
|
+
file_cache=worker._cache,
|
|
232
|
+
allowed_tools=allowed,
|
|
233
|
+
silent=True,
|
|
234
|
+
) or ""
|
|
235
|
+
worker.status = DONE
|
|
236
|
+
worker.result = output
|
|
237
|
+
worker.tool_uses = sum(
|
|
238
|
+
1 for m in worker.history if m.get("role") == "tool"
|
|
239
|
+
)
|
|
240
|
+
except Exception as exc:
|
|
241
|
+
worker.status = FAILED
|
|
242
|
+
worker.error = str(exc)
|
|
243
|
+
finally:
|
|
244
|
+
worker.duration_ms += int((time.time() - t0) * 1000)
|
|
245
|
+
self._push_notification(worker)
|
|
246
|
+
|
|
247
|
+
t = threading.Thread(target=_continue, daemon=True, name=f"bc-cont-{worker_id}")
|
|
248
|
+
worker._thread = t
|
|
249
|
+
t.start()
|
|
250
|
+
console.print(
|
|
251
|
+
f" 🔄 [dim]Continuing worker [cyan]{worker_id}[/cyan] with new instructions[/dim]"
|
|
252
|
+
)
|
|
253
|
+
return f"Worker {worker_id} re-started with continuation message."
|
|
254
|
+
|
|
255
|
+
return f"Worker {worker_id} status is '{worker.status}' — cannot message."
|
|
256
|
+
|
|
257
|
+
def stop(self, worker_id: str) -> str:
|
|
258
|
+
"""
|
|
259
|
+
Send an abort signal to a running worker.
|
|
260
|
+
The worker checks _abort on each tool call completion; it stops cleanly
|
|
261
|
+
at the next boundary rather than being killed mid-write.
|
|
262
|
+
A stopped worker can be continued with send_message().
|
|
263
|
+
"""
|
|
264
|
+
worker = self._get(worker_id)
|
|
265
|
+
if worker is None:
|
|
266
|
+
return f"[Error] No worker with id '{worker_id}'."
|
|
267
|
+
if worker.status != RUNNING:
|
|
268
|
+
return f"Worker {worker_id} is not running (status: {worker.status})."
|
|
269
|
+
|
|
270
|
+
worker._abort.set()
|
|
271
|
+
worker.status = STOPPED
|
|
272
|
+
console.print(
|
|
273
|
+
f" 🛑 [dim]Stop signal sent to worker [cyan]{worker_id}[/cyan][/dim]"
|
|
274
|
+
)
|
|
275
|
+
return f"Stop signal sent to worker {worker_id}. It will halt at the next safe boundary."
|
|
276
|
+
|
|
277
|
+
def drain_notifications(self) -> list[dict]:
|
|
278
|
+
"""
|
|
279
|
+
Drain all pending completion notifications from the queue.
|
|
280
|
+
Returns a list of history messages (role=user) ready to inject into
|
|
281
|
+
the coordinator's history before the next API call.
|
|
282
|
+
|
|
283
|
+
Called once per coordinator turn at the top of run_agent()'s loop.
|
|
284
|
+
Thread-safe: Queue.get_nowait() does not block.
|
|
285
|
+
"""
|
|
286
|
+
messages = []
|
|
287
|
+
while True:
|
|
288
|
+
try:
|
|
289
|
+
xml = self.notification_queue.get_nowait()
|
|
290
|
+
messages.append({"role": "user", "content": xml})
|
|
291
|
+
except Empty:
|
|
292
|
+
break
|
|
293
|
+
return messages
|
|
294
|
+
|
|
295
|
+
def status_table(self) -> str:
|
|
296
|
+
"""Render all workers as a Rich table string for /workers command."""
|
|
297
|
+
with self._lock:
|
|
298
|
+
workers = list(self._workers.values())
|
|
299
|
+
if not workers:
|
|
300
|
+
return "[dim]No workers launched in this coordinator session.[/dim]"
|
|
301
|
+
|
|
302
|
+
t = Table(box=box.SIMPLE, show_header=True, header_style="bold dim")
|
|
303
|
+
t.add_column("ID", style="cyan", no_wrap=True)
|
|
304
|
+
t.add_column("Type", style="dim", no_wrap=True)
|
|
305
|
+
t.add_column("Status", no_wrap=True)
|
|
306
|
+
t.add_column("Time", style="dim", no_wrap=True)
|
|
307
|
+
t.add_column("Tools", style="dim", no_wrap=True)
|
|
308
|
+
t.add_column("Description", style="white")
|
|
309
|
+
|
|
310
|
+
for w in workers:
|
|
311
|
+
icon = _STATUS_ICON.get(w.status, "❓")
|
|
312
|
+
time_s = f"{w.duration_ms/1000:.1f}s" if w.duration_ms else (
|
|
313
|
+
f"{w.elapsed_s:.0f}s…" if w.status == RUNNING else "—"
|
|
314
|
+
)
|
|
315
|
+
t.add_row(
|
|
316
|
+
w.worker_id,
|
|
317
|
+
w.agent_type,
|
|
318
|
+
f"{icon} {w.status}",
|
|
319
|
+
time_s,
|
|
320
|
+
str(w.tool_uses),
|
|
321
|
+
w.description[:50],
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
import io
|
|
325
|
+
buf = io.StringIO()
|
|
326
|
+
tmp = Console(file=buf, width=120, highlight=False, markup=True)
|
|
327
|
+
tmp.print(t)
|
|
328
|
+
return buf.getvalue()
|
|
329
|
+
|
|
330
|
+
# ── Private Helpers ────────────────────────────────────────────────────────
|
|
331
|
+
|
|
332
|
+
def _get(self, worker_id: str) -> Optional[Worker]:
|
|
333
|
+
with self._lock:
|
|
334
|
+
return self._workers.get(worker_id)
|
|
335
|
+
|
|
336
|
+
def _push_notification(self, worker: Worker):
|
|
337
|
+
"""
|
|
338
|
+
Build a <task-notification> XML block and push it to the queue.
|
|
339
|
+
The coordinator will drain this on its next turn and inject it
|
|
340
|
+
into conversation history as a user message.
|
|
341
|
+
"""
|
|
342
|
+
status = worker.status
|
|
343
|
+
icon = _STATUS_ICON.get(status, "❓")
|
|
344
|
+
summary = {
|
|
345
|
+
DONE: f'Agent "{worker.description}" completed',
|
|
346
|
+
FAILED: f'Agent "{worker.description}" failed: {worker.error[:120]}',
|
|
347
|
+
STOPPED: f'Agent "{worker.description}" was stopped',
|
|
348
|
+
}.get(status, f'Agent status changed to {status}')
|
|
349
|
+
|
|
350
|
+
result_xml = (
|
|
351
|
+
f"\n<result>\n{worker.result[:25000]}\n</result>"
|
|
352
|
+
if worker.result.strip() else ""
|
|
353
|
+
)
|
|
354
|
+
error_xml = (
|
|
355
|
+
f"\n<error>{worker.error}</error>"
|
|
356
|
+
if worker.error else ""
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
xml = (
|
|
360
|
+
f"<task-notification>\n"
|
|
361
|
+
f"<task-id>{worker.worker_id}</task-id>\n"
|
|
362
|
+
f"<agent-type>{worker.agent_type}</agent-type>\n"
|
|
363
|
+
f"<description>{worker.description}</description>\n"
|
|
364
|
+
f"<status>{status}</status>\n"
|
|
365
|
+
f"<summary>{summary}</summary>"
|
|
366
|
+
f"{result_xml}"
|
|
367
|
+
f"{error_xml}\n"
|
|
368
|
+
f"<usage>\n"
|
|
369
|
+
f" <tool_uses>{worker.tool_uses}</tool_uses>\n"
|
|
370
|
+
f" <duration_ms>{worker.duration_ms}</duration_ms>\n"
|
|
371
|
+
f"</usage>\n"
|
|
372
|
+
f"</task-notification>"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# Print completion banner to coordinator terminal
|
|
376
|
+
_banner_done(worker, icon)
|
|
377
|
+
self.notification_queue.put(xml)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
# ── Terminal Banners ───────────────────────────────────────────────────────────
|
|
381
|
+
|
|
382
|
+
def _banner_spawn(worker_id: str, info: dict, description: str):
|
|
383
|
+
color = info["color"]
|
|
384
|
+
icon = info["icon"]
|
|
385
|
+
label = info["label"]
|
|
386
|
+
console.print(
|
|
387
|
+
f"\n 🚀 [bold {color}]{icon} {label}[/bold {color}] "
|
|
388
|
+
f"[cyan]{worker_id}[/cyan] "
|
|
389
|
+
f"[dim]{description[:55]}[/dim] "
|
|
390
|
+
f"[dim italic]running in background[/dim italic]"
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _banner_done(worker: Worker, icon: str):
|
|
395
|
+
secs = worker.duration_ms / 1000
|
|
396
|
+
console.print(
|
|
397
|
+
f"\n {icon} [bold]Worker done[/bold] "
|
|
398
|
+
f"[cyan]{worker.worker_id}[/cyan] "
|
|
399
|
+
f"[dim]{worker.agent_type} {secs:.1f}s {worker.tool_uses} tool calls[/dim]"
|
|
400
|
+
)
|
|
401
|
+
if worker.error:
|
|
402
|
+
console.print(f" [red]Error:[/red] [dim]{worker.error[:120]}[/dim]")
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
# ── Coordinator System Prompt ──────────────────────────────────────────────────
|
|
406
|
+
|
|
407
|
+
COORDINATOR_SYSTEM_PROMPT = """
|
|
408
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
409
|
+
## COORDINATOR MODE — ACTIVE
|
|
410
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
411
|
+
|
|
412
|
+
You are Sylithe Code operating as a **coordinator**. You do NOT write code yourself.
|
|
413
|
+
You orchestrate specialist workers, synthesize their results, and direct the work.
|
|
414
|
+
|
|
415
|
+
═══════════════════════════════════════════════════════════
|
|
416
|
+
## 1. YOUR ROLE
|
|
417
|
+
═══════════════════════════════════════════════════════════
|
|
418
|
+
|
|
419
|
+
Coordinator responsibilities:
|
|
420
|
+
• Break tasks into parallel subtasks and assign them to workers
|
|
421
|
+
• Receive worker results as <task-notification> XML messages
|
|
422
|
+
• SYNTHESIZE findings — understand what they mean before directing next steps
|
|
423
|
+
• Keep the user informed: tell them what you launched and why
|
|
424
|
+
• Direct the full lifecycle: research → synthesis → implementation → verification
|
|
425
|
+
|
|
426
|
+
You answer questions directly when you can. Only delegate work that needs tools.
|
|
427
|
+
|
|
428
|
+
═══════════════════════════════════════════════════════════
|
|
429
|
+
## 2. YOUR TOOLS
|
|
430
|
+
═══════════════════════════════════════════════════════════
|
|
431
|
+
|
|
432
|
+
| Tool | When to use |
|
|
433
|
+
|----------------|----------------------------------------------------------|
|
|
434
|
+
| spawn_worker | Launch a background specialist — returns worker_id NOW |
|
|
435
|
+
| send_message | Continue an existing worker with a follow-up task |
|
|
436
|
+
| task_stop | Kill a worker that went in the wrong direction |
|
|
437
|
+
| read_file | Read files yourself to synthesize worker findings |
|
|
438
|
+
| glob / grep | Search codebase for context before directing workers |
|
|
439
|
+
| web_fetch | Look up docs you need for synthesis |
|
|
440
|
+
|
|
441
|
+
You do NOT have: write_file, edit_file, bash — workers handle all execution.
|
|
442
|
+
|
|
443
|
+
═══════════════════════════════════════════════════════════
|
|
444
|
+
## 3. WORKER TYPES
|
|
445
|
+
═══════════════════════════════════════════════════════════
|
|
446
|
+
|
|
447
|
+
| Type | Tools available | Best for |
|
|
448
|
+
|------------|------------------------------------|---------------------------------|
|
|
449
|
+
| explore | read_file, glob, grep, list_dir | Codebase mapping, finding code |
|
|
450
|
+
| researcher | web_fetch, read_file | Docs, API specs, examples |
|
|
451
|
+
| coder | ALL tools (read + write + bash) | Implementation, fixes, commits |
|
|
452
|
+
| verifier | read_file, glob, grep, bash | Tests, QA, security audit |
|
|
453
|
+
| general | ALL tools | Multi-role or unclear tasks |
|
|
454
|
+
|
|
455
|
+
═══════════════════════════════════════════════════════════
|
|
456
|
+
## 4. WORKER NOTIFICATIONS
|
|
457
|
+
═══════════════════════════════════════════════════════════
|
|
458
|
+
|
|
459
|
+
When a worker finishes, you receive a <task-notification> user message:
|
|
460
|
+
|
|
461
|
+
```xml
|
|
462
|
+
<task-notification>
|
|
463
|
+
<task-id>w-a1b2c3</task-id>
|
|
464
|
+
<agent-type>explore</agent-type>
|
|
465
|
+
<description>Auth module mapping</description>
|
|
466
|
+
<status>completed|failed|stopped</status>
|
|
467
|
+
<summary>Agent "Auth module mapping" completed</summary>
|
|
468
|
+
<result>
|
|
469
|
+
...worker's full output...
|
|
470
|
+
</result>
|
|
471
|
+
<usage>
|
|
472
|
+
<tool_uses>14</tool_uses>
|
|
473
|
+
<duration_ms>9420</duration_ms>
|
|
474
|
+
</usage>
|
|
475
|
+
</task-notification>
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
These arrive as "user" messages but are NOT from the user — they are internal
|
|
479
|
+
worker completions. Never thank them. Process them and direct next steps.
|
|
480
|
+
|
|
481
|
+
═══════════════════════════════════════════════════════════
|
|
482
|
+
## 5. WORKFLOW — RESEARCH → SYNTHESIS → IMPLEMENTATION → VERIFY
|
|
483
|
+
═══════════════════════════════════════════════════════════
|
|
484
|
+
|
|
485
|
+
### Phase 1: Research (parallel)
|
|
486
|
+
Spawn multiple explore/researcher workers simultaneously.
|
|
487
|
+
After launching, tell the user: "Investigating from N angles — will report back."
|
|
488
|
+
|
|
489
|
+
### Phase 2: Synthesis (you)
|
|
490
|
+
When notifications arrive, READ the findings carefully.
|
|
491
|
+
YOUR JOB: understand what they mean, then write a precise implementation spec:
|
|
492
|
+
- exact file path + line number
|
|
493
|
+
- exactly what to change and why
|
|
494
|
+
- what "done" looks like
|
|
495
|
+
|
|
496
|
+
NEVER write "based on your findings" — that delegates understanding to the worker.
|
|
497
|
+
YOU do the synthesis. The worker gets a spec, not an instruction to "figure it out."
|
|
498
|
+
|
|
499
|
+
### Phase 3: Implementation (workers, serialized per file area)
|
|
500
|
+
Continue the research worker (it has context) OR spawn a fresh coder worker.
|
|
501
|
+
Always ask workers to: run tests, commit, report the commit hash.
|
|
502
|
+
|
|
503
|
+
### Phase 4: Verification (fresh verifier worker)
|
|
504
|
+
Always spawn a FRESH verifier — it should see the code with no assumptions.
|
|
505
|
+
"Prove the code works, don't rubber-stamp it."
|
|
506
|
+
|
|
507
|
+
═══════════════════════════════════════════════════════════
|
|
508
|
+
## 6. PARALLELISM — YOUR SUPERPOWER
|
|
509
|
+
═══════════════════════════════════════════════════════════
|
|
510
|
+
|
|
511
|
+
**Workers are async. Parallelism is free. Use it.**
|
|
512
|
+
|
|
513
|
+
To run workers in parallel: call spawn_worker MULTIPLE TIMES in ONE response.
|
|
514
|
+
|
|
515
|
+
Rules:
|
|
516
|
+
✓ Read-only (explore/researcher): unlimited parallel — no file conflicts
|
|
517
|
+
✓ Write tasks: one worker per file area — avoid merge conflicts
|
|
518
|
+
✓ Verification can overlap implementation on different areas
|
|
519
|
+
|
|
520
|
+
Example — 3 parallel research workers in one response turn:
|
|
521
|
+
spawn_worker(task="Map all API routes in src/api/...", agent_type="explore", description="API route map")
|
|
522
|
+
spawn_worker(task="Find all auth-related code patterns...", agent_type="explore", description="Auth scan")
|
|
523
|
+
spawn_worker(task="Fetch Razorpay webhook Python docs...", agent_type="researcher", description="Razorpay docs")
|
|
524
|
+
→ "Investigating from 3 angles in parallel. Will synthesize when they report back."
|
|
525
|
+
|
|
526
|
+
═══════════════════════════════════════════════════════════
|
|
527
|
+
## 7. WRITING WORKER PROMPTS
|
|
528
|
+
═══════════════════════════════════════════════════════════
|
|
529
|
+
|
|
530
|
+
Workers have NO memory of your conversation. Every prompt must be 100% self-contained:
|
|
531
|
+
|
|
532
|
+
Required elements:
|
|
533
|
+
1. What to do (specific, not vague)
|
|
534
|
+
2. Exact file paths and line numbers when known
|
|
535
|
+
3. What "done" looks like
|
|
536
|
+
4. A purpose statement: "This research will inform..." / "This fix addresses..."
|
|
537
|
+
|
|
538
|
+
For implementation workers:
|
|
539
|
+
→ "Run tests after changing, then commit and report the commit hash."
|
|
540
|
+
|
|
541
|
+
For research workers:
|
|
542
|
+
→ "Report findings — do NOT modify files."
|
|
543
|
+
|
|
544
|
+
For verification workers:
|
|
545
|
+
→ "Prove the code works. Run edge cases. Be skeptical."
|
|
546
|
+
|
|
547
|
+
❌ Bad prompts:
|
|
548
|
+
"Fix the bug we discussed"
|
|
549
|
+
"Based on your findings, implement the fix"
|
|
550
|
+
"Look at the auth module"
|
|
551
|
+
|
|
552
|
+
✅ Good prompts:
|
|
553
|
+
"Fix the null check in src/auth/validate.py line 42. The `user` field on Session
|
|
554
|
+
is None when the session expires but the token remains cached. Add: if session.user
|
|
555
|
+
is None: raise HTTPException(401, 'Session expired'). Run pytest tests/test_auth.py,
|
|
556
|
+
fix any failures, then commit. Report the commit hash."
|
|
557
|
+
|
|
558
|
+
═══════════════════════════════════════════════════════════
|
|
559
|
+
## 8. CONTINUE vs SPAWN FRESH
|
|
560
|
+
═══════════════════════════════════════════════════════════
|
|
561
|
+
|
|
562
|
+
| Situation | Action |
|
|
563
|
+
|----------------------------------------------------|--------------------------|
|
|
564
|
+
| Research worker explored exact files to edit | send_message (continue) |
|
|
565
|
+
| Research was broad, implementation is narrow | spawn_worker (fresh) |
|
|
566
|
+
| Correcting a worker's own failure | send_message (continue) |
|
|
567
|
+
| Verifying code another worker wrote | spawn_worker (fresh) |
|
|
568
|
+
| Worker went in completely wrong direction | task_stop → spawn fresh |
|
|
569
|
+
| Unrelated task | spawn_worker (fresh) |
|
|
570
|
+
|
|
571
|
+
continue = reuse context. fresh = clean slate. High context overlap → continue.
|
|
572
|
+
|
|
573
|
+
⚠️ DO NOT use send_message just because a report seems short.
|
|
574
|
+
Worker reports are complete unless they explicitly say "truncated" or "ran out of tokens".
|
|
575
|
+
Synthesize from what you have. Avoid unnecessary continuation rounds.
|
|
576
|
+
|
|
577
|
+
═══════════════════════════════════════════════════════════
|
|
578
|
+
## 9. AFTER LAUNCHING WORKERS
|
|
579
|
+
═══════════════════════════════════════════════════════════
|
|
580
|
+
|
|
581
|
+
After calling spawn_worker: briefly tell the user what you launched, then END
|
|
582
|
+
your response. Do NOT predict results. Workers are running — their results arrive
|
|
583
|
+
as <task-notification> messages. Wait for them.
|
|
584
|
+
|
|
585
|
+
Good: "Launched 3 workers in parallel — mapping routes, scanning auth code, fetching
|
|
586
|
+
docs. Will synthesize findings when they arrive."
|
|
587
|
+
|
|
588
|
+
Bad: "I've launched a worker that will investigate the auth module and likely find the
|
|
589
|
+
null pointer on line 42, which I'll then fix by adding a null check..."
|
|
590
|
+
|
|
591
|
+
═══════════════════════════════════════════════════════════
|
|
592
|
+
## 10. FULL-STACK / MULTI-WORKER BUILD PROTOCOL
|
|
593
|
+
═══════════════════════════════════════════════════════════
|
|
594
|
+
|
|
595
|
+
When different workers build parts that must talk to each other (frontend +
|
|
596
|
+
backend, mobile app + API, two services), INTERFACE MISMATCH is the #1 failure:
|
|
597
|
+
each worker invents its own endpoints/ports/JSON keys and nothing connects.
|
|
598
|
+
Prevent it with a SHARED CONTRACT — this is mandatory, not optional:
|
|
599
|
+
|
|
600
|
+
STEP 1 — YOU write the full contract FIRST, before spawning any coder:
|
|
601
|
+
• Every endpoint: METHOD /api/path → request JSON (exact keys) → response JSON
|
|
602
|
+
(exact keys) → status codes
|
|
603
|
+
• Ports: backend port, frontend dev port
|
|
604
|
+
• Env var names both sides use (VITE_API_URL, DATABASE_URL, JWT_SECRET, ...)
|
|
605
|
+
• Auth: header format (Authorization: Bearer <token>), token lifetime, refresh flow
|
|
606
|
+
|
|
607
|
+
STEP 2 — Paste the IDENTICAL contract block into EVERY coder worker prompt.
|
|
608
|
+
Backend worker: "FIRST save this contract verbatim as API_CONTRACT.md in the
|
|
609
|
+
project root, then implement it EXACTLY — same paths, same keys, same ports."
|
|
610
|
+
Frontend worker: "Implement every API call EXACTLY per this contract. If
|
|
611
|
+
API_CONTRACT.md exists in the project root, read it first — it is law."
|
|
612
|
+
|
|
613
|
+
STEP 3 — Parallelism rule: frontend + backend coders may run in PARALLEL only
|
|
614
|
+
because both prompts embed the identical contract. If you cannot fully specify
|
|
615
|
+
the contract yet, build the backend FIRST, then read its actual routes yourself
|
|
616
|
+
and spawn the frontend worker with the real endpoints.
|
|
617
|
+
|
|
618
|
+
STEP 4 — After ALL coders report: ALWAYS spawn a FRESH verifier with this checklist:
|
|
619
|
+
• every frontend API call matches a backend route (path + method + JSON keys)
|
|
620
|
+
• vite proxy / VITE_API_URL port == actual backend port
|
|
621
|
+
• backend CORS allows the actual frontend origin
|
|
622
|
+
• GET /api/health returns 200; .env.example complete on both sides
|
|
623
|
+
• report every mismatch with file + line + the exact fix
|
|
624
|
+
|
|
625
|
+
Workers also coordinate through BUILD_LOG.md (append-only, project root): every
|
|
626
|
+
coder appends what it created (files, endpoints, exports, ports). When spawning a
|
|
627
|
+
worker into a project other workers already touched, ALWAYS tell it:
|
|
628
|
+
"Read API_CONTRACT.md and BUILD_LOG.md in the project root before writing anything."
|
|
629
|
+
"""
|
|
630
|
+
|
|
631
|
+
# ── Worker System Prompt Suffix ────────────────────────────────────────────────
|
|
632
|
+
|
|
633
|
+
_WORKER_INSTRUCTIONS = """
|
|
634
|
+
|
|
635
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
636
|
+
## WORKER MODE — YOU ARE A BACKGROUND SPECIALIST
|
|
637
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
638
|
+
|
|
639
|
+
You are a background worker spawned by the Sylithe Code coordinator.
|
|
640
|
+
The coordinator sent you a precise task. Execute it completely.
|
|
641
|
+
|
|
642
|
+
RULES (non-negotiable):
|
|
643
|
+
1. Do NOT ask questions — execute the task as specified
|
|
644
|
+
2. Do NOT editorialize or add meta-commentary
|
|
645
|
+
3. For implementation: run tests, commit, report the commit hash
|
|
646
|
+
4. For research: report findings — do NOT modify files
|
|
647
|
+
5. Stay strictly within your task scope
|
|
648
|
+
6. Keep your report under 800 words unless the task requires more
|
|
649
|
+
|
|
650
|
+
SHARED-PROJECT COORDINATION (other workers may build sibling parts):
|
|
651
|
+
7. BEFORE writing any code: read API_CONTRACT.md and BUILD_LOG.md in the
|
|
652
|
+
project root if they exist — they are the source of truth created by the
|
|
653
|
+
coordinator and other workers. Follow API_CONTRACT.md EXACTLY: same
|
|
654
|
+
endpoint paths, same JSON keys, same ports, same env var names.
|
|
655
|
+
NEVER change the contract — report any mismatch under Issues instead.
|
|
656
|
+
8. AFTER changing files: APPEND your work summary to BUILD_LOG.md using
|
|
657
|
+
write_file with mode='a' (NEVER overwrite it):
|
|
658
|
+
## <your scope> (<agent type>)
|
|
659
|
+
Files: <files you created/edited>
|
|
660
|
+
Interfaces: <endpoints/exports/ports/env vars you defined>
|
|
661
|
+
|
|
662
|
+
REPORT FORMAT (your final response):
|
|
663
|
+
Scope: <echo back your assigned task in one sentence>
|
|
664
|
+
Result: <what you found or built>
|
|
665
|
+
Key files: <exact file paths with line numbers — always include>
|
|
666
|
+
Files changed: <list + commit hash — include only if you modified files>
|
|
667
|
+
Issues: <anything the coordinator should know — bugs, risks, blockers>
|
|
668
|
+
|
|
669
|
+
Begin with "Scope:" — no preamble.
|
|
670
|
+
"""
|