opencode-titan 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/config/constants.d.ts +2 -2
- package/dist/config/schema.d.ts +4 -0
- package/dist/index.js +138 -27
- package/opencode-titan.schema.json +5 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,12 +133,17 @@ Here's a complete starting configuration:
|
|
|
133
133
|
> [!TIP]
|
|
134
134
|
> Mix providers to unlock real parallelism. Two Myrmidons running **different models** on the same provider run one after another; two Myrmidons on *different* providers run at the same time. Set `maxInstances` on a Myrmidon to run several copies of its *same* model in parallel on one provider.
|
|
135
135
|
|
|
136
|
+
> [!TIP]
|
|
137
|
+
> Running local models with fixed context windows? Set `maxContextLength` (in tokens) on those Myrmidons. Titan then knows each worker's budget and steers large or complex tasks away from small-context workers — avoiding the lossy mid-task history compaction that would otherwise wreck their output. Leave it off cloud/ample-capacity models.
|
|
138
|
+
|
|
136
139
|
## Meet the Agents
|
|
137
140
|
|
|
138
141
|
### 🧠 Titan — The Orchestrator
|
|
139
142
|
|
|
140
143
|
Titan is the most intelligent agent in the fleet, and by far the slowest and most expensive to run. It never reads a file, runs a search, or writes a line of code if a Myrmidon can do it instead. Its entire purpose is strategic: decompose the goal, route each task to the best-suited Myrmidon, gate the quality of what comes back, and synthesize the final result.
|
|
141
144
|
|
|
145
|
+
There are two deliberate exceptions where Titan works directly rather than delegating: **context-bound synthesis** (writing up findings, plans, or reports that live in its own accumulated context) and **foundational directive reads** — when you hand Titan a source-of-truth document to execute (a `PLAN.md`, spec, or task list), it reads that document itself, at full fidelity. Delegating the read would only return a lossy summary of the very directive meant to steer Titan's planning and routing.
|
|
146
|
+
|
|
142
147
|
<table>
|
|
143
148
|
<tr><td><b>Role</b></td><td><code>Planning, routing, quality-gating, and synthesis</code></td></tr>
|
|
144
149
|
<tr><td><b>Prompt</b></td><td><a href="src/agents/titan.ts"><code>titan.ts</code></a> — dynamically built from your fleet</td></tr>
|
|
@@ -177,6 +182,7 @@ Myrmidons are the hands. Each executes a delegated task and reports back concise
|
|
|
177
182
|
| `intelligence` | `number` (1–10) | ✅ | Reasoning capability rating; higher = better logic |
|
|
178
183
|
| `modelType` | `"dense"` \| `"sparse"` | ✅ | `dense` for logic/reasoning, `sparse` for search/info gathering |
|
|
179
184
|
| `maxInstances` | `number` (≥1) | | Max parallel instances Titan may run for this Myrmidon. Since instances share the same model (already in the provider's VRAM), they run concurrently on the same provider. Default: `1` |
|
|
185
|
+
| `maxContextLength` | `number` (≥1) | | Hard context-window limit (in tokens) for this Myrmidon's model. Mainly for locally hosted models with fixed windows. When set, Titan avoids handing this worker large/complex tasks that would exceed its budget (and force a lossy compaction). Omit for ample-capacity/cloud models. |
|
|
180
186
|
| `temperature` | `number` (0–2) | | Sampling temperature (default: `0.1`) |
|
|
181
187
|
| `variant` | `string` | | Selects a named model variant defined by the provider for this `model` (see [Model variants](#model-variants)). Must match a variant key the provider declares for the model; leave unset to use the model's defaults |
|
|
182
188
|
| `displayName` | `string` | | Friendly name shown in the UI |
|
|
@@ -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.
|
|
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. EXCEPTION 1 \u2014 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. EXCEPTION 2 \u2014 foundational directive reads: when the user hands you a source-of-truth doc (PLAN.md, spec, task list) to read and execute, READ IT YOURSELF at full fidelity \u2014 it drives how you plan and delegate, and a Myrmidon would only return a lossy summary of the very directive meant to steer you. (Ordinary file reads that just feed a worker's sub-task are still delegated.) 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. 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.\
|
|
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. EXCEPTION \u2014 foundational directive reads: if the user hands you a source-of-truth document to read and execute on (a PLAN.md, spec, design doc, task list, requirements file) that will drive how you plan and delegate the whole effort, READ IT YOURSELF at full fidelity. Delegating it returns a lossy summary of the very directive meant to steer your planning. This is narrow: it applies only when the document IS your controlling directive. An ordinary file read that merely feeds one worker's sub-task is still delegated.\n5. 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.\n6. 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, OR a foundational directive read per point 4. 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/config/schema.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare const MyrmidonConfigSchema: z.ZodObject<{
|
|
|
14
14
|
sparse: "sparse";
|
|
15
15
|
}>;
|
|
16
16
|
maxInstances: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
maxContextLength: z.ZodOptional<z.ZodNumber>;
|
|
17
18
|
temperature: z.ZodOptional<z.ZodNumber>;
|
|
18
19
|
variant: z.ZodOptional<z.ZodString>;
|
|
19
20
|
displayName: z.ZodOptional<z.ZodString>;
|
|
@@ -33,6 +34,7 @@ export declare const ChildAgentConfigSchema: z.ZodObject<{
|
|
|
33
34
|
sparse: "sparse";
|
|
34
35
|
}>;
|
|
35
36
|
maxInstances: z.ZodOptional<z.ZodNumber>;
|
|
37
|
+
maxContextLength: z.ZodOptional<z.ZodNumber>;
|
|
36
38
|
temperature: z.ZodOptional<z.ZodNumber>;
|
|
37
39
|
variant: z.ZodOptional<z.ZodString>;
|
|
38
40
|
displayName: z.ZodOptional<z.ZodString>;
|
|
@@ -66,6 +68,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
66
68
|
sparse: "sparse";
|
|
67
69
|
}>;
|
|
68
70
|
maxInstances: z.ZodOptional<z.ZodNumber>;
|
|
71
|
+
maxContextLength: z.ZodOptional<z.ZodNumber>;
|
|
69
72
|
temperature: z.ZodOptional<z.ZodNumber>;
|
|
70
73
|
variant: z.ZodOptional<z.ZodString>;
|
|
71
74
|
displayName: z.ZodOptional<z.ZodString>;
|
|
@@ -80,6 +83,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
80
83
|
sparse: "sparse";
|
|
81
84
|
}>;
|
|
82
85
|
maxInstances: z.ZodOptional<z.ZodNumber>;
|
|
86
|
+
maxContextLength: z.ZodOptional<z.ZodNumber>;
|
|
83
87
|
temperature: z.ZodOptional<z.ZodNumber>;
|
|
84
88
|
variant: z.ZodOptional<z.ZodString>;
|
|
85
89
|
displayName: z.ZodOptional<z.ZodString>;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.
|
|
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. EXCEPTION 1 \u2014 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. EXCEPTION 2 \u2014 foundational directive reads: when the user hands you a source-of-truth doc (PLAN.md, spec, task list) to read and execute, READ IT YOURSELF at full fidelity \u2014 it drives how you plan and delegate, and a Myrmidon would only return a lossy summary of the very directive meant to steer you. (Ordinary file reads that just feed a worker's sub-task are still delegated.) 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.
|
|
@@ -9,8 +9,9 @@ Before you act on the user's message above, STOP and route it through delegation
|
|
|
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
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.
|
|
13
|
-
5.
|
|
12
|
+
4. EXCEPTION \u2014 foundational directive reads: if the user hands you a source-of-truth document to read and execute on (a PLAN.md, spec, design doc, task list, requirements file) that will drive how you plan and delegate the whole effort, READ IT YOURSELF at full fidelity. Delegating it returns a lossy summary of the very directive meant to steer your planning. This is narrow: it applies only when the document IS your controlling directive. An ordinary file read that merely feeds one worker's sub-task is still delegated.
|
|
13
|
+
5. 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.
|
|
14
|
+
6. 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, OR a foundational directive read per point 4. When in doubt about fresh work, delegate; when in doubt about whether the task needs context only you hold, keep it.
|
|
14
15
|
|
|
15
16
|
Treat this as if the user explicitly said: "delegate this to your Myrmidons."
|
|
16
17
|
</delegation_directive>`;
|
|
@@ -35,6 +36,11 @@ var MyrmidonConfigSchema = z.object({
|
|
|
35
36
|
// Because instances share the same model (already loaded in the provider's
|
|
36
37
|
// VRAM), they can run concurrently on the same provider. Defaults to 1.
|
|
37
38
|
maxInstances: z.number().int().min(1).optional(),
|
|
39
|
+
// Maximum context window (in tokens) this Myrmidon's model can handle before
|
|
40
|
+
// it is forced to compact/truncate its history. Optional — mainly relevant
|
|
41
|
+
// for locally hosted models with fixed context limits. When present, Titan
|
|
42
|
+
// uses it to avoid handing large or complex tasks to small-context workers.
|
|
43
|
+
maxContextLength: z.number().int().min(1).optional(),
|
|
38
44
|
temperature: z.number().min(0).max(2).optional(),
|
|
39
45
|
variant: z.string().optional(),
|
|
40
46
|
displayName: z.string().min(1).optional(),
|
|
@@ -359,6 +365,20 @@ ${customAppendPrompt}` : effectiveBase;
|
|
|
359
365
|
}
|
|
360
366
|
function buildTitanPrompt(myrmidons, maxResponseWords = DEFAULT_MAX_RESPONSE_WORDS) {
|
|
361
367
|
const resolveProvider = resolveMyrmidonProvider;
|
|
368
|
+
const bestFor = (myrmidon) => {
|
|
369
|
+
const strong = myrmidon.intelligence >= 7;
|
|
370
|
+
const fast = myrmidon.speed >= 7;
|
|
371
|
+
if (myrmidon.modelType === "dense") {
|
|
372
|
+
if (strong) {
|
|
373
|
+
return "hard reasoning, complex code generation, debugging strategy, architectural decisions";
|
|
374
|
+
}
|
|
375
|
+
return "moderate logic tasks, small bounded code changes, structured analysis";
|
|
376
|
+
}
|
|
377
|
+
if (fast) {
|
|
378
|
+
return "fast codebase search, documentation lookups, broad exploration, gathering many facts in parallel";
|
|
379
|
+
}
|
|
380
|
+
return "general information gathering and lookups";
|
|
381
|
+
};
|
|
362
382
|
const myrmidonDescriptions = myrmidons.map((myrmidon, idx) => {
|
|
363
383
|
const name = `myrmidon-${idx}`;
|
|
364
384
|
const provider = resolveProvider(myrmidon);
|
|
@@ -372,9 +392,39 @@ function buildTitanPrompt(myrmidons, maxResponseWords = DEFAULT_MAX_RESPONSE_WOR
|
|
|
372
392
|
- **Speed:** ${myrmidon.speed}/10 (${myrmidon.speed >= 7 ? "fast" : myrmidon.speed >= 4 ? "moderate" : "slow"})
|
|
373
393
|
- **Intelligence:** ${myrmidon.intelligence}/10 (${myrmidon.intelligence >= 7 ? "strong reasoning" : myrmidon.intelligence >= 4 ? "adequate reasoning" : "limited reasoning"})
|
|
374
394
|
- **Model Type:** ${myrmidon.modelType} (${myrmidon.modelType === "dense" ? "better at logic, planning, complex problem-solving" : "better at information gathering, fast lookups, broad search"})
|
|
375
|
-
|
|
395
|
+
- **Best for:** ${bestFor(myrmidon)}${myrmidon.maxContextLength ? `
|
|
396
|
+
- **Context Window:** ~${myrmidon.maxContextLength.toLocaleString()} tokens (HARD limit \u2014 this worker cannot hold more than this. Do NOT hand it tasks whose instructions + expected exploration would exceed roughly this budget; give large/complex/many-file work to a bigger-context worker instead.)` : ""}
|
|
397
|
+
${myrmidon.displayName ? `- **Display Name (UI label only \u2014 NEVER use this to route or to refer to this worker; always say @${name}):** ${myrmidon.displayName}` : ""}
|
|
376
398
|
</Myrmidon>`;
|
|
377
399
|
}).join("\n\n");
|
|
400
|
+
const byIntelligence = myrmidons.map((m, idx) => ({ m, idx })).sort((a, b) => b.m.intelligence - a.m.intelligence || a.idx - b.idx);
|
|
401
|
+
const bySpeed = myrmidons.map((m, idx) => ({ m, idx })).sort((a, b) => b.m.speed - a.m.speed || a.idx - b.idx);
|
|
402
|
+
const smartest = byIntelligence[0];
|
|
403
|
+
const fastest = bySpeed[0];
|
|
404
|
+
const denseRanked = byIntelligence.filter(({ m }) => m.modelType === "dense");
|
|
405
|
+
const sparseRanked = bySpeed.filter(({ m }) => m.modelType === "sparse");
|
|
406
|
+
const rankLines = [];
|
|
407
|
+
if (smartest) {
|
|
408
|
+
rankLines.push(
|
|
409
|
+
`- **Smartest (use for the hardest reasoning):** @myrmidon-${smartest.idx} (intelligence ${smartest.m.intelligence}/10)`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
if (fastest) {
|
|
413
|
+
rankLines.push(
|
|
414
|
+
`- **Fastest (use for time-sensitive lookups / max parallelism):** @myrmidon-${fastest.idx} (speed ${fastest.m.speed}/10)`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
if (denseRanked.length > 0) {
|
|
418
|
+
rankLines.push(
|
|
419
|
+
`- **Dense (logic/planning), best\u2192worst:** ${denseRanked.map(({ idx, m }) => `@myrmidon-${idx} (int ${m.intelligence})`).join(", ")}`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (sparseRanked.length > 0) {
|
|
423
|
+
rankLines.push(
|
|
424
|
+
`- **Sparse (search/gathering), fastest\u2192slowest:** ${sparseRanked.map(({ idx, m }) => `@myrmidon-${idx} (spd ${m.speed})`).join(", ")}`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
const capabilityRoster = rankLines.join("\n");
|
|
378
428
|
const hasDense = myrmidons.some((m) => m.modelType === "dense");
|
|
379
429
|
const hasSparse = myrmidons.some((m) => m.modelType === "sparse");
|
|
380
430
|
const hasHighIntel = myrmidons.some((m) => m.intelligence >= 7);
|
|
@@ -414,6 +464,22 @@ ${myrmidon.displayName ? `- **Display Name:** ${myrmidon.displayName}` : ""}
|
|
|
414
464
|
const selfParallelMyrmidons = myrmidons.map((myrmidon, idx) => ({ myrmidon, idx })).filter(({ myrmidon }) => (myrmidon.maxInstances ?? 1) > 1).map(
|
|
415
465
|
({ myrmidon, idx }) => `- @myrmidon-${idx}: up to ${myrmidon.maxInstances} instances in parallel (same model on ${resolveProvider(myrmidon)} \u2014 safe to load once, run many)`
|
|
416
466
|
);
|
|
467
|
+
const contextLimitedMyrmidons = myrmidons.map((myrmidon, idx) => ({ myrmidon, idx })).filter(({ myrmidon }) => typeof myrmidon.maxContextLength === "number").sort(
|
|
468
|
+
(a, b) => (a.myrmidon.maxContextLength ?? 0) - (b.myrmidon.maxContextLength ?? 0)
|
|
469
|
+
).map(
|
|
470
|
+
({ myrmidon, idx }) => `- @myrmidon-${idx}: ~${(myrmidon.maxContextLength ?? 0).toLocaleString()} tokens`
|
|
471
|
+
);
|
|
472
|
+
const contextBudgetGuidance = contextLimitedMyrmidons.length > 0 ? `
|
|
473
|
+
## Context-Window Budgets
|
|
474
|
+
Some Myrmidons declare a HARD context window \u2014 the maximum history their model can hold before it is forced to compact or truncate. A Myrmidon that compacts mid-task loses information and produces unreliable results, so you must never push one past its budget. Match task size to context budget:
|
|
475
|
+
|
|
476
|
+
${contextLimitedMyrmidons.join("\n")}
|
|
477
|
+
|
|
478
|
+
- Estimate each task's footprint: your instructions + the files/output the Myrmidon must read and produce to finish it. Route large, complex, or many-file tasks to workers with a large (or unspecified) context window; reserve small-context workers for tightly-scoped, self-contained tasks.
|
|
479
|
+
- When a task is too big for the only capable worker's context, SPLIT it into smaller bounded sub-tasks that each fit \u2014 do not send one oversized prompt and hope it fits.
|
|
480
|
+
- Do NOT, however, instruct a Myrmidon to artificially cut its work short to "save context." Scope the task to fit; let the worker do that scoped task fully.
|
|
481
|
+
- Myrmidons without a declared context window are assumed to have ample capacity (typically cloud-hosted) \u2014 no special sizing needed.
|
|
482
|
+
` : "";
|
|
417
483
|
return `<Role>
|
|
418
484
|
You are Titan \u2014 the most intelligent and capable agent in this system, but also by far the slowest. You are borderline unusably slow on this hardware. Your survival depends on one rule:
|
|
419
485
|
|
|
@@ -425,6 +491,14 @@ You have ${myrmidons.length} Myrmidons available:
|
|
|
425
491
|
|
|
426
492
|
${myrmidonDescriptions}
|
|
427
493
|
|
|
494
|
+
**CAPABILITY ROSTER \u2014 pick by fitness, never by number**
|
|
495
|
+
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:
|
|
496
|
+
${capabilityRoster}
|
|
497
|
+
|
|
498
|
+
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.
|
|
499
|
+
|
|
500
|
+
**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.
|
|
501
|
+
|
|
428
502
|
${sharedProviders.length > 0 ? `
|
|
429
503
|
**\u26A0\uFE0F PROVIDER CONFLICTS \u2014 READ BEFORE EVERY DISPATCH \u26A0\uFE0F**
|
|
430
504
|
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:
|
|
@@ -470,15 +544,26 @@ When you do produce such a deliverable yourself, you may still delegate the *mec
|
|
|
470
544
|
|
|
471
545
|
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.
|
|
472
546
|
|
|
547
|
+
## The Second Exception: Foundational Directive Reads
|
|
548
|
+
There is one more class of work you must do YOURSELF: **reading the source-of-truth document that will drive your own planning and delegation.** When the user hands you a foundational artifact \u2014 a PLAN.md, spec, design doc, task list, requirements file, or similar \u2014 and asks you to read it and execute on it, that document IS the input to YOUR job. It controls how you decompose the work and which Myrmidons you route it to.
|
|
549
|
+
|
|
550
|
+
If you delegate that read, a Myrmidon returns a lossy, summarized paraphrase \u2014 and you would then plan the entire effort off a degraded copy of the very directive that's supposed to steer you. The information lost in summarization is exactly the detail you need to delegate correctly. So you read it yourself, at full fidelity, then plan and delegate from the complete picture.
|
|
551
|
+
|
|
552
|
+
This is a NARROW exception \u2014 it is not license to read files whenever you feel like it. It applies only when the document is the controlling directive for the current effort. Distinguish carefully:
|
|
553
|
+
- **User points you at a plan/spec/instructions to drive the whole task** (e.g. "read PLAN.md and start executing it", "follow the spec in DESIGN.md") \u2192 read it YOURSELF. It's the foundation of your planning.
|
|
554
|
+
- **You need the contents of some file as raw material for a delegated sub-task** (e.g. "what does this util do", "find where X is defined", "refactor this module") \u2192 DELEGATE the read. It's fresh lookup work, not your controlling directive.
|
|
555
|
+
|
|
556
|
+
Rule of thumb: if summarizing the document away would compromise how YOU plan and route, read it yourself. If it's just one more fact-gathering step feeding a worker's task, delegate it.
|
|
557
|
+
|
|
473
558
|
## Parallelization is Key
|
|
474
559
|
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.
|
|
475
560
|
|
|
476
561
|
**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.
|
|
477
562
|
|
|
478
|
-
Examples:
|
|
479
|
-
- 3 independent investigation tasks \u2192 dispatch
|
|
480
|
-
- 5 files to analyze \u2192 dispatch one Myrmidon per file, all in ONE response.
|
|
481
|
-
- Mix of coding + research + validation \u2192
|
|
563
|
+
Examples (note: the worker names below are illustrative \u2014 always pick by capability from the roster, not by these literal indices):
|
|
564
|
+
- 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.
|
|
565
|
+
- 5 files to analyze \u2192 dispatch one Myrmidon per file, all in ONE response, spreading them across your fastest workers and free providers.
|
|
566
|
+
- 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.
|
|
482
567
|
|
|
483
568
|
Identify idle Myrmidons before acting on anything yourself.
|
|
484
569
|
|
|
@@ -500,7 +585,7 @@ ${capabilityGuidance.join("\n")}
|
|
|
500
585
|
- Each Myrmidon should receive a clear, bounded objective
|
|
501
586
|
- Reference file paths and line numbers instead of pasting full file contents
|
|
502
587
|
- Provide enough context for the Myrmidon to succeed independently
|
|
503
|
-
|
|
588
|
+
${contextBudgetGuidance}
|
|
504
589
|
</DelegationPhilosophy>
|
|
505
590
|
|
|
506
591
|
<Workflow>
|
|
@@ -513,6 +598,13 @@ Build a minimal work graph:
|
|
|
513
598
|
- Which tasks depend on others?
|
|
514
599
|
- Which Myrmidon is best suited for each task based on speed, intelligence, and model type?
|
|
515
600
|
|
|
601
|
+
### Worker-selection algorithm (run this for EVERY task \u2014 do not default to index order)
|
|
602
|
+
For each unit of work, in order:
|
|
603
|
+
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*?
|
|
604
|
+
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).
|
|
605
|
+
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.
|
|
606
|
+
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.
|
|
607
|
+
|
|
516
608
|
## 3. Dispatch
|
|
517
609
|
Launch ALL independent tasks in a single response using the task() tool.
|
|
518
610
|
|
|
@@ -742,14 +834,22 @@ var OpenCodeDistributedDelegation = async (ctx) => {
|
|
|
742
834
|
"[opencode-titan] WARN: No Myrmidons configured. Titan cannot delegate without Myrmidons. Add myrmidons to your config."
|
|
743
835
|
);
|
|
744
836
|
}
|
|
745
|
-
const
|
|
837
|
+
const taskLocks = /* @__PURE__ */ new Map();
|
|
746
838
|
const lockKey = (sessionID, callID) => `${sessionID}:${callID}`;
|
|
747
839
|
const releaseLock = (key) => {
|
|
748
|
-
const
|
|
749
|
-
if (!
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
840
|
+
const entry = taskLocks.get(key);
|
|
841
|
+
if (!entry) return;
|
|
842
|
+
taskLocks.delete(key);
|
|
843
|
+
entry.cancelled = true;
|
|
844
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
845
|
+
entry.release?.();
|
|
846
|
+
};
|
|
847
|
+
const releaseLocksForSession = (sessionID) => {
|
|
848
|
+
for (const [key, entry] of [...taskLocks.entries()]) {
|
|
849
|
+
if (entry.sessionID === sessionID) {
|
|
850
|
+
releaseLock(key);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
753
853
|
};
|
|
754
854
|
return {
|
|
755
855
|
name: "opencode-titan",
|
|
@@ -770,14 +870,25 @@ var OpenCodeDistributedDelegation = async (ctx) => {
|
|
|
770
870
|
if (!lockInfo) return;
|
|
771
871
|
const { provider, model, maxInstances } = lockInfo;
|
|
772
872
|
const key = lockKey(input.sessionID, input.callID);
|
|
773
|
-
if (
|
|
873
|
+
if (taskLocks.has(key)) return;
|
|
874
|
+
const entry = {
|
|
875
|
+
provider,
|
|
876
|
+
sessionID: input.sessionID,
|
|
877
|
+
cancelled: false
|
|
878
|
+
};
|
|
879
|
+
taskLocks.set(key, entry);
|
|
774
880
|
const release = await providerLocks.acquire(
|
|
775
881
|
provider,
|
|
776
882
|
model,
|
|
777
883
|
maxInstances
|
|
778
884
|
);
|
|
885
|
+
if (entry.cancelled) {
|
|
886
|
+
release();
|
|
887
|
+
taskLocks.delete(key);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
779
890
|
const timer = setTimeout(() => {
|
|
780
|
-
if (
|
|
891
|
+
if (taskLocks.has(key)) {
|
|
781
892
|
console.warn(
|
|
782
893
|
`[opencode-titan] WARN: provider lock for "${provider}" (${key}) auto-released after timeout; the task may have been interrupted.`
|
|
783
894
|
);
|
|
@@ -785,7 +896,8 @@ var OpenCodeDistributedDelegation = async (ctx) => {
|
|
|
785
896
|
}
|
|
786
897
|
}, PROVIDER_LOCK_TIMEOUT_MS);
|
|
787
898
|
timer.unref?.();
|
|
788
|
-
|
|
899
|
+
entry.release = release;
|
|
900
|
+
entry.timer = timer;
|
|
789
901
|
},
|
|
790
902
|
// Release the provider lock once the subagent task completes (or errors).
|
|
791
903
|
"tool.execute.after": async (input) => {
|
|
@@ -862,16 +974,15 @@ ${DELEGATION_REMINDER}`;
|
|
|
862
974
|
// Clean up session tracking on deletion
|
|
863
975
|
event: async (input) => {
|
|
864
976
|
const event = input.event;
|
|
977
|
+
const props = event.properties;
|
|
978
|
+
const sessionID = props?.sessionID ?? props?.info?.id;
|
|
979
|
+
if (sessionID && (event.type === "session.idle" || event.type === "session.error" || event.type === "session.deleted")) {
|
|
980
|
+
releaseLocksForSession(sessionID);
|
|
981
|
+
}
|
|
865
982
|
if (event.type === "session.deleted") {
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
sessionAgentMap.delete(sessionID);
|
|
870
|
-
for (const key of [...heldLocks.keys()]) {
|
|
871
|
-
if (key.startsWith(`${sessionID}:`)) {
|
|
872
|
-
releaseLock(key);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
983
|
+
const deletedID = props?.info?.id ?? props?.sessionID;
|
|
984
|
+
if (deletedID) {
|
|
985
|
+
sessionAgentMap.delete(deletedID);
|
|
875
986
|
}
|
|
876
987
|
}
|
|
877
988
|
}
|
|
@@ -98,6 +98,11 @@
|
|
|
98
98
|
"minimum": 1,
|
|
99
99
|
"description": "Maximum number of parallel instances Titan may run for this Myrmidon. Since instances share the same model (already loaded in the provider's VRAM), they can run concurrently on the same provider. Defaults to 1."
|
|
100
100
|
},
|
|
101
|
+
"maxContextLength": {
|
|
102
|
+
"type": "integer",
|
|
103
|
+
"minimum": 1,
|
|
104
|
+
"description": "Maximum context window (in tokens) this Myrmidon's model can handle before it is forced to compact or truncate its history. Optional — mainly relevant for locally hosted models with fixed context limits. When present, Titan uses it to avoid handing large or complex tasks to small-context workers."
|
|
105
|
+
},
|
|
101
106
|
"temperature": {
|
|
102
107
|
"type": "number",
|
|
103
108
|
"minimum": 0,
|