opencode-titan 0.1.3 → 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 +76 -23
- 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(),
|
|
@@ -386,7 +392,8 @@ function buildTitanPrompt(myrmidons, maxResponseWords = DEFAULT_MAX_RESPONSE_WOR
|
|
|
386
392
|
- **Speed:** ${myrmidon.speed}/10 (${myrmidon.speed >= 7 ? "fast" : myrmidon.speed >= 4 ? "moderate" : "slow"})
|
|
387
393
|
- **Intelligence:** ${myrmidon.intelligence}/10 (${myrmidon.intelligence >= 7 ? "strong reasoning" : myrmidon.intelligence >= 4 ? "adequate reasoning" : "limited reasoning"})
|
|
388
394
|
- **Model Type:** ${myrmidon.modelType} (${myrmidon.modelType === "dense" ? "better at logic, planning, complex problem-solving" : "better at information gathering, fast lookups, broad search"})
|
|
389
|
-
- **Best for:** ${bestFor(myrmidon)}
|
|
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.)` : ""}
|
|
390
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}` : ""}
|
|
391
398
|
</Myrmidon>`;
|
|
392
399
|
}).join("\n\n");
|
|
@@ -457,6 +464,22 @@ ${myrmidon.displayName ? `- **Display Name (UI label only \u2014 NEVER use this
|
|
|
457
464
|
const selfParallelMyrmidons = myrmidons.map((myrmidon, idx) => ({ myrmidon, idx })).filter(({ myrmidon }) => (myrmidon.maxInstances ?? 1) > 1).map(
|
|
458
465
|
({ myrmidon, idx }) => `- @myrmidon-${idx}: up to ${myrmidon.maxInstances} instances in parallel (same model on ${resolveProvider(myrmidon)} \u2014 safe to load once, run many)`
|
|
459
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
|
+
` : "";
|
|
460
483
|
return `<Role>
|
|
461
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:
|
|
462
485
|
|
|
@@ -521,6 +544,17 @@ When you do produce such a deliverable yourself, you may still delegate the *mec
|
|
|
521
544
|
|
|
522
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.
|
|
523
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
|
+
|
|
524
558
|
## Parallelization is Key
|
|
525
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.
|
|
526
560
|
|
|
@@ -551,7 +585,7 @@ ${capabilityGuidance.join("\n")}
|
|
|
551
585
|
- Each Myrmidon should receive a clear, bounded objective
|
|
552
586
|
- Reference file paths and line numbers instead of pasting full file contents
|
|
553
587
|
- Provide enough context for the Myrmidon to succeed independently
|
|
554
|
-
|
|
588
|
+
${contextBudgetGuidance}
|
|
555
589
|
</DelegationPhilosophy>
|
|
556
590
|
|
|
557
591
|
<Workflow>
|
|
@@ -800,14 +834,22 @@ var OpenCodeDistributedDelegation = async (ctx) => {
|
|
|
800
834
|
"[opencode-titan] WARN: No Myrmidons configured. Titan cannot delegate without Myrmidons. Add myrmidons to your config."
|
|
801
835
|
);
|
|
802
836
|
}
|
|
803
|
-
const
|
|
837
|
+
const taskLocks = /* @__PURE__ */ new Map();
|
|
804
838
|
const lockKey = (sessionID, callID) => `${sessionID}:${callID}`;
|
|
805
839
|
const releaseLock = (key) => {
|
|
806
|
-
const
|
|
807
|
-
if (!
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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
|
+
}
|
|
811
853
|
};
|
|
812
854
|
return {
|
|
813
855
|
name: "opencode-titan",
|
|
@@ -828,14 +870,25 @@ var OpenCodeDistributedDelegation = async (ctx) => {
|
|
|
828
870
|
if (!lockInfo) return;
|
|
829
871
|
const { provider, model, maxInstances } = lockInfo;
|
|
830
872
|
const key = lockKey(input.sessionID, input.callID);
|
|
831
|
-
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);
|
|
832
880
|
const release = await providerLocks.acquire(
|
|
833
881
|
provider,
|
|
834
882
|
model,
|
|
835
883
|
maxInstances
|
|
836
884
|
);
|
|
885
|
+
if (entry.cancelled) {
|
|
886
|
+
release();
|
|
887
|
+
taskLocks.delete(key);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
837
890
|
const timer = setTimeout(() => {
|
|
838
|
-
if (
|
|
891
|
+
if (taskLocks.has(key)) {
|
|
839
892
|
console.warn(
|
|
840
893
|
`[opencode-titan] WARN: provider lock for "${provider}" (${key}) auto-released after timeout; the task may have been interrupted.`
|
|
841
894
|
);
|
|
@@ -843,7 +896,8 @@ var OpenCodeDistributedDelegation = async (ctx) => {
|
|
|
843
896
|
}
|
|
844
897
|
}, PROVIDER_LOCK_TIMEOUT_MS);
|
|
845
898
|
timer.unref?.();
|
|
846
|
-
|
|
899
|
+
entry.release = release;
|
|
900
|
+
entry.timer = timer;
|
|
847
901
|
},
|
|
848
902
|
// Release the provider lock once the subagent task completes (or errors).
|
|
849
903
|
"tool.execute.after": async (input) => {
|
|
@@ -920,16 +974,15 @@ ${DELEGATION_REMINDER}`;
|
|
|
920
974
|
// Clean up session tracking on deletion
|
|
921
975
|
event: async (input) => {
|
|
922
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
|
+
}
|
|
923
982
|
if (event.type === "session.deleted") {
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
sessionAgentMap.delete(sessionID);
|
|
928
|
-
for (const key of [...heldLocks.keys()]) {
|
|
929
|
-
if (key.startsWith(`${sessionID}:`)) {
|
|
930
|
-
releaseLock(key);
|
|
931
|
-
}
|
|
932
|
-
}
|
|
983
|
+
const deletedID = props?.info?.id ?? props?.sessionID;
|
|
984
|
+
if (deletedID) {
|
|
985
|
+
sessionAgentMap.delete(deletedID);
|
|
933
986
|
}
|
|
934
987
|
}
|
|
935
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,
|