osborn 0.9.218 → 0.9.219

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.
@@ -46,6 +46,10 @@ export declare const NAMED_AGENTS: {
46
46
  description: string;
47
47
  tools: string[];
48
48
  grounded: boolean;
49
+ coordination: {
50
+ then: string[];
51
+ startNote: string;
52
+ };
49
53
  model: string;
50
54
  prompt: string;
51
55
  };
@@ -63,6 +67,11 @@ export declare const NAMED_AGENTS: {
63
67
  write: "anywhere";
64
68
  };
65
69
  reminder: string;
70
+ coordination: {
71
+ then: string[];
72
+ mode: "parallel";
73
+ startNote: string;
74
+ };
66
75
  model: string;
67
76
  prompt: string;
68
77
  };
@@ -366,6 +366,12 @@ export const NAMED_AGENTS = {
366
366
  ].join(' '),
367
367
  tools: ['Read', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch', 'Task'],
368
368
  grounded: true, // applyGrounding() injects the osborn-recall command + ensures Bash
369
+ // Declarative flow. == today: after the researcher finishes, a reasoner-based
370
+ // research gate judges completeness and may send it back for more.
371
+ coordination: {
372
+ then: ['reasoner'],
373
+ startNote: 'When you finish, a reasoner-based gate judges whether your findings are COMPLETE and well-sourced against the task; thin or unsourced findings get sent back to you. Cite file paths + line numbers and explicitly note what you looked for but did NOT find.',
374
+ },
369
375
  model: 'sonnet',
370
376
  prompt: [
371
377
  'You are Osborn\'s research agent. Your job is information gathering — thorough, structured, factual.',
@@ -472,6 +478,13 @@ export const NAMED_AGENTS = {
472
478
  // Soft behavior via the composable `reminder` seam → SDK criticalSystemReminder_EXPERIMENTAL.
473
479
  // Pinned into the writer's system prompt as a hard-to-ignore reminder.
474
480
  reminder: 'BEFORE you write implementation code: make sure a test exists for the behavior you are about to change. If none exists, say so explicitly in your report so the tester can cover it — the tester is the agent that writes tests. NEVER weaken, skip, or delete a test to make your change pass.',
481
+ // Declarative flow (drives SubagentStop dispatch + SubagentStart injection).
482
+ // == today: after the writer finishes, reviewer AND tester run in parallel.
483
+ coordination: {
484
+ then: ['reviewer', 'tester'],
485
+ mode: 'parallel',
486
+ startNote: 'When you finish, your change is automatically verified in parallel: a reviewer checks correctness against the git diff, and a tester runs the suite. Make the change review-ready and leave the tree in a runnable state — do not skip cleanup expecting a second pass.',
487
+ },
475
488
  model: 'opus',
476
489
  prompt: [
477
490
  'You are Osborn\'s writer agent. You execute file changes with a verify-first approach.',
@@ -881,15 +894,20 @@ function decideWrite(policy, filePath) {
881
894
  }
882
895
  return { decision: 'defer' };
883
896
  }
897
+ /** Coordination config for the acting/target agent (null agentType = main). */
898
+ function coordinationFor(agentType, roster) {
899
+ const def = agentType ? roster?.[agentType] : null;
900
+ return def?.coordination ?? null;
901
+ }
884
902
  /**
885
903
  * Strip/map behavior meta-fields so the roster is a clean AgentDefinition set
886
- * for the SDK: drop `policy` (enforced in-process by the write-gate) and map
887
- * `reminder` → criticalSystemReminder_EXPERIMENTAL. NEVER mutates the input.
904
+ * for the SDK: drop `policy` (write-gate) and `coordination` (hook-driven),
905
+ * and map `reminder` → criticalSystemReminder_EXPERIMENTAL. NEVER mutates input.
888
906
  */
889
907
  function finalizeRoster(agents) {
890
908
  const out = {};
891
909
  for (const [name, agent] of Object.entries(agents)) {
892
- const { policy, reminder, ...rest } = agent;
910
+ const { policy, reminder, coordination, ...rest } = agent;
893
911
  if (reminder && !rest.criticalSystemReminder_EXPERIMENTAL) {
894
912
  rest.criticalSystemReminder_EXPERIMENTAL = reminder;
895
913
  }
@@ -2137,6 +2155,18 @@ class ClaudeLLMStream extends llm.LLMStream {
2137
2155
  }
2138
2156
  console.log(`🔧 Tool call ${turnToolCallCount}/${TOOL_CALL_BUDGET}: ${toolName}`);
2139
2157
  }
2158
+ // Delegation-point injection — when the hub spawns a sub-agent, add
2159
+ // that agent's discretionary delegationNote to the hub's context
2160
+ // (e.g. "add a tester for high-risk edits"). Empty by default → inert.
2161
+ if (toolName === 'Task') {
2162
+ const targetType = String(toolInput?.subagent_type || '');
2163
+ const note = coordinationFor(targetType || null, enforcementRoster)?.delegationNote;
2164
+ if (note) {
2165
+ this.#eventEmitter.emit('tool_use', { name: toolName, input: toolInput, agentRole: agentType || 'main' });
2166
+ console.log(`🤝 Delegation note → subagent_type=${targetType}`);
2167
+ return { hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: note } };
2168
+ }
2169
+ }
2140
2170
  // Write/Edit/MultiEdit access control — DATA-DRIVEN by each agent's
2141
2171
  // declarative policy.write (see AgentWritePolicy). Replaces the old
2142
2172
  // hardcoded per-role branches; DB-backed custom agents get enforced too.
@@ -2506,6 +2536,12 @@ class ClaudeLLMStream extends llm.LLMStream {
2506
2536
  hooks: [async (input) => {
2507
2537
  console.log('[LIFECYCLE-PROBE] SubagentStart', JSON.stringify(input));
2508
2538
  this.#eventEmitter.emit('agent_started', { agent_type: input?.agent_type, agent_id: input?.agent_id });
2539
+ // Inject the agent's declarative startNote (who it is paired with) —
2540
+ // reliable, always-seen at boot regardless of what the hub relayed.
2541
+ const startNote = coordinationFor(input?.agent_type ?? null, enforcementRoster)?.startNote;
2542
+ if (startNote) {
2543
+ return { hookSpecificOutput: { hookEventName: 'SubagentStart', additionalContext: startNote } };
2544
+ }
2509
2545
  return {};
2510
2546
  }]
2511
2547
  }],
@@ -2518,15 +2554,34 @@ class ClaudeLLMStream extends llm.LLMStream {
2518
2554
  const aid = input?.agent_id ?? ('sa-' + Date.now());
2519
2555
  statusManager.upsertDispatch(aid, { subagentType: at, dispatchState: 'completed', artifact: msg });
2520
2556
  this.#eventEmitter.emit('task_completed', { agent_type: at, agent_id: aid, last_assistant_message: String(msg).slice(0, 400) });
2521
- // Infinite-loop guard — never re-dispatch the reviewer, tester, or reasoner.
2557
+ // Infinite-loop guard — verifiers never re-dispatch (they carry no
2558
+ // coordination.then anyway; this is defense-in-depth against a
2559
+ // DB-backed agent accidentally arming a loop).
2522
2560
  if (at === 'reviewer' || at === 'tester' || at === 'reasoner')
2523
2561
  return {};
2524
- if (at === 'writer' && msg) {
2525
- void this.#llmRef.spawnReviewer(aid, msg, this.#eventEmitter);
2526
- void this.#llmRef.spawnTester(aid, msg, this.#eventEmitter);
2527
- }
2528
- else if (at === 'researcher' && msg) {
2529
- void this.#llmRef.spawnResearchGate(aid, msg, this.#eventEmitter);
2562
+ // Declarative verifier chaining — driven by the finishing agent's
2563
+ // coordination.then (replaces the hardcoded writer/researcher branches).
2564
+ const coord = coordinationFor(at ?? null, enforcementRoster);
2565
+ if (coord?.then?.length && msg) {
2566
+ const spawn = (role) => {
2567
+ if (role === 'reviewer')
2568
+ return this.#llmRef.spawnReviewer(aid, msg, this.#eventEmitter);
2569
+ if (role === 'tester')
2570
+ return this.#llmRef.spawnTester(aid, msg, this.#eventEmitter);
2571
+ if (role === 'reasoner' || role === 'gate')
2572
+ return this.#llmRef.spawnResearchGate(aid, msg, this.#eventEmitter);
2573
+ console.warn(`[DISPATCH] unknown coordination target '${role}' for ${at} — skipped`);
2574
+ return Promise.resolve();
2575
+ };
2576
+ if (coord.mode === 'sequential') {
2577
+ // Await in order WITHOUT blocking the hook return (fire the chain async).
2578
+ void (async () => { for (const r of coord.then)
2579
+ await spawn(r); })();
2580
+ }
2581
+ else {
2582
+ for (const r of coord.then)
2583
+ void spawn(r);
2584
+ }
2530
2585
  }
2531
2586
  return {};
2532
2587
  }]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.218",
3
+ "version": "0.9.219",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {