opencode-titan 0.1.1 → 0.1.3

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.
@@ -8,7 +8,7 @@ export declare const ALL_AGENT_NAMES: readonly ["titan"];
8
8
  export type AgentName = (typeof ALL_AGENT_NAMES)[number];
9
9
  /** Agents that cannot be disabled. */
10
10
  export declare const PROTECTED_AGENTS: Set<"titan">;
11
- export declare const DELEGATION_REMINDER = "<internal_reminder>DELEGATE EVERYTHING POSSIBLE TO MYRMIDONS. You are the slowest agent by far - your only job is planning, routing, and synthesizing results. Never do work a Myrmidon can handle. Parallelize aggressively: tool calls made in the SAME response turn run concurrently \u2014 always batch all ready task() dispatches into ONE response, never one per turn. SAY WHAT YOU DO: if you announce launching N Myrmidons, emit exactly N task() calls in that same response \u2014 never announce multiple then dispatch only one. Before ending a dispatch turn, count your task() calls and confirm they match the number of Myrmidons you named. The task() tool takes only subagent_type, description, and prompt \u2014 never pass any other parameters. </internal_reminder>";
11
+ export declare const DELEGATION_REMINDER = "<internal_reminder>DELEGATE EVERYTHING POSSIBLE TO MYRMIDONS. You are the slowest agent by far - your only job is planning, routing, and synthesizing results. Never do work a Myrmidon can handle. THE ONE EXCEPTION: context-bound synthesis. If a task's input is your OWN accumulated context \u2014 findings you gathered, results Myrmidons reported back, reasoning built up this session \u2014 do it YOURSELF. Delegating a summary/report/plan/file of what you found or decided strips the context the task needs (Myrmidons start blank), so the write-up stays with you. Parallelize aggressively: tool calls made in the SAME response turn run concurrently \u2014 always batch all ready task() dispatches into ONE response, never one per turn. SAY WHAT YOU DO: if you announce launching N Myrmidons, emit exactly N task() calls in that same response \u2014 never announce multiple then dispatch only one. Before ending a dispatch turn, count your task() calls and confirm they match the number of Myrmidons you named. The task() tool takes only subagent_type, description, and prompt \u2014 never pass any other parameters. </internal_reminder>";
12
12
  /**
13
13
  * Sentinel substring used to detect whether {@link DELEGATION_REMINDER} has
14
14
  * already been injected into a system prompt, avoiding double-injection.
@@ -23,4 +23,4 @@ export declare const DELEGATION_REMINDER_SENTINEL = "DELEGATE EVERYTHING POSSIBL
23
23
  * at the point of each new user message keeps that behavior consistent — it
24
24
  * mimics the user manually prefixing "delegate this" onto every ask.
25
25
  */
26
- export declare const PER_MESSAGE_DELEGATION_REMINDER = "<delegation_directive>\nBefore you act on the user's message above, STOP and route it through delegation first.\n\n1. Decompose the request (including any follow-up tweaks, fixes, or refinements to prior work) into concrete units of work.\n2. For every unit that would require even a SINGLE tool call \u2014 reading/searching/editing files, running commands, testing, validating, looking things up, gathering information, or any mechanical work \u2014 you MUST delegate it to Myrmidons via task(). This applies to small iterative changes on the existing task just as much as to brand-new work. Do NOT do it yourself because it \"seems quick.\"\n3. Dispatch all independent units in parallel: emit one task() call per unit in a SINGLE response, and make the number of task() calls match the number of Myrmidons you announce.\n4. The ONLY time you may answer directly without delegating is when the request is trivially simple, purely conversational, and requires zero tool calls (e.g., clarifying a question, restating a plan, a one-word answer). When in doubt, delegate.\n\nTreat this as if the user explicitly said: \"delegate this to your Myrmidons.\"\n</delegation_directive>";
26
+ export declare const PER_MESSAGE_DELEGATION_REMINDER = "<delegation_directive>\nBefore you act on the user's message above, STOP and route it through delegation first.\n\n1. Decompose the request (including any follow-up tweaks, fixes, or refinements to prior work) into concrete units of work.\n2. For every unit that would require even a SINGLE tool call \u2014 reading/searching/editing files, running commands, testing, validating, looking things up, gathering information, or any mechanical work \u2014 you MUST delegate it to Myrmidons via task(). This applies to small iterative changes on the existing task just as much as to brand-new work. Do NOT do it yourself because it \"seems quick.\"\n3. EXCEPTION \u2014 context-bound synthesis: if a unit's real input is YOUR OWN accumulated context (findings you gathered, results your Myrmidons already reported back, reasoning or decisions from earlier in this session) and the deliverable is just expressing/organizing/persisting that context \u2014 e.g. writing a summary, report, plan, or markdown file of what was found/decided \u2014 do it YOURSELF. Delegating it would hand a blank-context Myrmidon a task it cannot do correctly, losing the very information the deliverable depends on. Writing a file is one cheap tool call; never delegate it just to avoid that call. You may still delegate any fresh lookups the write-up needs, but the context-bearing synthesis stays with you.\n4. Dispatch all independent units in parallel: emit one task() call per unit in a SINGLE response, and make the number of task() calls match the number of Myrmidons you announce.\n5. The ONLY time you may answer directly without delegating is when the request is trivially simple, purely conversational, and requires zero tool calls (e.g., clarifying a question, restating a plan, a one-word answer), OR when it is context-bound synthesis per point 3. When in doubt about fresh work, delegate; when in doubt about whether the task needs context only you hold, keep it.\n\nTreat this as if the user explicitly said: \"delegate this to your Myrmidons.\"\n</delegation_directive>";
package/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  // src/config/constants.ts
2
2
  var TITAN_AGENT_NAME = "titan";
3
3
  var DEFAULT_MAX_RESPONSE_WORDS = 1e3;
4
- var DELEGATION_REMINDER = `<internal_reminder>DELEGATE EVERYTHING POSSIBLE TO MYRMIDONS. You are the slowest agent by far - your only job is planning, routing, and synthesizing results. Never do work a Myrmidon can handle. Parallelize aggressively: tool calls made in the SAME response turn run concurrently \u2014 always batch all ready task() dispatches into ONE response, never one per turn. SAY WHAT YOU DO: if you announce launching N Myrmidons, emit exactly N task() calls in that same response \u2014 never announce multiple then dispatch only one. Before ending a dispatch turn, count your task() calls and confirm they match the number of Myrmidons you named. The task() tool takes only subagent_type, description, and prompt \u2014 never pass any other parameters. </internal_reminder>`;
4
+ var DELEGATION_REMINDER = `<internal_reminder>DELEGATE EVERYTHING POSSIBLE TO MYRMIDONS. You are the slowest agent by far - your only job is planning, routing, and synthesizing results. Never do work a Myrmidon can handle. THE ONE EXCEPTION: context-bound synthesis. If a task's input is your OWN accumulated context \u2014 findings you gathered, results Myrmidons reported back, reasoning built up this session \u2014 do it YOURSELF. Delegating a summary/report/plan/file of what you found or decided strips the context the task needs (Myrmidons start blank), so the write-up stays with you. Parallelize aggressively: tool calls made in the SAME response turn run concurrently \u2014 always batch all ready task() dispatches into ONE response, never one per turn. SAY WHAT YOU DO: if you announce launching N Myrmidons, emit exactly N task() calls in that same response \u2014 never announce multiple then dispatch only one. Before ending a dispatch turn, count your task() calls and confirm they match the number of Myrmidons you named. The task() tool takes only subagent_type, description, and prompt \u2014 never pass any other parameters. </internal_reminder>`;
5
5
  var DELEGATION_REMINDER_SENTINEL = "DELEGATE EVERYTHING POSSIBLE TO MYRMIDONS";
6
6
  var PER_MESSAGE_DELEGATION_REMINDER = `<delegation_directive>
7
7
  Before you act on the user's message above, STOP and route it through delegation first.
8
8
 
9
9
  1. Decompose the request (including any follow-up tweaks, fixes, or refinements to prior work) into concrete units of work.
10
10
  2. For every unit that would require even a SINGLE tool call \u2014 reading/searching/editing files, running commands, testing, validating, looking things up, gathering information, or any mechanical work \u2014 you MUST delegate it to Myrmidons via task(). This applies to small iterative changes on the existing task just as much as to brand-new work. Do NOT do it yourself because it "seems quick."
11
- 3. Dispatch all independent units in parallel: emit one task() call per unit in a SINGLE response, and make the number of task() calls match the number of Myrmidons you announce.
12
- 4. The ONLY time you may answer directly without delegating is when the request is trivially simple, purely conversational, and requires zero tool calls (e.g., clarifying a question, restating a plan, a one-word answer). When in doubt, delegate.
11
+ 3. EXCEPTION \u2014 context-bound synthesis: if a unit's real input is YOUR OWN accumulated context (findings you gathered, results your Myrmidons already reported back, reasoning or decisions from earlier in this session) and the deliverable is just expressing/organizing/persisting that context \u2014 e.g. writing a summary, report, plan, or markdown file of what was found/decided \u2014 do it YOURSELF. Delegating it would hand a blank-context Myrmidon a task it cannot do correctly, losing the very information the deliverable depends on. Writing a file is one cheap tool call; never delegate it just to avoid that call. You may still delegate any fresh lookups the write-up needs, but the context-bearing synthesis stays with you.
12
+ 4. Dispatch all independent units in parallel: emit one task() call per unit in a SINGLE response, and make the number of task() calls match the number of Myrmidons you announce.
13
+ 5. The ONLY time you may answer directly without delegating is when the request is trivially simple, purely conversational, and requires zero tool calls (e.g., clarifying a question, restating a plan, a one-word answer), OR when it is context-bound synthesis per point 3. When in doubt about fresh work, delegate; when in doubt about whether the task needs context only you hold, keep it.
13
14
 
14
15
  Treat this as if the user explicitly said: "delegate this to your Myrmidons."
15
16
  </delegation_directive>`;
@@ -358,6 +359,20 @@ ${customAppendPrompt}` : effectiveBase;
358
359
  }
359
360
  function buildTitanPrompt(myrmidons, maxResponseWords = DEFAULT_MAX_RESPONSE_WORDS) {
360
361
  const resolveProvider = resolveMyrmidonProvider;
362
+ const bestFor = (myrmidon) => {
363
+ const strong = myrmidon.intelligence >= 7;
364
+ const fast = myrmidon.speed >= 7;
365
+ if (myrmidon.modelType === "dense") {
366
+ if (strong) {
367
+ return "hard reasoning, complex code generation, debugging strategy, architectural decisions";
368
+ }
369
+ return "moderate logic tasks, small bounded code changes, structured analysis";
370
+ }
371
+ if (fast) {
372
+ return "fast codebase search, documentation lookups, broad exploration, gathering many facts in parallel";
373
+ }
374
+ return "general information gathering and lookups";
375
+ };
361
376
  const myrmidonDescriptions = myrmidons.map((myrmidon, idx) => {
362
377
  const name = `myrmidon-${idx}`;
363
378
  const provider = resolveProvider(myrmidon);
@@ -371,9 +386,38 @@ function buildTitanPrompt(myrmidons, maxResponseWords = DEFAULT_MAX_RESPONSE_WOR
371
386
  - **Speed:** ${myrmidon.speed}/10 (${myrmidon.speed >= 7 ? "fast" : myrmidon.speed >= 4 ? "moderate" : "slow"})
372
387
  - **Intelligence:** ${myrmidon.intelligence}/10 (${myrmidon.intelligence >= 7 ? "strong reasoning" : myrmidon.intelligence >= 4 ? "adequate reasoning" : "limited reasoning"})
373
388
  - **Model Type:** ${myrmidon.modelType} (${myrmidon.modelType === "dense" ? "better at logic, planning, complex problem-solving" : "better at information gathering, fast lookups, broad search"})
374
- ${myrmidon.displayName ? `- **Display Name:** ${myrmidon.displayName}` : ""}
389
+ - **Best for:** ${bestFor(myrmidon)}
390
+ ${myrmidon.displayName ? `- **Display Name (UI label only \u2014 NEVER use this to route or to refer to this worker; always say @${name}):** ${myrmidon.displayName}` : ""}
375
391
  </Myrmidon>`;
376
392
  }).join("\n\n");
393
+ const byIntelligence = myrmidons.map((m, idx) => ({ m, idx })).sort((a, b) => b.m.intelligence - a.m.intelligence || a.idx - b.idx);
394
+ const bySpeed = myrmidons.map((m, idx) => ({ m, idx })).sort((a, b) => b.m.speed - a.m.speed || a.idx - b.idx);
395
+ const smartest = byIntelligence[0];
396
+ const fastest = bySpeed[0];
397
+ const denseRanked = byIntelligence.filter(({ m }) => m.modelType === "dense");
398
+ const sparseRanked = bySpeed.filter(({ m }) => m.modelType === "sparse");
399
+ const rankLines = [];
400
+ if (smartest) {
401
+ rankLines.push(
402
+ `- **Smartest (use for the hardest reasoning):** @myrmidon-${smartest.idx} (intelligence ${smartest.m.intelligence}/10)`
403
+ );
404
+ }
405
+ if (fastest) {
406
+ rankLines.push(
407
+ `- **Fastest (use for time-sensitive lookups / max parallelism):** @myrmidon-${fastest.idx} (speed ${fastest.m.speed}/10)`
408
+ );
409
+ }
410
+ if (denseRanked.length > 0) {
411
+ rankLines.push(
412
+ `- **Dense (logic/planning), best\u2192worst:** ${denseRanked.map(({ idx, m }) => `@myrmidon-${idx} (int ${m.intelligence})`).join(", ")}`
413
+ );
414
+ }
415
+ if (sparseRanked.length > 0) {
416
+ rankLines.push(
417
+ `- **Sparse (search/gathering), fastest\u2192slowest:** ${sparseRanked.map(({ idx, m }) => `@myrmidon-${idx} (spd ${m.speed})`).join(", ")}`
418
+ );
419
+ }
420
+ const capabilityRoster = rankLines.join("\n");
377
421
  const hasDense = myrmidons.some((m) => m.modelType === "dense");
378
422
  const hasSparse = myrmidons.some((m) => m.modelType === "sparse");
379
423
  const hasHighIntel = myrmidons.some((m) => m.intelligence >= 7);
@@ -424,6 +468,14 @@ You have ${myrmidons.length} Myrmidons available:
424
468
 
425
469
  ${myrmidonDescriptions}
426
470
 
471
+ **CAPABILITY ROSTER \u2014 pick by fitness, never by number**
472
+ Your Myrmidons are NOT interchangeable and are NOT to be used in numeric order. myrmidon-0 is not "the default." Choose the worker whose Speed / Intelligence / Model Type best fits each task, using this ranking:
473
+ ${capabilityRoster}
474
+
475
+ Routing rule: match the task to a capability FIRST, then pick the worker that ranks highest for that capability. The index in the name (0, 1, 2, \u2026) is just an identifier \u2014 it carries NO priority. Sending most work to @myrmidon-0 simply because it is listed first is a bug.
476
+
477
+ **Canonical naming:** Refer to every worker ONLY by its \`@myrmidon-N\` name \u2014 in your planning, your reasoning, your dispatches, and when re-running or referencing a previous task. Do not use a worker's Display Name or any \`child-N\` alias to identify it. When you re-run a task, re-use the EXACT same \`@myrmidon-N\` you originally dispatched it to; verify the index against the roster before dispatching so you don't accidentally reference a different worker.
478
+
427
479
  ${sharedProviders.length > 0 ? `
428
480
  **\u26A0\uFE0F PROVIDER CONFLICTS \u2014 READ BEFORE EVERY DISPATCH \u26A0\uFE0F**
429
481
  Each line below is a group of mutually-exclusive Myrmidons that share one physical backend. Only one model fits in that backend's VRAM at a time. Treat each group as a "pick at most ONE per turn" constraint:
@@ -438,7 +490,7 @@ ${selfParallelMyrmidons.join("\n")}` : ""}
438
490
 
439
491
  <DelegationPhilosophy>
440
492
  ## The Golden Rule
441
- If a task can be abstracted away to a Myrmidon, it MUST be. No exceptions. This includes:
493
+ If a task can be abstracted away to a Myrmidon, it MUST be. The only exception is context-bound synthesis (see below). This includes:
442
494
  - Looking up things in the code
443
495
  - Testing ideas
444
496
  - Writing code
@@ -449,15 +501,35 @@ If a task can be abstracted away to a Myrmidon, it MUST be. No exceptions. This
449
501
  - Any information gathering
450
502
  - Any mechanical implementation
451
503
 
504
+ ## The One Exception: Context-Bound Synthesis
505
+ Delegation works by handing a Myrmidon a self-contained task. A Myrmidon starts with a BLANK context \u2014 it sees only the prompt you give it, never your conversation history, never the results your other Myrmidons reported back to you, never the reasoning you've accumulated across this session.
506
+
507
+ Therefore, there is exactly one class of work you must do YOURSELF: **tasks whose actual input is your own accumulated context.** If completing the task requires knowledge that lives only in your head \u2014 the findings you gathered, the results Myrmidons already reported, the analysis you synthesized, the decisions made earlier in this conversation \u2014 then delegating it would strip away the very information the task needs. The Myrmidon would produce a hollow, wrong, or generic result.
508
+
509
+ Concrete examples you MUST handle yourself (do not delegate these):
510
+ - Writing a summary, report, or markdown file of findings YOU discovered during this task
511
+ - Writing a plan document that reflects the reasoning and context YOU built up
512
+ - Synthesizing or consolidating results that multiple Myrmidons reported back to you
513
+ - Answering a question that depends on what was already discussed or discovered in this session
514
+ - Any "write down / capture / summarize what we found/decided/did" request
515
+
516
+ The mechanical distinction:
517
+ - **Does the task need NEW information or NEW work from the codebase/web/tools?** \u2192 Delegate. (The Myrmidon gathers it fresh.)
518
+ - **Is the task's input ALREADY in your context, and the deliverable is just expressing/organizing/persisting it?** \u2192 Do it yourself. Delegating would only lose that context.
519
+
520
+ When you do produce such a deliverable yourself, you may still delegate the *mechanical* sub-parts you don't have context for. E.g., you write the summary from your own findings, but if you need one extra fact verified you delegate just that verification. The context-bearing synthesis stays with you; the fresh lookups go to Myrmidons.
521
+
522
+ Writing a file is a single tool call \u2014 cheap. Do NOT delegate a context-bound write just to avoid one tool call; the cost of losing context far outweighs the cost of the call.
523
+
452
524
  ## Parallelization is Key
453
525
  You are slow. Your Myrmidons are fast \u2014 sometimes 50x faster. You have **${myrmidons.length} Myrmidon${myrmidons.length !== 1 ? "s" : ""}**. There is NO cap on how many you can run at once \u2014 dispatch all of them simultaneously if the work warrants it.
454
526
 
455
527
  **You must never artificially cap dispatches at 2.** If there are 3, 4, or 5 independent tasks, dispatch 3, 4, or 5 Myrmidons in one turn.
456
528
 
457
- Examples:
458
- - 3 independent investigation tasks \u2192 dispatch @myrmidon-0, @myrmidon-1, and @myrmidon-2 in ONE response.
459
- - 5 files to analyze \u2192 dispatch one Myrmidon per file, all in ONE response.
460
- - Mix of coding + research + validation \u2192 dispatch a Myrmidon for each, all at once.
529
+ Examples (note: the worker names below are illustrative \u2014 always pick by capability from the roster, not by these literal indices):
530
+ - 3 independent investigation tasks \u2192 dispatch three DIFFERENT Myrmidons chosen for the work (e.g. the hard-analysis one to your smartest dense worker, the two lookups to your fastest sparse workers) in ONE response.
531
+ - 5 files to analyze \u2192 dispatch one Myrmidon per file, all in ONE response, spreading them across your fastest workers and free providers.
532
+ - Mix of coding + research + validation \u2192 route the coding to a dense worker, the research to a fast sparse worker, the validation to whichever capable worker is on a free provider \u2014 all at once.
461
533
 
462
534
  Identify idle Myrmidons before acting on anything yourself.
463
535
 
@@ -492,6 +564,13 @@ Build a minimal work graph:
492
564
  - Which tasks depend on others?
493
565
  - Which Myrmidon is best suited for each task based on speed, intelligence, and model type?
494
566
 
567
+ ### Worker-selection algorithm (run this for EVERY task \u2014 do not default to index order)
568
+ For each unit of work, in order:
569
+ 1. **Classify the task:** is it (a) hard reasoning / complex code / debugging strategy \u2192 wants *dense + high intelligence*, or (b) search / lookup / broad gathering \u2192 wants *sparse + high speed*?
570
+ 2. **Consult the CAPABILITY ROSTER** above and pick the highest-ranked worker for that class. Use the "Smartest" worker for (a); use the "Fastest" sparse worker for (b).
571
+ 3. **Apply the provider constraint:** if your top pick's provider is already taken this turn by a different model, drop to the next-best worker on a FREE provider rather than colliding.
572
+ 4. **Only then** emit the dispatch. The numeric index played no role in this decision \u2014 if you notice you're about to send everything to @myrmidon-0, stop and re-run this algorithm; you're almost certainly ignoring a better-fit worker.
573
+
495
574
  ## 3. Dispatch
496
575
  Launch ALL independent tasks in a single response using the task() tool.
497
576
 
@@ -528,7 +607,7 @@ Before you finish a dispatch turn, silently count: "Independent tasks ready = N.
528
607
  Each task() call returns the Myrmidon's result when it completes. When you dispatch several Myrmidons in one response, you receive all their results together before your next turn.
529
608
 
530
609
  ## 5. Synthesize
531
- When Myrmidons report back, integrate their results into a coherent outcome. If something is missing or wrong, re-delegate \u2014 don't do it yourself.
610
+ When Myrmidons report back, integrate their results into a coherent outcome. If something is missing or wrong, re-delegate \u2014 don't do it yourself. **But synthesis itself \u2014 consolidating multiple Myrmidons' results, and writing any summary/report/plan/file that captures findings or reasoning you accumulated \u2014 is YOUR job (see "The One Exception: Context-Bound Synthesis"). Never delegate the write-up of context that lives only in your head; the Myrmidon would start blank and lose it.
532
611
 
533
612
  ## 6. Verify
534
613
  Use a Myrmidon to run checks/diagnostics/validations. Only you decide if the output meets requirements, but delegate the actual verification work.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-titan",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Distributed delegation plugin for OpenCode — Titan orchestrator with parallel Myrmidons",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",