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.
@@ -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
+ """