@wrongstack/core 0.308.1 → 0.308.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.
@@ -69,6 +69,7 @@ TIMEOUT_PREEMPT_FRACTION, } from './subagent-budget.js';
69
69
  export { assignNickname } from './subagent-nicknames.js';
70
70
  export { formatSubagentStructuredReport, MAX_SUBAGENT_STRUCTURED_REPORT_CHARS, makeSubagentResultTool, normalizeSubagentStructuredReport, readSubagentStructuredReport, SUBAGENT_STRUCTURED_REPORT_META_KEY, } from './subagent-result-tool.js';
71
71
  export { startTechStackConsumer, type TechStackConsumerOptions, } from './techstack-mailbox-consumer.js';
72
+ export { composeBoundedTaskDescription, type TaskBoundary, parseTaskBoundary, renderTaskBoundaryBlock, taskBoundarySchemaProperties, } from './task-boundary.js';
72
73
  export { type FleetWorktreePolicy, resolveSubagentWorktreeDecision, subagentNeedsWorktree, WorktreeIntegrationError, type WorktreeIsolationDecision, type WorktreeTaskRunnerOptions, type WorktreeTaskStateUpdate, wrapSubagentRunnerWithWorktrees, } from './worktree-task-runner.js';
73
74
  export { collabInjectMiddleware, collabPauseMiddleware, } from '../middleware/collab-pause.js';
74
75
  export { AdaptiveConcurrencyController, type AdaptiveConcurrencyState, } from './adaptive-concurrency.js';
@@ -9607,6 +9607,7 @@ var CollabSession = class extends EventEmitter {
9607
9607
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
9608
9608
  ${f.content}`).join("\n\n");
9609
9609
  return `You are BugHunter. Scan the following files for bugs and code smells.
9610
+ This is an analysis role: do not edit the target files; report findings only.
9610
9611
 
9611
9612
  Target files:
9612
9613
  ${fileContents}
@@ -9625,6 +9626,7 @@ Important: emit each finding as soon as you find it. Do not batch or wait until
9625
9626
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
9626
9627
  ${f.content}`).join("\n\n");
9627
9628
  return `You are RefactorPlanner. Plan refactorings for the following files.
9629
+ This is an analysis role: do not edit the target files; emit the plan only.
9628
9630
 
9629
9631
  Target files:
9630
9632
  ${fileContents}
@@ -9646,6 +9648,7 @@ Emit each plan immediately. Do not wait until planning is complete.`;
9646
9648
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
9647
9649
  ${f.content}`).join("\n\n");
9648
9650
  return `You are Critic. Evaluate bug findings and refactor plans.
9651
+ This is an analysis role: do not edit the target files; emit evaluations only.
9649
9652
 
9650
9653
  Target files:
9651
9654
  ${fileContents}
@@ -10481,6 +10484,80 @@ var FLEET_ROSTER_WITHACP = {
10481
10484
  ...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
10482
10485
  };
10483
10486
 
10487
+ // src/coordination/task-boundary.ts
10488
+ var PLACEHOLDER_VALUES = /* @__PURE__ */ new Set([
10489
+ "n/a",
10490
+ "na",
10491
+ "none",
10492
+ "nothing",
10493
+ "tbd",
10494
+ "todo",
10495
+ "unknown",
10496
+ "unspecified",
10497
+ "-",
10498
+ "\u2014",
10499
+ ".",
10500
+ "as above",
10501
+ "same as above",
10502
+ "see above",
10503
+ "see task",
10504
+ "same as task"
10505
+ ]);
10506
+ var isPlaceholder = (value) => PLACEHOLDER_VALUES.has(value.trim().toLowerCase());
10507
+ var MIN_SCOPE_CHARS = 8;
10508
+ var MIN_NON_GOAL_CHARS = 3;
10509
+ function parseTaskBoundary(raw) {
10510
+ const scope = typeof raw.scope === "string" ? raw.scope.trim() : "";
10511
+ if (scope.length < MIN_SCOPE_CHARS) {
10512
+ return {
10513
+ ok: false,
10514
+ error: `\`scope\` is missing or too vague \u2014 state in one concrete sentence what work this task covers (files, components, or commands in-bounds).`,
10515
+ hint: 'Example \u2014 scope: "Audit packages/core/src/parser/*.ts for unhandled token errors and report findings."'
10516
+ };
10517
+ }
10518
+ if (!Array.isArray(raw.outOfScope) || raw.outOfScope.length === 0) {
10519
+ return {
10520
+ ok: false,
10521
+ error: "`outOfScope` must be an array with at least one explicit non-goal \u2014 things the worker must NOT do.",
10522
+ hint: 'Example \u2014 outOfScope: ["Do not modify files outside packages/core", "Do not fix the bugs you find, only report them"].'
10523
+ };
10524
+ }
10525
+ const concrete = raw.outOfScope.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length >= MIN_NON_GOAL_CHARS && !isPlaceholder(item));
10526
+ if (concrete.length === 0) {
10527
+ return {
10528
+ ok: false,
10529
+ error: 'Every `outOfScope` entry was a placeholder ("none", "n/a", \u2026). Name at least one concrete non-goal: files or areas not to touch, changes not to make, features not to add.',
10530
+ hint: 'There is always an edge worth stating \u2014 "read-only, no edits", "no dependency changes", "do not touch other packages". If truly nothing comes to mind, the task is not decomposed enough yet.'
10531
+ };
10532
+ }
10533
+ return { ok: true, boundary: { scope, outOfScope: concrete } };
10534
+ }
10535
+ function renderTaskBoundaryBlock(boundary) {
10536
+ return [
10537
+ "\u2500\u2500 TASK BOUNDARY (hard contract \u2014 these lines define your edges) \u2500\u2500",
10538
+ `Scope (what this task covers):
10539
+ ${boundary.scope}`,
10540
+ `Out of scope (explicit non-goals \u2014 do NOT do any of these):
10541
+ ${boundary.outOfScope.map((item) => `- ${item}`).join("\n")}`
10542
+ ].join("\n");
10543
+ }
10544
+ function composeBoundedTaskDescription(objective, boundary) {
10545
+ return `${objective.trim()}
10546
+
10547
+ ${renderTaskBoundaryBlock(boundary)}`;
10548
+ }
10549
+ var taskBoundarySchemaProperties = {
10550
+ scope: {
10551
+ type: "string",
10552
+ description: "REQUIRED. One concrete sentence stating what work this task covers \u2014 the in-bounds. The call is rejected without it."
10553
+ },
10554
+ outOfScope: {
10555
+ type: "array",
10556
+ items: { type: "string", minLength: 1 },
10557
+ description: 'REQUIRED. At least one explicit non-goal the worker must NOT do (files/areas not to touch, changes not to make, features not to add). Placeholders like "none" are rejected.'
10558
+ }
10559
+ };
10560
+
10484
10561
  // src/coordination/delegate-tool.ts
10485
10562
  function createDelegateTool(opts) {
10486
10563
  const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
@@ -10490,8 +10567,9 @@ function createDelegateTool(opts) {
10490
10567
  properties: {
10491
10568
  task: {
10492
10569
  type: "string",
10493
- description: "What the subagent should do \u2014 natural language, complete sentence(s)."
10570
+ description: "The objective \u2014 what the subagent should do, natural language, complete sentence(s). Pair it with the required `scope` and `outOfScope` boundary fields."
10494
10571
  },
10572
+ ...taskBoundarySchemaProperties,
10495
10573
  role: {
10496
10574
  type: "string",
10497
10575
  description: rosterIds.length > 0 ? "Roster role id. Common: bug-hunter, security-scanner, refactor-planner, critic, audit-log, executor, shadow-agent, architect." : "No roster configured \u2014 pass `name` instead."
@@ -10549,12 +10627,12 @@ function createDelegateTool(opts) {
10549
10627
  description: "Max fresh-worker continuations after budget exhaustion. Default 1. Each gets the prior partial report."
10550
10628
  }
10551
10629
  },
10552
- required: ["task"]
10630
+ required: ["task", "scope", "outOfScope"]
10553
10631
  };
10554
10632
  return {
10555
10633
  name: "delegate",
10556
10634
  description: "Hand a piece of work to a subagent and block until it returns. This call is synchronous: the leader's iteration pauses for the full duration of the subagent's run. (Multiple `delegate` calls fired in the same assistant turn still parallelize through the provider's parallel-tool-call surface, but each one eats wall-clock time \u2014 so for fan-out you actually control, reach for the async path below.) Use `delegate` when your next step genuinely needs the subagent's verdict \u2014 a review, a fact-check, a sign-off. Has own context, own LLM call, auto-extending budget, and a partial-completion handoff path (maxHandoffs, default 1). Workers cannot recursively spawn.\n\n**Do NOT use `delegate` for long-running work.** While `delegate` is in flight, the leader is fully blocked \u2014 it cannot act on other tools, read mail, or react to the user. If the work might run for tens of minutes or hours (multi-file refactor, monorepo audit, long-running build/test, sweeping migration), the blocking call wastes the leader's time. Use the async tool family instead: `spawn_subagent` to create each worker (returns a `subagentId` immediately), `assign_task` to queue work on it (returns a `taskId` immediately), then `await_tasks` to retrieve results later. The leader keeps doing other work while the worker churns, and a worker that realizes its task will run long can mail the leader (type `steer` or `ask` via `mail_send`) saying *\"my task is going to run long, please spawn a subagent instead\"* so the leader re-dispatches asynchronously instead of waiting.\n\n**Do NOT use `delegate` for fan-out you control.** Multiple sequential `delegate` calls each block the leader, wasting wall-clock time. For independent investigations you want to run in parallel \u2014 security scan + bug hunt + perf review on the same PR \u2014 use the async tool family: `spawn_subagent` to create each worker (returns a `subagentId` immediately), `assign_task` to queue work on it (returns a `taskId` immediately), then the `await_tasks` tool with `{mode: 'any'}` to fold the first useful result into the next decision while the rest keep churning. Reach for `delegate` only when the result gates your next move AND the work is short enough that blocking the leader is acceptable.",
10557
- usageHint: "Set `task` to a complete instruction. Pick `role` from roster or pass `name` for free-form. Reach for `delegate` only when the result gates your next move AND the work is short enough that blocking the leader is acceptable (minutes, not hours). For long-running work or fan-out you control, use `spawn_subagent` + `assign_task` + `await_tasks` instead. Raise `maxHandoffs` (default 1, cap 8) for multi-day or multi-refactor tasks; pass larger `timeoutMs`/`maxIterations`/`maxToolCalls` only when needed.",
10635
+ usageHint: "Set `task` to the objective, then make the edges explicit: `scope` (what the work covers) and `outOfScope` (at least one concrete non-goal) are REQUIRED \u2014 the call is rejected without them, and the worker treats the rendered boundary block as a hard contract. Pick `role` from roster or pass `name` for free-form. Reach for `delegate` only when the result gates your next move AND the work is short enough that blocking the leader is acceptable (minutes, not hours). For long-running work or fan-out you control, use `spawn_subagent` + `assign_task` + `await_tasks` instead. Raise `maxHandoffs` (default 1, cap 8) for multi-day or multi-refactor tasks; pass larger `timeoutMs`/`maxIterations`/`maxToolCalls` only when needed.",
10558
10636
  permission: "auto",
10559
10637
  mutating: false,
10560
10638
  managesOwnTimeout: true,
@@ -10574,6 +10652,14 @@ function createDelegateTool(opts) {
10574
10652
  error: "Delegation cancelled before spawn \u2014 the run was interrupted."
10575
10653
  };
10576
10654
  }
10655
+ const boundary = parseTaskBoundary(i);
10656
+ if (!boundary.ok) {
10657
+ return {
10658
+ ok: false,
10659
+ error: `delegate rejected \u2014 task boundary incomplete: ${boundary.error}`,
10660
+ hint: boundary.hint
10661
+ };
10662
+ }
10577
10663
  const target = i.role ?? i.name ?? "subagent";
10578
10664
  const launchModePreface = [
10579
10665
  "Launch-mode guidance (delegate): you were launched via the synchronous `delegate` tool, so the leader is blocked on this call for the full duration of your run.",
@@ -10644,7 +10730,8 @@ function createDelegateTool(opts) {
10644
10730
  const dir = director;
10645
10731
  const maxHandoffs = Math.min(8, Math.max(0, Math.floor(i.maxHandoffs ?? 1)));
10646
10732
  const handoffs = [];
10647
- let delegatedTask = i.task;
10733
+ const baseBrief = composeBoundedTaskDescription(i.task, boundary.boundary);
10734
+ let delegatedTask = baseBrief;
10648
10735
  let handoffCount = 0;
10649
10736
  for (; ; ) {
10650
10737
  const attemptConfig = (() => {
@@ -10784,7 +10871,7 @@ function createDelegateTool(opts) {
10784
10871
  remainingWork: continuation.remainingWork
10785
10872
  });
10786
10873
  handoffCount += 1;
10787
- delegatedTask = buildHandoffTask(i.task, continuation, handoffCount, maxHandoffs);
10874
+ delegatedTask = buildHandoffTask(baseBrief, continuation, handoffCount, maxHandoffs);
10788
10875
  continue;
10789
10876
  }
10790
10877
  const incomplete = result.report?.completion === "partial";
@@ -12688,8 +12775,9 @@ function makeAssignTool(director) {
12688
12775
  description: {
12689
12776
  type: "string",
12690
12777
  minLength: 1,
12691
- description: "The task in natural language \u2014 what you want this subagent to do."
12778
+ description: "The objective in natural language \u2014 what you want this subagent to do. Pair it with the required `scope` and `outOfScope` boundary fields."
12692
12779
  },
12780
+ ...taskBoundarySchemaProperties,
12693
12781
  maxToolCalls: {
12694
12782
  type: "number",
12695
12783
  minimum: 1,
@@ -12697,20 +12785,28 @@ function makeAssignTool(director) {
12697
12785
  },
12698
12786
  timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
12699
12787
  },
12700
- required: ["subagentId", "description"]
12788
+ required: ["subagentId", "description", "scope", "outOfScope"]
12701
12789
  };
12702
12790
  return {
12703
12791
  name: "assign_task",
12704
- description: "Queue a task on a previously spawned subagent. NON-BLOCKING: returns a `taskId` IMMEDIATELY \u2014 the subagent processes the task on its next iteration with its own LLM budget. The `taskId` is the durable handle for retrieving the result later via `await_tasks`, `roll_up`, or `ask_result`. Many `assign_task` calls can be in flight in parallel against the same or different subagents. This is the primary tool for fan-out work; do NOT use `delegate` to spawn multiple investigations sequentially.",
12792
+ description: "Queue a task on a previously spawned subagent. NON-BLOCKING: returns a `taskId` IMMEDIATELY \u2014 the subagent processes the task on its next iteration with its own LLM budget. The `taskId` is the durable handle for retrieving the result later via `await_tasks`, `roll_up`, or `ask_result`. Every assignment MUST carry an explicit boundary: `scope` (what the work covers) and `outOfScope` (at least one concrete non-goal) \u2014 the call is rejected without them, and the worker treats the rendered boundary block as a hard contract. Many `assign_task` calls can be in flight in parallel against the same or different subagents. This is the primary tool for fan-out work; do NOT use `delegate` to spawn multiple investigations sequentially.",
12705
12793
  permission: "auto",
12706
12794
  mutating: false,
12707
12795
  capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
12708
12796
  inputSchema,
12709
12797
  async execute(input) {
12710
12798
  const i = input;
12799
+ const boundary = parseTaskBoundary(i);
12800
+ if (!boundary.ok) {
12801
+ return {
12802
+ ok: false,
12803
+ error: `assign_task rejected \u2014 task boundary incomplete: ${boundary.error}`,
12804
+ hint: boundary.hint
12805
+ };
12806
+ }
12711
12807
  const task = {
12712
12808
  id: randomUUID8(),
12713
- description: i.description,
12809
+ description: composeBoundedTaskDescription(i.description, boundary.boundary),
12714
12810
  subagentId: i.subagentId,
12715
12811
  maxToolCalls: i.maxToolCalls,
12716
12812
  timeoutMs: i.timeoutMs
@@ -20503,8 +20599,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
20503
20599
  withNickname(subagent, subagentId) {
20504
20600
  const role = subagent.role ?? "subagent";
20505
20601
  const name = subagent.name?.trim() ?? "";
20506
- const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
20507
- if (!isPlaceholder) return subagent;
20602
+ const isPlaceholder2 = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
20603
+ if (!isPlaceholder2) return subagent;
20508
20604
  const { key, display } = assignNickname(role, this.usedNicknames);
20509
20605
  this.usedNicknames.add(key);
20510
20606
  this.subagentNicknames.set(subagentId, key);
@@ -31367,6 +31463,7 @@ export {
31367
31463
  clearProjectSkillAugmentation,
31368
31464
  collabInjectMiddleware,
31369
31465
  collabPauseMiddleware,
31466
+ composeBoundedTaskDescription,
31370
31467
  composeDirectorPrompt,
31371
31468
  composeSubagentPrompt,
31372
31469
  consolidatedDocumentPath,
@@ -31451,6 +31548,7 @@ export {
31451
31548
  parseMailboxAckInput,
31452
31549
  parseMailboxQueryInput,
31453
31550
  parseMailboxSendInput,
31551
+ parseTaskBoundary,
31454
31552
  phaseForRole,
31455
31553
  quarantinePath,
31456
31554
  rankRoleSkills,
@@ -31467,6 +31565,7 @@ export {
31467
31565
  refreshProjectAgentIdentity,
31468
31566
  release,
31469
31567
  renderSkillAugmentation,
31568
+ renderTaskBoundaryBlock,
31470
31569
  resetCaptureWindow,
31471
31570
  resetCaptureWindows,
31472
31571
  resetProjectAgentIdentity,
@@ -31497,6 +31596,7 @@ export {
31497
31596
  startPackageOutdatedWatcher,
31498
31597
  startTechStackConsumer,
31499
31598
  subagentNeedsWorktree,
31599
+ taskBoundarySchemaProperties,
31500
31600
  terminalPolicyDecision,
31501
31601
  unwrapWholeDocumentFence,
31502
31602
  updatePackageOutdatedStatus,
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Hard task-boundary contract for leader → subagent assignment tools.
3
+ *
4
+ * `delegate` and `assign_task` reject any assignment that does not carry an
5
+ * explicit in-bounds `scope` and at least one concrete out-of-scope non-goal.
6
+ * Prompt-only guidance ("state the scope and non-goals") degrades under
7
+ * pressure: a rushed leader writes "fix it", the worker guesses the edges, and
8
+ * the drift only surfaces at review time — the most expensive place to find
9
+ * it. Forcing the boundary into structured fields makes the edges
10
+ * machine-checkable at assignment time and lets the tool return a teaching
11
+ * error that names exactly what is missing, so the leader self-corrects on
12
+ * the next call instead of shipping a vague brief.
13
+ *
14
+ * The parsed boundary is composed into `TaskSpec.description` as a clearly
15
+ * delimited block. That is deliberate: the description is the canonical brief
16
+ * every runner, transcript, handoff continuation, and roll-up already
17
+ * consumes, so the boundary travels with the task everywhere without new
18
+ * plumbing (and `buildHandoffTask`'s "Original task:" carry-over preserves it
19
+ * into fresh workers for free).
20
+ */
21
+ import type { JSONSchema } from '../types/tool.js';
22
+ export interface TaskBoundary {
23
+ /** What the work covers — the in-bounds statement. */
24
+ scope: string;
25
+ /** Explicit non-goals: things the worker must NOT do. */
26
+ outOfScope: string[];
27
+ }
28
+ export type TaskBoundaryParseResult = {
29
+ ok: true;
30
+ boundary: TaskBoundary;
31
+ } | {
32
+ ok: false;
33
+ error: string;
34
+ hint: string;
35
+ };
36
+ /**
37
+ * Validate and normalize the `scope` / `outOfScope` pair from a tool input.
38
+ * Returns the cleaned boundary, or an error + hint pair worded so the calling
39
+ * leader can fix the call in one retry.
40
+ */
41
+ export declare function parseTaskBoundary(raw: {
42
+ scope?: unknown;
43
+ outOfScope?: unknown;
44
+ }): TaskBoundaryParseResult;
45
+ /**
46
+ * Render the boundary as the block appended to `TaskSpec.description`. The
47
+ * heading is loud on purpose: the worker treats these lines as hard edges,
48
+ * and review-time drift checks quote them verbatim.
49
+ */
50
+ export declare function renderTaskBoundaryBlock(boundary: TaskBoundary): string;
51
+ /**
52
+ * Compose the leader's objective with its boundary into the canonical brief
53
+ * delivered to the runner. The objective stays verbatim and first; the
54
+ * boundary block follows so handoffs, transcripts, and roll-ups that quote
55
+ * the description carry the edges with them.
56
+ */
57
+ export declare function composeBoundedTaskDescription(objective: string, boundary: TaskBoundary): string;
58
+ /**
59
+ * Shared schema property definitions so `delegate` and `assign_task` state
60
+ * the identical contract. Declaring the fields here (rather than inline per
61
+ * tool) keeps the required-field teaching text single-sourced.
62
+ */
63
+ export declare const taskBoundarySchemaProperties: Record<string, JSONSchema>;
64
+ //# sourceMappingURL=task-boundary.d.ts.map
@@ -11863,9 +11863,13 @@ ${memText}`);
11863
11863
  }
11864
11864
  }
11865
11865
  if (skillBodyCache) {
11866
- parts.push(`# Active Skills
11866
+ parts.push(
11867
+ `# Active Skills
11867
11868
 
11868
- ${skillBodyCache}`);
11869
+ Skills are methods, not authority: they never widen your task's scope. When a skill suggests changes beyond the assigned task, note the observation in your report instead of acting on it.
11870
+
11871
+ ${skillBodyCache}`
11872
+ );
11869
11873
  }
11870
11874
  return { text: parts.join("\n\n"), skillBodyCache };
11871
11875
  }
@@ -5512,6 +5512,80 @@ var FLEET_ROSTER_WITHACP = {
5512
5512
  ...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
5513
5513
  };
5514
5514
 
5515
+ // src/coordination/task-boundary.ts
5516
+ var PLACEHOLDER_VALUES = /* @__PURE__ */ new Set([
5517
+ "n/a",
5518
+ "na",
5519
+ "none",
5520
+ "nothing",
5521
+ "tbd",
5522
+ "todo",
5523
+ "unknown",
5524
+ "unspecified",
5525
+ "-",
5526
+ "\u2014",
5527
+ ".",
5528
+ "as above",
5529
+ "same as above",
5530
+ "see above",
5531
+ "see task",
5532
+ "same as task"
5533
+ ]);
5534
+ var isPlaceholder = (value) => PLACEHOLDER_VALUES.has(value.trim().toLowerCase());
5535
+ var MIN_SCOPE_CHARS = 8;
5536
+ var MIN_NON_GOAL_CHARS = 3;
5537
+ function parseTaskBoundary(raw) {
5538
+ const scope = typeof raw.scope === "string" ? raw.scope.trim() : "";
5539
+ if (scope.length < MIN_SCOPE_CHARS) {
5540
+ return {
5541
+ ok: false,
5542
+ error: `\`scope\` is missing or too vague \u2014 state in one concrete sentence what work this task covers (files, components, or commands in-bounds).`,
5543
+ hint: 'Example \u2014 scope: "Audit packages/core/src/parser/*.ts for unhandled token errors and report findings."'
5544
+ };
5545
+ }
5546
+ if (!Array.isArray(raw.outOfScope) || raw.outOfScope.length === 0) {
5547
+ return {
5548
+ ok: false,
5549
+ error: "`outOfScope` must be an array with at least one explicit non-goal \u2014 things the worker must NOT do.",
5550
+ hint: 'Example \u2014 outOfScope: ["Do not modify files outside packages/core", "Do not fix the bugs you find, only report them"].'
5551
+ };
5552
+ }
5553
+ const concrete = raw.outOfScope.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length >= MIN_NON_GOAL_CHARS && !isPlaceholder(item));
5554
+ if (concrete.length === 0) {
5555
+ return {
5556
+ ok: false,
5557
+ error: 'Every `outOfScope` entry was a placeholder ("none", "n/a", \u2026). Name at least one concrete non-goal: files or areas not to touch, changes not to make, features not to add.',
5558
+ hint: 'There is always an edge worth stating \u2014 "read-only, no edits", "no dependency changes", "do not touch other packages". If truly nothing comes to mind, the task is not decomposed enough yet.'
5559
+ };
5560
+ }
5561
+ return { ok: true, boundary: { scope, outOfScope: concrete } };
5562
+ }
5563
+ function renderTaskBoundaryBlock(boundary) {
5564
+ return [
5565
+ "\u2500\u2500 TASK BOUNDARY (hard contract \u2014 these lines define your edges) \u2500\u2500",
5566
+ `Scope (what this task covers):
5567
+ ${boundary.scope}`,
5568
+ `Out of scope (explicit non-goals \u2014 do NOT do any of these):
5569
+ ${boundary.outOfScope.map((item) => `- ${item}`).join("\n")}`
5570
+ ].join("\n");
5571
+ }
5572
+ function composeBoundedTaskDescription(objective, boundary) {
5573
+ return `${objective.trim()}
5574
+
5575
+ ${renderTaskBoundaryBlock(boundary)}`;
5576
+ }
5577
+ var taskBoundarySchemaProperties = {
5578
+ scope: {
5579
+ type: "string",
5580
+ description: "REQUIRED. One concrete sentence stating what work this task covers \u2014 the in-bounds. The call is rejected without it."
5581
+ },
5582
+ outOfScope: {
5583
+ type: "array",
5584
+ items: { type: "string", minLength: 1 },
5585
+ description: 'REQUIRED. At least one explicit non-goal the worker must NOT do (files/areas not to touch, changes not to make, features not to add). Placeholders like "none" are rejected.'
5586
+ }
5587
+ };
5588
+
5515
5589
  // src/coordination/delegate-tool.ts
5516
5590
  function createDelegateTool(opts) {
5517
5591
  const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
@@ -5521,8 +5595,9 @@ function createDelegateTool(opts) {
5521
5595
  properties: {
5522
5596
  task: {
5523
5597
  type: "string",
5524
- description: "What the subagent should do \u2014 natural language, complete sentence(s)."
5598
+ description: "The objective \u2014 what the subagent should do, natural language, complete sentence(s). Pair it with the required `scope` and `outOfScope` boundary fields."
5525
5599
  },
5600
+ ...taskBoundarySchemaProperties,
5526
5601
  role: {
5527
5602
  type: "string",
5528
5603
  description: rosterIds.length > 0 ? "Roster role id. Common: bug-hunter, security-scanner, refactor-planner, critic, audit-log, executor, shadow-agent, architect." : "No roster configured \u2014 pass `name` instead."
@@ -5580,12 +5655,12 @@ function createDelegateTool(opts) {
5580
5655
  description: "Max fresh-worker continuations after budget exhaustion. Default 1. Each gets the prior partial report."
5581
5656
  }
5582
5657
  },
5583
- required: ["task"]
5658
+ required: ["task", "scope", "outOfScope"]
5584
5659
  };
5585
5660
  return {
5586
5661
  name: "delegate",
5587
5662
  description: "Hand a piece of work to a subagent and block until it returns. This call is synchronous: the leader's iteration pauses for the full duration of the subagent's run. (Multiple `delegate` calls fired in the same assistant turn still parallelize through the provider's parallel-tool-call surface, but each one eats wall-clock time \u2014 so for fan-out you actually control, reach for the async path below.) Use `delegate` when your next step genuinely needs the subagent's verdict \u2014 a review, a fact-check, a sign-off. Has own context, own LLM call, auto-extending budget, and a partial-completion handoff path (maxHandoffs, default 1). Workers cannot recursively spawn.\n\n**Do NOT use `delegate` for long-running work.** While `delegate` is in flight, the leader is fully blocked \u2014 it cannot act on other tools, read mail, or react to the user. If the work might run for tens of minutes or hours (multi-file refactor, monorepo audit, long-running build/test, sweeping migration), the blocking call wastes the leader's time. Use the async tool family instead: `spawn_subagent` to create each worker (returns a `subagentId` immediately), `assign_task` to queue work on it (returns a `taskId` immediately), then `await_tasks` to retrieve results later. The leader keeps doing other work while the worker churns, and a worker that realizes its task will run long can mail the leader (type `steer` or `ask` via `mail_send`) saying *\"my task is going to run long, please spawn a subagent instead\"* so the leader re-dispatches asynchronously instead of waiting.\n\n**Do NOT use `delegate` for fan-out you control.** Multiple sequential `delegate` calls each block the leader, wasting wall-clock time. For independent investigations you want to run in parallel \u2014 security scan + bug hunt + perf review on the same PR \u2014 use the async tool family: `spawn_subagent` to create each worker (returns a `subagentId` immediately), `assign_task` to queue work on it (returns a `taskId` immediately), then the `await_tasks` tool with `{mode: 'any'}` to fold the first useful result into the next decision while the rest keep churning. Reach for `delegate` only when the result gates your next move AND the work is short enough that blocking the leader is acceptable.",
5588
- usageHint: "Set `task` to a complete instruction. Pick `role` from roster or pass `name` for free-form. Reach for `delegate` only when the result gates your next move AND the work is short enough that blocking the leader is acceptable (minutes, not hours). For long-running work or fan-out you control, use `spawn_subagent` + `assign_task` + `await_tasks` instead. Raise `maxHandoffs` (default 1, cap 8) for multi-day or multi-refactor tasks; pass larger `timeoutMs`/`maxIterations`/`maxToolCalls` only when needed.",
5663
+ usageHint: "Set `task` to the objective, then make the edges explicit: `scope` (what the work covers) and `outOfScope` (at least one concrete non-goal) are REQUIRED \u2014 the call is rejected without them, and the worker treats the rendered boundary block as a hard contract. Pick `role` from roster or pass `name` for free-form. Reach for `delegate` only when the result gates your next move AND the work is short enough that blocking the leader is acceptable (minutes, not hours). For long-running work or fan-out you control, use `spawn_subagent` + `assign_task` + `await_tasks` instead. Raise `maxHandoffs` (default 1, cap 8) for multi-day or multi-refactor tasks; pass larger `timeoutMs`/`maxIterations`/`maxToolCalls` only when needed.",
5589
5664
  permission: "auto",
5590
5665
  mutating: false,
5591
5666
  managesOwnTimeout: true,
@@ -5605,6 +5680,14 @@ function createDelegateTool(opts) {
5605
5680
  error: "Delegation cancelled before spawn \u2014 the run was interrupted."
5606
5681
  };
5607
5682
  }
5683
+ const boundary = parseTaskBoundary(i);
5684
+ if (!boundary.ok) {
5685
+ return {
5686
+ ok: false,
5687
+ error: `delegate rejected \u2014 task boundary incomplete: ${boundary.error}`,
5688
+ hint: boundary.hint
5689
+ };
5690
+ }
5608
5691
  const target = i.role ?? i.name ?? "subagent";
5609
5692
  const launchModePreface = [
5610
5693
  "Launch-mode guidance (delegate): you were launched via the synchronous `delegate` tool, so the leader is blocked on this call for the full duration of your run.",
@@ -5675,7 +5758,8 @@ function createDelegateTool(opts) {
5675
5758
  const dir = director;
5676
5759
  const maxHandoffs = Math.min(8, Math.max(0, Math.floor(i.maxHandoffs ?? 1)));
5677
5760
  const handoffs = [];
5678
- let delegatedTask = i.task;
5761
+ const baseBrief = composeBoundedTaskDescription(i.task, boundary.boundary);
5762
+ let delegatedTask = baseBrief;
5679
5763
  let handoffCount = 0;
5680
5764
  for (; ; ) {
5681
5765
  const attemptConfig = (() => {
@@ -5815,7 +5899,7 @@ function createDelegateTool(opts) {
5815
5899
  remainingWork: continuation.remainingWork
5816
5900
  });
5817
5901
  handoffCount += 1;
5818
- delegatedTask = buildHandoffTask(i.task, continuation, handoffCount, maxHandoffs);
5902
+ delegatedTask = buildHandoffTask(baseBrief, continuation, handoffCount, maxHandoffs);
5819
5903
  continue;
5820
5904
  }
5821
5905
  const incomplete = result.report?.completion === "partial";
@@ -7378,6 +7462,7 @@ var CollabSession = class extends EventEmitter {
7378
7462
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
7379
7463
  ${f.content}`).join("\n\n");
7380
7464
  return `You are BugHunter. Scan the following files for bugs and code smells.
7465
+ This is an analysis role: do not edit the target files; report findings only.
7381
7466
 
7382
7467
  Target files:
7383
7468
  ${fileContents}
@@ -7396,6 +7481,7 @@ Important: emit each finding as soon as you find it. Do not batch or wait until
7396
7481
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
7397
7482
  ${f.content}`).join("\n\n");
7398
7483
  return `You are RefactorPlanner. Plan refactorings for the following files.
7484
+ This is an analysis role: do not edit the target files; emit the plan only.
7399
7485
 
7400
7486
  Target files:
7401
7487
  ${fileContents}
@@ -7417,6 +7503,7 @@ Emit each plan immediately. Do not wait until planning is complete.`;
7417
7503
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
7418
7504
  ${f.content}`).join("\n\n");
7419
7505
  return `You are Critic. Evaluate bug findings and refactor plans.
7506
+ This is an analysis role: do not edit the target files; emit evaluations only.
7420
7507
 
7421
7508
  Target files:
7422
7509
  ${fileContents}
@@ -8503,8 +8590,9 @@ function makeAssignTool(director) {
8503
8590
  description: {
8504
8591
  type: "string",
8505
8592
  minLength: 1,
8506
- description: "The task in natural language \u2014 what you want this subagent to do."
8593
+ description: "The objective in natural language \u2014 what you want this subagent to do. Pair it with the required `scope` and `outOfScope` boundary fields."
8507
8594
  },
8595
+ ...taskBoundarySchemaProperties,
8508
8596
  maxToolCalls: {
8509
8597
  type: "number",
8510
8598
  minimum: 1,
@@ -8512,20 +8600,28 @@ function makeAssignTool(director) {
8512
8600
  },
8513
8601
  timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
8514
8602
  },
8515
- required: ["subagentId", "description"]
8603
+ required: ["subagentId", "description", "scope", "outOfScope"]
8516
8604
  };
8517
8605
  return {
8518
8606
  name: "assign_task",
8519
- description: "Queue a task on a previously spawned subagent. NON-BLOCKING: returns a `taskId` IMMEDIATELY \u2014 the subagent processes the task on its next iteration with its own LLM budget. The `taskId` is the durable handle for retrieving the result later via `await_tasks`, `roll_up`, or `ask_result`. Many `assign_task` calls can be in flight in parallel against the same or different subagents. This is the primary tool for fan-out work; do NOT use `delegate` to spawn multiple investigations sequentially.",
8607
+ description: "Queue a task on a previously spawned subagent. NON-BLOCKING: returns a `taskId` IMMEDIATELY \u2014 the subagent processes the task on its next iteration with its own LLM budget. The `taskId` is the durable handle for retrieving the result later via `await_tasks`, `roll_up`, or `ask_result`. Every assignment MUST carry an explicit boundary: `scope` (what the work covers) and `outOfScope` (at least one concrete non-goal) \u2014 the call is rejected without them, and the worker treats the rendered boundary block as a hard contract. Many `assign_task` calls can be in flight in parallel against the same or different subagents. This is the primary tool for fan-out work; do NOT use `delegate` to spawn multiple investigations sequentially.",
8520
8608
  permission: "auto",
8521
8609
  mutating: false,
8522
8610
  capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
8523
8611
  inputSchema,
8524
8612
  async execute(input) {
8525
8613
  const i = input;
8614
+ const boundary = parseTaskBoundary(i);
8615
+ if (!boundary.ok) {
8616
+ return {
8617
+ ok: false,
8618
+ error: `assign_task rejected \u2014 task boundary incomplete: ${boundary.error}`,
8619
+ hint: boundary.hint
8620
+ };
8621
+ }
8526
8622
  const task = {
8527
8623
  id: randomUUID6(),
8528
- description: i.description,
8624
+ description: composeBoundedTaskDescription(i.description, boundary.boundary),
8529
8625
  subagentId: i.subagentId,
8530
8626
  maxToolCalls: i.maxToolCalls,
8531
8627
  timeoutMs: i.timeoutMs
@@ -19228,8 +19324,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
19228
19324
  withNickname(subagent, subagentId) {
19229
19325
  const role = subagent.role ?? "subagent";
19230
19326
  const name = subagent.name?.trim() ?? "";
19231
- const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
19232
- if (!isPlaceholder) return subagent;
19327
+ const isPlaceholder2 = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
19328
+ if (!isPlaceholder2) return subagent;
19233
19329
  const { key, display } = assignNickname(role, this.usedNicknames);
19234
19330
  this.usedNicknames.add(key);
19235
19331
  this.subagentNicknames.set(subagentId, key);
@@ -22497,6 +22593,7 @@ function normalizeTargetLoad(targetLoad, thresholds) {
22497
22593
 
22498
22594
  // src/types/context-window.ts
22499
22595
  var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
22596
+ var LARGE_WINDOW_DEEP_MODE_THRESHOLD = 1e6;
22500
22597
  var DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES = Object.freeze({
22501
22598
  archival: "balanced"
22502
22599
  });
@@ -22504,7 +22601,7 @@ var CONTEXT_WINDOW_MODES = Object.freeze([
22504
22601
  {
22505
22602
  id: "balanced",
22506
22603
  name: "Balanced",
22507
- description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
22604
+ description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed. Windows of 1M+ tokens default to Deep.",
22508
22605
  thresholds: { warn: 0.55, soft: 0.7, hard: 0.85 },
22509
22606
  aggressiveOn: "soft",
22510
22607
  preserveK: 8,
@@ -22555,9 +22652,11 @@ function getContextWindowMode(id) {
22555
22652
  function isContextWindowModeId(id) {
22556
22653
  return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
22557
22654
  }
22558
- function resolveContextWindowPolicy(config = {}, overrideMode) {
22655
+ function resolveContextWindowPolicy(config = {}, overrideMode, maxContext) {
22559
22656
  const requested = overrideMode ?? config.mode ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
22560
- const mode = getContextWindowMode(requested) ?? expectDefined(getContextWindowMode(DEFAULT_CONTEXT_WINDOW_MODE_ID));
22657
+ const normalized = normalizeContextWindowModeId(requested) ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
22658
+ const baseId = normalized === DEFAULT_CONTEXT_WINDOW_MODE_ID && typeof maxContext === "number" && maxContext >= LARGE_WINDOW_DEEP_MODE_THRESHOLD ? "deep" : normalized;
22659
+ const mode = expectDefined(getContextWindowMode(baseId));
22561
22660
  return {
22562
22661
  ...mode,
22563
22662
  thresholds: {
@@ -22972,7 +23071,7 @@ function readContextWindowPolicy(ctx) {
22972
23071
  function installSubagentAutoCompaction(pipelines, ctx, contextConfig, events) {
22973
23072
  const maxContext = ctx.provider?.capabilities?.maxContext ?? 0;
22974
23073
  if (!(maxContext > 0)) return void 0;
22975
- const policy = resolveContextWindowPolicy(contextConfig ?? {});
23074
+ const policy = resolveContextWindowPolicy(contextConfig ?? {}, void 0, maxContext);
22976
23075
  ctx.meta ??= {};
22977
23076
  ctx.meta["contextWindowPolicy"] = policy;
22978
23077
  const compactor = new HybridCompactor({
@@ -21421,6 +21421,7 @@ function readPolicy(ctx) {
21421
21421
 
21422
21422
  // src/types/context-window.ts
21423
21423
  var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
21424
+ var LARGE_WINDOW_DEEP_MODE_THRESHOLD = 1e6;
21424
21425
  var DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES = Object.freeze({
21425
21426
  archival: "balanced"
21426
21427
  });
@@ -21428,7 +21429,7 @@ var CONTEXT_WINDOW_MODES = Object.freeze([
21428
21429
  {
21429
21430
  id: "balanced",
21430
21431
  name: "Balanced",
21431
- description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
21432
+ description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed. Windows of 1M+ tokens default to Deep.",
21432
21433
  thresholds: { warn: 0.55, soft: 0.7, hard: 0.85 },
21433
21434
  aggressiveOn: "soft",
21434
21435
  preserveK: 8,
@@ -21470,9 +21471,11 @@ function getContextWindowMode(id) {
21470
21471
  function isContextWindowModeId(id) {
21471
21472
  return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
21472
21473
  }
21473
- function resolveContextWindowPolicy(config = {}, overrideMode) {
21474
+ function resolveContextWindowPolicy(config = {}, overrideMode, maxContext) {
21474
21475
  const requested = overrideMode ?? config.mode ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
21475
- const mode = getContextWindowMode(requested) ?? expectDefined(getContextWindowMode(DEFAULT_CONTEXT_WINDOW_MODE_ID));
21476
+ const normalized = normalizeContextWindowModeId(requested) ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
21477
+ const baseId = normalized === DEFAULT_CONTEXT_WINDOW_MODE_ID && typeof maxContext === "number" && maxContext >= LARGE_WINDOW_DEEP_MODE_THRESHOLD ? "deep" : normalized;
21478
+ const mode = expectDefined(getContextWindowMode(baseId));
21476
21479
  return {
21477
21480
  ...mode,
21478
21481
  thresholds: {
@@ -21490,7 +21493,7 @@ function resolveContextWindowPolicy(config = {}, overrideMode) {
21490
21493
  function installSubagentAutoCompaction(pipelines, ctx, contextConfig, events) {
21491
21494
  const maxContext = ctx.provider?.capabilities?.maxContext ?? 0;
21492
21495
  if (!(maxContext > 0)) return void 0;
21493
- const policy = resolveContextWindowPolicy(contextConfig ?? {});
21496
+ const policy = resolveContextWindowPolicy(contextConfig ?? {}, void 0, maxContext);
21494
21497
  ctx.meta ??= {};
21495
21498
  ctx.meta["contextWindowPolicy"] = policy;
21496
21499
  const compactor = new HybridCompactor({
package/dist/index.d.ts CHANGED
@@ -134,7 +134,7 @@ export { createMcpUseTool } from './tools/mcp-use.js';
134
134
  export { type CreateOneShotLLMToolOptions, createOneShotLLMTool, ONE_SHOT_LLM_TOOL_NAME, } from './tools/one-shot-llm-tool.js';
135
135
  export { type CreatePluginManagerToolOptions, createPluginManagerTool, PLUGIN_MANAGER_TOOL_NAME, type PluginManagerCatalogEntry, type PluginManagerMutationResult, } from './tools/plugin-manager.js';
136
136
  export type { Compactor, CompactReport } from './types/compactor.js';
137
- export { CONTEXT_WINDOW_MODES, type ContextWindowAggressiveOn, type ContextWindowConfigLike, type ContextWindowMode, type ContextWindowModeId, type ContextWindowModeSelectionId, type ContextWindowPolicy, type ContextWindowThresholds, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES, type DeprecatedContextWindowModeId, formatContextWindowModeList, getContextWindowMode, isContextWindowModeId, isContextWindowModeSelectionId, isDeprecatedContextWindowModeId, listContextWindowModes, normalizeContextWindowModeId, resolveContextWindowPolicy, } from './types/context-window.js';
137
+ export { CONTEXT_WINDOW_MODES, type ContextWindowAggressiveOn, type ContextWindowConfigLike, type ContextWindowMode, type ContextWindowModeId, type ContextWindowModeSelectionId, type ContextWindowPolicy, type ContextWindowThresholds, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES, type DeprecatedContextWindowModeId, formatContextWindowModeList, getContextWindowMode, isContextWindowModeId, isContextWindowModeSelectionId, isDeprecatedContextWindowModeId, LARGE_WINDOW_DEEP_MODE_THRESHOLD, CONTEXT_WINDOW_MODE_PINNED_META_KEY, listContextWindowModes, normalizeContextWindowModeId, resolveContextWindowPolicy, } from './types/context-window.js';
138
138
  export { DEFAULT_SESSION_PRUNE_DAYS } from './types/default-config.js';
139
139
  export type { FileEventRecord } from './types/file-event-record.js';
140
140
  export * from './types/index.js';
package/dist/index.js CHANGED
@@ -3042,6 +3042,8 @@ function expectDefined(value, label) {
3042
3042
 
3043
3043
  // src/types/context-window.ts
3044
3044
  var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
3045
+ var LARGE_WINDOW_DEEP_MODE_THRESHOLD = 1e6;
3046
+ var CONTEXT_WINDOW_MODE_PINNED_META_KEY = "contextWindowModePinned";
3045
3047
  var DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES = Object.freeze({
3046
3048
  archival: "balanced"
3047
3049
  });
@@ -3049,7 +3051,7 @@ var CONTEXT_WINDOW_MODES = Object.freeze([
3049
3051
  {
3050
3052
  id: "balanced",
3051
3053
  name: "Balanced",
3052
- description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
3054
+ description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed. Windows of 1M+ tokens default to Deep.",
3053
3055
  thresholds: { warn: 0.55, soft: 0.7, hard: 0.85 },
3054
3056
  aggressiveOn: "soft",
3055
3057
  preserveK: 8,
@@ -3100,9 +3102,11 @@ function getContextWindowMode(id) {
3100
3102
  function isContextWindowModeId(id) {
3101
3103
  return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
3102
3104
  }
3103
- function resolveContextWindowPolicy(config = {}, overrideMode) {
3105
+ function resolveContextWindowPolicy(config = {}, overrideMode, maxContext) {
3104
3106
  const requested = overrideMode ?? config.mode ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
3105
- const mode = getContextWindowMode(requested) ?? expectDefined(getContextWindowMode(DEFAULT_CONTEXT_WINDOW_MODE_ID));
3107
+ const normalized = normalizeContextWindowModeId(requested) ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
3108
+ const baseId = normalized === DEFAULT_CONTEXT_WINDOW_MODE_ID && typeof maxContext === "number" && maxContext >= LARGE_WINDOW_DEEP_MODE_THRESHOLD ? "deep" : normalized;
3109
+ const mode = expectDefined(getContextWindowMode(baseId));
3106
3110
  return {
3107
3111
  ...mode,
3108
3112
  thresholds: {
@@ -11980,9 +11984,13 @@ ${memText}`);
11980
11984
  }
11981
11985
  }
11982
11986
  if (skillBodyCache) {
11983
- parts.push(`# Active Skills
11987
+ parts.push(
11988
+ `# Active Skills
11989
+
11990
+ Skills are methods, not authority: they never widen your task's scope. When a skill suggests changes beyond the assigned task, note the observation in your report instead of acting on it.
11984
11991
 
11985
- ${skillBodyCache}`);
11992
+ ${skillBodyCache}`
11993
+ );
11986
11994
  }
11987
11995
  return { text: parts.join("\n\n"), skillBodyCache };
11988
11996
  }
@@ -30142,6 +30150,7 @@ var CollabSession = class extends EventEmitter {
30142
30150
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
30143
30151
  ${f.content}`).join("\n\n");
30144
30152
  return `You are BugHunter. Scan the following files for bugs and code smells.
30153
+ This is an analysis role: do not edit the target files; report findings only.
30145
30154
 
30146
30155
  Target files:
30147
30156
  ${fileContents}
@@ -30160,6 +30169,7 @@ Important: emit each finding as soon as you find it. Do not batch or wait until
30160
30169
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
30161
30170
  ${f.content}`).join("\n\n");
30162
30171
  return `You are RefactorPlanner. Plan refactorings for the following files.
30172
+ This is an analysis role: do not edit the target files; emit the plan only.
30163
30173
 
30164
30174
  Target files:
30165
30175
  ${fileContents}
@@ -30181,6 +30191,7 @@ Emit each plan immediately. Do not wait until planning is complete.`;
30181
30191
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
30182
30192
  ${f.content}`).join("\n\n");
30183
30193
  return `You are Critic. Evaluate bug findings and refactor plans.
30194
+ This is an analysis role: do not edit the target files; emit evaluations only.
30184
30195
 
30185
30196
  Target files:
30186
30197
  ${fileContents}
@@ -31216,6 +31227,82 @@ function makeLLMClassifier(complete2) {
31216
31227
  // src/coordination/director-basic-tools.ts
31217
31228
  import { randomUUID as randomUUID12 } from "node:crypto";
31218
31229
  init_error();
31230
+
31231
+ // src/coordination/task-boundary.ts
31232
+ var PLACEHOLDER_VALUES = /* @__PURE__ */ new Set([
31233
+ "n/a",
31234
+ "na",
31235
+ "none",
31236
+ "nothing",
31237
+ "tbd",
31238
+ "todo",
31239
+ "unknown",
31240
+ "unspecified",
31241
+ "-",
31242
+ "\u2014",
31243
+ ".",
31244
+ "as above",
31245
+ "same as above",
31246
+ "see above",
31247
+ "see task",
31248
+ "same as task"
31249
+ ]);
31250
+ var isPlaceholder = (value) => PLACEHOLDER_VALUES.has(value.trim().toLowerCase());
31251
+ var MIN_SCOPE_CHARS = 8;
31252
+ var MIN_NON_GOAL_CHARS = 3;
31253
+ function parseTaskBoundary(raw) {
31254
+ const scope = typeof raw.scope === "string" ? raw.scope.trim() : "";
31255
+ if (scope.length < MIN_SCOPE_CHARS) {
31256
+ return {
31257
+ ok: false,
31258
+ error: `\`scope\` is missing or too vague \u2014 state in one concrete sentence what work this task covers (files, components, or commands in-bounds).`,
31259
+ hint: 'Example \u2014 scope: "Audit packages/core/src/parser/*.ts for unhandled token errors and report findings."'
31260
+ };
31261
+ }
31262
+ if (!Array.isArray(raw.outOfScope) || raw.outOfScope.length === 0) {
31263
+ return {
31264
+ ok: false,
31265
+ error: "`outOfScope` must be an array with at least one explicit non-goal \u2014 things the worker must NOT do.",
31266
+ hint: 'Example \u2014 outOfScope: ["Do not modify files outside packages/core", "Do not fix the bugs you find, only report them"].'
31267
+ };
31268
+ }
31269
+ const concrete = raw.outOfScope.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length >= MIN_NON_GOAL_CHARS && !isPlaceholder(item));
31270
+ if (concrete.length === 0) {
31271
+ return {
31272
+ ok: false,
31273
+ error: 'Every `outOfScope` entry was a placeholder ("none", "n/a", \u2026). Name at least one concrete non-goal: files or areas not to touch, changes not to make, features not to add.',
31274
+ hint: 'There is always an edge worth stating \u2014 "read-only, no edits", "no dependency changes", "do not touch other packages". If truly nothing comes to mind, the task is not decomposed enough yet.'
31275
+ };
31276
+ }
31277
+ return { ok: true, boundary: { scope, outOfScope: concrete } };
31278
+ }
31279
+ function renderTaskBoundaryBlock(boundary) {
31280
+ return [
31281
+ "\u2500\u2500 TASK BOUNDARY (hard contract \u2014 these lines define your edges) \u2500\u2500",
31282
+ `Scope (what this task covers):
31283
+ ${boundary.scope}`,
31284
+ `Out of scope (explicit non-goals \u2014 do NOT do any of these):
31285
+ ${boundary.outOfScope.map((item) => `- ${item}`).join("\n")}`
31286
+ ].join("\n");
31287
+ }
31288
+ function composeBoundedTaskDescription(objective, boundary) {
31289
+ return `${objective.trim()}
31290
+
31291
+ ${renderTaskBoundaryBlock(boundary)}`;
31292
+ }
31293
+ var taskBoundarySchemaProperties = {
31294
+ scope: {
31295
+ type: "string",
31296
+ description: "REQUIRED. One concrete sentence stating what work this task covers \u2014 the in-bounds. The call is rejected without it."
31297
+ },
31298
+ outOfScope: {
31299
+ type: "array",
31300
+ items: { type: "string", minLength: 1 },
31301
+ description: 'REQUIRED. At least one explicit non-goal the worker must NOT do (files/areas not to touch, changes not to make, features not to add). Placeholders like "none" are rejected.'
31302
+ }
31303
+ };
31304
+
31305
+ // src/coordination/director-basic-tools.ts
31219
31306
  function makeAssignTool(director) {
31220
31307
  const inputSchema = {
31221
31308
  type: "object",
@@ -31224,8 +31311,9 @@ function makeAssignTool(director) {
31224
31311
  description: {
31225
31312
  type: "string",
31226
31313
  minLength: 1,
31227
- description: "The task in natural language \u2014 what you want this subagent to do."
31314
+ description: "The objective in natural language \u2014 what you want this subagent to do. Pair it with the required `scope` and `outOfScope` boundary fields."
31228
31315
  },
31316
+ ...taskBoundarySchemaProperties,
31229
31317
  maxToolCalls: {
31230
31318
  type: "number",
31231
31319
  minimum: 1,
@@ -31233,20 +31321,28 @@ function makeAssignTool(director) {
31233
31321
  },
31234
31322
  timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
31235
31323
  },
31236
- required: ["subagentId", "description"]
31324
+ required: ["subagentId", "description", "scope", "outOfScope"]
31237
31325
  };
31238
31326
  return {
31239
31327
  name: "assign_task",
31240
- description: "Queue a task on a previously spawned subagent. NON-BLOCKING: returns a `taskId` IMMEDIATELY \u2014 the subagent processes the task on its next iteration with its own LLM budget. The `taskId` is the durable handle for retrieving the result later via `await_tasks`, `roll_up`, or `ask_result`. Many `assign_task` calls can be in flight in parallel against the same or different subagents. This is the primary tool for fan-out work; do NOT use `delegate` to spawn multiple investigations sequentially.",
31328
+ description: "Queue a task on a previously spawned subagent. NON-BLOCKING: returns a `taskId` IMMEDIATELY \u2014 the subagent processes the task on its next iteration with its own LLM budget. The `taskId` is the durable handle for retrieving the result later via `await_tasks`, `roll_up`, or `ask_result`. Every assignment MUST carry an explicit boundary: `scope` (what the work covers) and `outOfScope` (at least one concrete non-goal) \u2014 the call is rejected without them, and the worker treats the rendered boundary block as a hard contract. Many `assign_task` calls can be in flight in parallel against the same or different subagents. This is the primary tool for fan-out work; do NOT use `delegate` to spawn multiple investigations sequentially.",
31241
31329
  permission: "auto",
31242
31330
  mutating: false,
31243
31331
  capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
31244
31332
  inputSchema,
31245
31333
  async execute(input) {
31246
31334
  const i = input;
31335
+ const boundary = parseTaskBoundary(i);
31336
+ if (!boundary.ok) {
31337
+ return {
31338
+ ok: false,
31339
+ error: `assign_task rejected \u2014 task boundary incomplete: ${boundary.error}`,
31340
+ hint: boundary.hint
31341
+ };
31342
+ }
31247
31343
  const task = {
31248
31344
  id: randomUUID12(),
31249
- description: i.description,
31345
+ description: composeBoundedTaskDescription(i.description, boundary.boundary),
31250
31346
  subagentId: i.subagentId,
31251
31347
  maxToolCalls: i.maxToolCalls,
31252
31348
  timeoutMs: i.timeoutMs
@@ -39083,8 +39179,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39083
39179
  withNickname(subagent, subagentId) {
39084
39180
  const role = subagent.role ?? "subagent";
39085
39181
  const name = subagent.name?.trim() ?? "";
39086
- const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
39087
- if (!isPlaceholder) return subagent;
39182
+ const isPlaceholder2 = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
39183
+ if (!isPlaceholder2) return subagent;
39088
39184
  const { key, display } = assignNickname(role, this.usedNicknames);
39089
39185
  this.usedNicknames.add(key);
39090
39186
  this.subagentNicknames.set(subagentId, key);
@@ -43320,8 +43416,9 @@ function createDelegateTool(opts) {
43320
43416
  properties: {
43321
43417
  task: {
43322
43418
  type: "string",
43323
- description: "What the subagent should do \u2014 natural language, complete sentence(s)."
43419
+ description: "The objective \u2014 what the subagent should do, natural language, complete sentence(s). Pair it with the required `scope` and `outOfScope` boundary fields."
43324
43420
  },
43421
+ ...taskBoundarySchemaProperties,
43325
43422
  role: {
43326
43423
  type: "string",
43327
43424
  description: rosterIds.length > 0 ? "Roster role id. Common: bug-hunter, security-scanner, refactor-planner, critic, audit-log, executor, shadow-agent, architect." : "No roster configured \u2014 pass `name` instead."
@@ -43379,12 +43476,12 @@ function createDelegateTool(opts) {
43379
43476
  description: "Max fresh-worker continuations after budget exhaustion. Default 1. Each gets the prior partial report."
43380
43477
  }
43381
43478
  },
43382
- required: ["task"]
43479
+ required: ["task", "scope", "outOfScope"]
43383
43480
  };
43384
43481
  return {
43385
43482
  name: "delegate",
43386
43483
  description: "Hand a piece of work to a subagent and block until it returns. This call is synchronous: the leader's iteration pauses for the full duration of the subagent's run. (Multiple `delegate` calls fired in the same assistant turn still parallelize through the provider's parallel-tool-call surface, but each one eats wall-clock time \u2014 so for fan-out you actually control, reach for the async path below.) Use `delegate` when your next step genuinely needs the subagent's verdict \u2014 a review, a fact-check, a sign-off. Has own context, own LLM call, auto-extending budget, and a partial-completion handoff path (maxHandoffs, default 1). Workers cannot recursively spawn.\n\n**Do NOT use `delegate` for long-running work.** While `delegate` is in flight, the leader is fully blocked \u2014 it cannot act on other tools, read mail, or react to the user. If the work might run for tens of minutes or hours (multi-file refactor, monorepo audit, long-running build/test, sweeping migration), the blocking call wastes the leader's time. Use the async tool family instead: `spawn_subagent` to create each worker (returns a `subagentId` immediately), `assign_task` to queue work on it (returns a `taskId` immediately), then `await_tasks` to retrieve results later. The leader keeps doing other work while the worker churns, and a worker that realizes its task will run long can mail the leader (type `steer` or `ask` via `mail_send`) saying *\"my task is going to run long, please spawn a subagent instead\"* so the leader re-dispatches asynchronously instead of waiting.\n\n**Do NOT use `delegate` for fan-out you control.** Multiple sequential `delegate` calls each block the leader, wasting wall-clock time. For independent investigations you want to run in parallel \u2014 security scan + bug hunt + perf review on the same PR \u2014 use the async tool family: `spawn_subagent` to create each worker (returns a `subagentId` immediately), `assign_task` to queue work on it (returns a `taskId` immediately), then the `await_tasks` tool with `{mode: 'any'}` to fold the first useful result into the next decision while the rest keep churning. Reach for `delegate` only when the result gates your next move AND the work is short enough that blocking the leader is acceptable.",
43387
- usageHint: "Set `task` to a complete instruction. Pick `role` from roster or pass `name` for free-form. Reach for `delegate` only when the result gates your next move AND the work is short enough that blocking the leader is acceptable (minutes, not hours). For long-running work or fan-out you control, use `spawn_subagent` + `assign_task` + `await_tasks` instead. Raise `maxHandoffs` (default 1, cap 8) for multi-day or multi-refactor tasks; pass larger `timeoutMs`/`maxIterations`/`maxToolCalls` only when needed.",
43484
+ usageHint: "Set `task` to the objective, then make the edges explicit: `scope` (what the work covers) and `outOfScope` (at least one concrete non-goal) are REQUIRED \u2014 the call is rejected without them, and the worker treats the rendered boundary block as a hard contract. Pick `role` from roster or pass `name` for free-form. Reach for `delegate` only when the result gates your next move AND the work is short enough that blocking the leader is acceptable (minutes, not hours). For long-running work or fan-out you control, use `spawn_subagent` + `assign_task` + `await_tasks` instead. Raise `maxHandoffs` (default 1, cap 8) for multi-day or multi-refactor tasks; pass larger `timeoutMs`/`maxIterations`/`maxToolCalls` only when needed.",
43388
43485
  permission: "auto",
43389
43486
  mutating: false,
43390
43487
  managesOwnTimeout: true,
@@ -43404,6 +43501,14 @@ function createDelegateTool(opts) {
43404
43501
  error: "Delegation cancelled before spawn \u2014 the run was interrupted."
43405
43502
  };
43406
43503
  }
43504
+ const boundary = parseTaskBoundary(i);
43505
+ if (!boundary.ok) {
43506
+ return {
43507
+ ok: false,
43508
+ error: `delegate rejected \u2014 task boundary incomplete: ${boundary.error}`,
43509
+ hint: boundary.hint
43510
+ };
43511
+ }
43407
43512
  const target = i.role ?? i.name ?? "subagent";
43408
43513
  const launchModePreface = [
43409
43514
  "Launch-mode guidance (delegate): you were launched via the synchronous `delegate` tool, so the leader is blocked on this call for the full duration of your run.",
@@ -43474,7 +43579,8 @@ function createDelegateTool(opts) {
43474
43579
  const dir = director;
43475
43580
  const maxHandoffs = Math.min(8, Math.max(0, Math.floor(i.maxHandoffs ?? 1)));
43476
43581
  const handoffs = [];
43477
- let delegatedTask = i.task;
43582
+ const baseBrief = composeBoundedTaskDescription(i.task, boundary.boundary);
43583
+ let delegatedTask = baseBrief;
43478
43584
  let handoffCount = 0;
43479
43585
  for (; ; ) {
43480
43586
  const attemptConfig = (() => {
@@ -43614,7 +43720,7 @@ function createDelegateTool(opts) {
43614
43720
  remainingWork: continuation.remainingWork
43615
43721
  });
43616
43722
  handoffCount += 1;
43617
- delegatedTask = buildHandoffTask(i.task, continuation, handoffCount, maxHandoffs);
43723
+ delegatedTask = buildHandoffTask(baseBrief, continuation, handoffCount, maxHandoffs);
43618
43724
  continue;
43619
43725
  }
43620
43726
  const incomplete = result.report?.completion === "partial";
@@ -58285,7 +58391,7 @@ function readContextWindowPolicy(ctx) {
58285
58391
  function installSubagentAutoCompaction(pipelines, ctx, contextConfig, events) {
58286
58392
  const maxContext = ctx.provider?.capabilities?.maxContext ?? 0;
58287
58393
  if (!(maxContext > 0)) return void 0;
58288
- const policy = resolveContextWindowPolicy(contextConfig ?? {});
58394
+ const policy = resolveContextWindowPolicy(contextConfig ?? {}, void 0, maxContext);
58289
58395
  ctx.meta ??= {};
58290
58396
  ctx.meta["contextWindowPolicy"] = policy;
58291
58397
  const compactor = new HybridCompactor({
@@ -97079,6 +97185,7 @@ export {
97079
97185
  COMPLETED_WORK_LEDGER_MARKER,
97080
97186
  CONFIG_BEHAVIOR_DEFAULTS,
97081
97187
  CONTEXT_WINDOW_MODES,
97188
+ CONTEXT_WINDOW_MODE_PINNED_META_KEY,
97082
97189
  CORE_RECONSTRUCT_EVENTS,
97083
97190
  COUNCIL_JUDGE_PROMPT_PATH,
97084
97191
  COUNCIL_REFUSAL_OPTION_ID,
@@ -97270,6 +97377,7 @@ export {
97270
97377
  KNOWN_TOKEN_GROUPS,
97271
97378
  KNOWN_TOKEN_NAMES,
97272
97379
  KnowledgeGraph,
97380
+ LARGE_WINDOW_DEEP_MODE_THRESHOLD,
97273
97381
  LAYER_1_IDENTITY,
97274
97382
  LEADER_MODEL_SET_TOOL_NAME,
97275
97383
  LEARNED_HARD_LIMIT,
@@ -97546,6 +97654,7 @@ export {
97546
97654
  compileUserRegex,
97547
97655
  completeBrainLlm,
97548
97656
  completePartialObject,
97657
+ composeBoundedTaskDescription,
97549
97658
  composeDirectorPrompt,
97550
97659
  composeSubagentPrompt,
97551
97660
  computeMessageTokens,
@@ -97963,6 +98072,7 @@ export {
97963
98072
  parseReviewSeverity,
97964
98073
  parseSkillFrontmatter,
97965
98074
  parseSkillRef,
98075
+ parseTaskBoundary,
97966
98076
  peekQueuedMessages,
97967
98077
  pendingBtwCount,
97968
98078
  persistReviewReport,
@@ -98021,6 +98131,7 @@ export {
98021
98131
  renderPrometheus,
98022
98132
  renderPrompt,
98023
98133
  renderSkillAugmentation,
98134
+ renderTaskBoundaryBlock,
98024
98135
  repairConfigDefaults,
98025
98136
  repairToolUseAdjacency,
98026
98137
  repeatedReadPressure,
@@ -98185,6 +98296,7 @@ export {
98185
98296
  syncReportCompletion,
98186
98297
  syncReportReopen,
98187
98298
  takeHeapSample,
98299
+ taskBoundarySchemaProperties,
98188
98300
  terminalPolicyDecision,
98189
98301
  tightenHqRedactionPolicy,
98190
98302
  toAlertMessage,
@@ -2895,7 +2895,7 @@ var CONTEXT_WINDOW_MODES = Object.freeze([
2895
2895
  {
2896
2896
  id: "balanced",
2897
2897
  name: "Balanced",
2898
- description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
2898
+ description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed. Windows of 1M+ tokens default to Deep.",
2899
2899
  thresholds: { warn: 0.55, soft: 0.7, hard: 0.85 },
2900
2900
  aggressiveOn: "soft",
2901
2901
  preserveK: 8,
@@ -4656,7 +4656,7 @@ var CONTEXT_WINDOW_MODES = Object.freeze([
4656
4656
  {
4657
4657
  id: "balanced",
4658
4658
  name: "Balanced",
4659
- description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
4659
+ description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed. Windows of 1M+ tokens default to Deep.",
4660
4660
  thresholds: { warn: 0.55, soft: 0.7, hard: 0.85 },
4661
4661
  aggressiveOn: "soft",
4662
4662
  preserveK: 8,
@@ -153,7 +153,7 @@ export interface LaunchConfig {
153
153
  *
154
154
  * Stored so the menu can offer a one-line "Continue with last
155
155
  * settings? [Y/n/q]" summary on the next boot instead of re-asking
156
- * the same 1-of-4 question. Distinct from `mode` (tui/repl) — that
156
+ * the same 1-of-5 question. Distinct from `mode` (tui/repl) — that
157
157
  * field is set by the inner pre-launch prompts that run AFTER the
158
158
  * user has chosen "TUI/REPL" here.
159
159
  *
@@ -171,7 +171,7 @@ export interface LaunchConfig {
171
171
  */
172
172
  export interface LaunchMenuChoice {
173
173
  /** Which top-level surface the user picked from the menu. */
174
- mode: 'tui-repl' | 'webui' | 'simpleui' | 'hq';
174
+ mode: 'tui-repl' | 'webui' | 'simpleui' | 'hq' | 'desktop';
175
175
  /** Port override the user typed (defaults to the surface's default). */
176
176
  port?: number | undefined;
177
177
  /** Host override the user typed (defaults to 127.0.0.1). */
@@ -45,6 +45,23 @@ export interface ContextWindowConfigLike {
45
45
  targetLoad?: number | undefined;
46
46
  }
47
47
  export declare const DEFAULT_CONTEXT_WINDOW_MODE_ID: ContextWindowModeId;
48
+ /**
49
+ * Windows at or above this size default to the `deep` policy instead of
50
+ * `balanced`: 1M-class windows exist to be filled, and balanced's hard line
51
+ * (0.85) would compact at ~890K of a 1.05M window, stranding the tail. Deep
52
+ * holds compaction until 0.96 and keeps a wider verbatim tail. An explicit
53
+ * `frugal`/`deep` choice, custom modes, and per-field threshold overrides are
54
+ * always respected as-is; only the balanced default is swapped.
55
+ */
56
+ export declare const LARGE_WINDOW_DEEP_MODE_THRESHOLD = 1000000;
57
+ /**
58
+ * Meta key the mode-switch surfaces (`/context mode`, WebUI `context.mode.switch`,
59
+ * `/context thresholds`) set after the user deliberately picks a policy for the
60
+ * session. Window-change flows re-resolve the default policy against the new
61
+ * window (so a 1M↔200K model switch keeps the policy scaled to the window) but
62
+ * must leave a user-pinned choice alone.
63
+ */
64
+ export declare const CONTEXT_WINDOW_MODE_PINNED_META_KEY = "contextWindowModePinned";
48
65
  export declare const DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES: Readonly<Record<DeprecatedContextWindowModeId, ContextWindowModeId>>;
49
66
  export declare const CONTEXT_WINDOW_MODES: readonly ContextWindowMode[];
50
67
  export declare function listContextWindowModes(): ContextWindowMode[];
@@ -53,6 +70,6 @@ export declare function isDeprecatedContextWindowModeId(id: string): id is Depre
53
70
  export declare function isContextWindowModeSelectionId(id: string): id is ContextWindowModeSelectionId;
54
71
  export declare function getContextWindowMode(id: string | null | undefined): ContextWindowMode | null;
55
72
  export declare function isContextWindowModeId(id: string): id is ContextWindowModeId;
56
- export declare function resolveContextWindowPolicy(config?: ContextWindowConfigLike, overrideMode?: string | null | undefined): ContextWindowPolicy;
73
+ export declare function resolveContextWindowPolicy(config?: ContextWindowConfigLike, overrideMode?: string | null | undefined, maxContext?: number | undefined): ContextWindowPolicy;
57
74
  export declare function formatContextWindowModeList(activeId?: string | null): string;
58
75
  //# sourceMappingURL=context-window.d.ts.map
@@ -7,7 +7,7 @@ export type { AdaptiveConcurrencyConfig, AgentLearningConfig, AutonomyConfig, Br
7
7
  export { DEFAULT_TUI_THINKING_WORD, FLEET_CHAT_VERBOSITY_VALUES, MAX_TUI_THINKING_WORD_LENGTH, normalizeTokenSavingTier, normalizeTuiThinkingWord, resolveFleetChatVerbosity, resolveTokenSavingTier, THEME_PRESET_IDS, } from './config.js';
8
8
  export type { CompletedWorkEvidence, CompletedWorkSource, ContextEvidenceState, ContextFileEvidence, ContextIntentEvidence, ContextRepeatedReadEvidence, ToolEvidenceStatus, ToolOutputMetadata, } from './context-evidence.js';
9
9
  export type { ContextSnapshot, ContextWindowAggressiveOn, ContextWindowConfigLike, ContextWindowMode, ContextWindowModeId, ContextWindowModeSelectionId, ContextWindowPolicy, ContextWindowThresholds, DeprecatedContextWindowModeId, } from './context-window.js';
10
- export { CONTEXT_WINDOW_MODES, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES, formatContextWindowModeList, getContextWindowMode, isContextWindowModeId, isContextWindowModeSelectionId, isDeprecatedContextWindowModeId, listContextWindowModes, normalizeContextWindowModeId, resolveContextWindowPolicy, } from './context-window.js';
10
+ export { CONTEXT_WINDOW_MODES, CONTEXT_WINDOW_MODE_PINNED_META_KEY, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES, LARGE_WINDOW_DEEP_MODE_THRESHOLD, formatContextWindowModeList, getContextWindowMode, isContextWindowModeId, isContextWindowModeSelectionId, isDeprecatedContextWindowModeId, listContextWindowModes, normalizeContextWindowModeId, resolveContextWindowPolicy, } from './context-window.js';
11
11
  export type { CouncilDistinctness, CouncilLLMCaller, CouncilModelTarget, CouncilOption, CouncilPersona, CouncilProfileConfig, CouncilQuestion, CouncilResolutionMethod, CouncilResult, CouncilSeatConfig, CouncilUsage, CouncilVoteResult, CouncilVoteStatus, ResolvedCouncilProfile, ResolvedCouncilSeat, } from './council.js';
12
12
  export { DEFAULT_AUTONOMY_CONFIG, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONTEXT_CONFIG, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_TOOLS_CONFIG, } from './default-config.js';
13
13
  export type { DesignKitEntry, DesignKitLoader, DesignKitManifest, DesignKitTokens, DesignStack, DesignStudioState, DesignTokenSet, TokenValueKind, } from './design-kit.js';
@@ -111,6 +111,8 @@ function expectDefined(value, label) {
111
111
 
112
112
  // src/types/context-window.ts
113
113
  var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
114
+ var LARGE_WINDOW_DEEP_MODE_THRESHOLD = 1e6;
115
+ var CONTEXT_WINDOW_MODE_PINNED_META_KEY = "contextWindowModePinned";
114
116
  var DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES = Object.freeze({
115
117
  archival: "balanced"
116
118
  });
@@ -118,7 +120,7 @@ var CONTEXT_WINDOW_MODES = Object.freeze([
118
120
  {
119
121
  id: "balanced",
120
122
  name: "Balanced",
121
- description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
123
+ description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed. Windows of 1M+ tokens default to Deep.",
122
124
  thresholds: { warn: 0.55, soft: 0.7, hard: 0.85 },
123
125
  aggressiveOn: "soft",
124
126
  preserveK: 8,
@@ -169,9 +171,11 @@ function getContextWindowMode(id) {
169
171
  function isContextWindowModeId(id) {
170
172
  return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
171
173
  }
172
- function resolveContextWindowPolicy(config = {}, overrideMode) {
174
+ function resolveContextWindowPolicy(config = {}, overrideMode, maxContext) {
173
175
  const requested = overrideMode ?? config.mode ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
174
- const mode = getContextWindowMode(requested) ?? expectDefined(getContextWindowMode(DEFAULT_CONTEXT_WINDOW_MODE_ID));
176
+ const normalized = normalizeContextWindowModeId(requested) ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
177
+ const baseId = normalized === DEFAULT_CONTEXT_WINDOW_MODE_ID && typeof maxContext === "number" && maxContext >= LARGE_WINDOW_DEEP_MODE_THRESHOLD ? "deep" : normalized;
178
+ const mode = expectDefined(getContextWindowMode(baseId));
175
179
  return {
176
180
  ...mode,
177
181
  thresholds: {
@@ -1313,6 +1317,7 @@ export {
1313
1317
  BUILTIN_PROMPT_CATEGORIES,
1314
1318
  CHAT_MARKER_SOURCES,
1315
1319
  CONTEXT_WINDOW_MODES,
1320
+ CONTEXT_WINDOW_MODE_PINNED_META_KEY,
1316
1321
  ConfigError,
1317
1322
  DEFAULT_AUTONOMY_CONFIG,
1318
1323
  DEFAULT_CIRCUIT_BREAKER_CONFIG,
@@ -1333,6 +1338,7 @@ export {
1333
1338
  GOVERNED_TOOL_EXECUTOR_META_KEY,
1334
1339
  KNOWN_TOKEN_GROUPS,
1335
1340
  KNOWN_TOKEN_NAMES,
1341
+ LARGE_WINDOW_DEEP_MODE_THRESHOLD,
1336
1342
  MALFORMED_ARG_MARKERS,
1337
1343
  MAX_TUI_THINKING_WORD_LENGTH,
1338
1344
  MEMORY_TYPE_LABELS,
@@ -48,7 +48,7 @@ For controlled fan-out, use `spawn_subagent` → `assign_task` →
48
48
 
49
49
  ## Dispatch contract
50
50
 
51
- Every assigned task should state:
51
+ Every assigned task must state:
52
52
 
53
53
  - the objective and why it matters;
54
54
  - exact scope and non-goals;
@@ -58,6 +58,14 @@ Every assigned task should state:
58
58
  - the narrowest required verification;
59
59
  - known dependencies, risks, and assumptions.
60
60
 
61
+ The assignment tools enforce the boundary: `delegate` and `assign_task` reject
62
+ any call without an explicit `scope` (what the work covers) and at least one
63
+ concrete `outOfScope` non-goal (what the worker must not do). Treat a
64
+ rejection as a design checkpoint, not paperwork — if you cannot name what is
65
+ out of scope, the task is not decomposed enough yet. The boundary is rendered
66
+ into the worker's brief as a hard contract and survives into handoff
67
+ continuations, so write it once, precisely.
68
+
61
69
  Match role and model to the work: use economical workers for bounded discovery
62
70
  and capable workers for ambiguous implementation or synthesis. Provider
63
71
  diversity is useful for independent review, not an end in itself.
@@ -7,6 +7,10 @@ self-contained handoff; do not take over fleet orchestration.
7
7
  - Treat the assigned objective, scope, write authority, non-goals, and
8
8
  completion criteria as your boundary. Later role, task, and per-spawn
9
9
  instructions may narrow this baseline.
10
+ - Your brief carries an explicit "TASK BOUNDARY" block (scope plus
11
+ out-of-scope non-goals). It is a hard contract, not a suggestion: stay
12
+ inside it even when an out-of-scope change looks quick or obviously right —
13
+ report it back instead of doing it.
10
14
  - Inspect before editing. Resolve discoverable context yourself and use the
11
15
  project's existing conventions, tests, and tooling.
12
16
  - Make only task-relevant changes. Preserve unrelated work and avoid broad
@@ -192,6 +196,9 @@ Your final output is an integration artifact for the Director. It must state:
192
196
  - files materially examined or changed;
193
197
  - verification commands and observed results;
194
198
  - atomic findings, decisions, or behavior changes;
199
+ - out-of-scope observations — issues you noticed but did not touch, each
200
+ with a file/symbol anchor — so the Director can surface them to the user
201
+ instead of a worker silently fixing them;
195
202
  - uncertainty flags, blockers, and exact remaining work.
196
203
 
197
204
  Never end with a bare “done.” Distinguish direct evidence from inference and
@@ -9,10 +9,10 @@ The user is an experienced developer; accelerate them and stay focused.
9
9
 
10
10
  1. Understand the real request before acting.
11
11
  2. Ask one concrete question only when ambiguity changes the approach.
12
- 3. For clear requests, proceed with the smallest safe change.
12
+ 3. For clear requests, proceed with the smallest safe change; before non-trivial work, state in one short line what is in scope and what is not.
13
13
  4. Read relevant files before editing them.
14
14
  5. Prefer surgical edits over rewrites.
15
- 6. Do not change unrelated code.
15
+ 6. Do not change unrelated code; if you notice an unrelated problem, report it in your summary instead of fixing it.
16
16
  7. Match the file's existing conventions; add a dependency only when the task requires it.
17
17
  8. The cost ladder — before writing new code, stop at the first rung that answers: can it be deleted instead; does it need to exist; does this repo already do it; does the language, runtime, or platform do it; does an installed dependency do it; is it one line? Only then write the minimum that works.
18
18
  9. The ladder trims code you invented, never the user's request. Reuse claims need a named file, symbol, or package — not recollection. Do not narrate rung numbers.
@@ -106,13 +106,13 @@ Reasoning depth is a dial, not a constant. Match it to the blast radius of what
106
106
  <!--ws:else-->
107
107
  2. **Honor the live tool boundary.** If this request is read-only, report findings without proposing unavailable calls.
108
108
  <!--ws:end-->
109
- 3. **Announce, then act.** Before a non-trivial change, one sentence on what you're about to do — not a wall of text. Afterwards, summarize the outcome, not the mechanics.
109
+ 3. **Announce the edges, then act.** Before a non-trivial change, one short statement of what you're about to do and what is explicitly out of scope for this task — not a wall of text. Afterwards, summarize the outcome, not the mechanics, and surface any out-of-scope issues you noticed but did not touch.
110
110
  4. **Be honest about limits, precisely.** If you don't know, say so. Never fabricate file contents, command output, or test results. Never call work "production-ready" or "fully tested" — the user makes that call. State what you ran and what it returned; do not imply verification you did not perform.
111
111
  5. **Separate verified from assumed.** Use plain markers in reports: *verified* (you ran it / read it), *assumed* (reasonable inference, unchecked), *unknown* (needs the user or a tool you lack). One glance should tell the user how much to trust each claim.
112
112
  6. **Be concise and scannable.** No marketing language, no filler. If a one-liner answers, a one-liner is the answer. Code blocks for code, backticks for paths, bold for key terms; paragraphs max 3 sentences. (Active modes may override verbosity.)
113
113
  7. **Match the user's language.** Reply in the language the user writes in; if they mix, follow the dominant one.
114
114
  8. **Ask when blocked, proceed when not.** If ambiguity meaningfully changes the approach (unclear file, conflicting requirements), ask. Otherwise pick a reasonable default, state the assumption, and proceed.
115
- 9. **Stay focused, stay native.** Fix only what was asked — no refactoring or reformatting of neighboring code. Match the surrounding code's conventions (naming, imports, error handling) instead of imposing your own, and add a new dependency only when the task requires it and you say so. Comment only to explain *why*, not *what*. Don't lecture about engineering principles unless asked.
115
+ 9. **Stay focused, stay native.** Fix only what was asked — no refactoring or reformatting of neighboring code. When you notice an unrelated problem while working (another bug five lines above the one you were asked to fix, a neighboring broken test, a suspicious call site), do not fix it — name it in your final summary as an observation and leave the decision to the user. Match the surrounding code's conventions (naming, imports, error handling) instead of imposing your own, and add a new dependency only when the task requires it and you say so. Comment only to explain *why*, not *what*. Don't lecture about engineering principles unless asked.
116
116
  10. **The working tree is shared.** Never commit, push, amend, or discard changes unless the user asked for it. Treat destructive commands (recursive delete, hard reset, force push, history rewrites) as requiring an explicit request — never run them as convenience cleanup.
117
117
  11. **Leave the knowledge behind, not just the diff.** A task that taught you something durable about this codebase isn't finished until that knowledge is in memory (see Memory management).
118
118
  12. **Keep helper scripts temporary and contained.** This rule applies to every agent, regardless of role (leader, coordinator, or subagent). Create all ad hoc helper scripts and their temporary inputs/outputs only under `<project-root>/.temp_files/` — never in the repository root or source directories. Write each helper script so its paths, imports, and generated artifacts work from that location. Delete the helper script and any temporary artifacts it created as soon as they are no longer needed, and always before reporting the task complete. Only remove files created for the current task; never delete pre-existing or user-owned contents of `.temp_files/`. This rule does not apply to permanent project scripts explicitly requested by the user.
@@ -49,12 +49,12 @@ This parse is **internal reasoning**, not something you output. It keeps you anc
49
49
  <!--ws:else-->
50
50
  2. **Honor the live tool boundary.** If this request is read-only, report findings without proposing unavailable calls.
51
51
  <!--ws:end-->
52
- 3. **Announce, then act.** Before a non-trivial change, one sentence on what you're about to do — not a wall of text. Afterwards, summarize the outcome, not the mechanics.
52
+ 3. **Announce the edges, then act.** Before a non-trivial change, one short statement of what you're about to do and what is explicitly out of scope for this task — not a wall of text. Afterwards, summarize the outcome, not the mechanics, and surface any out-of-scope issues you noticed but did not touch.
53
53
  4. **Be honest about limits.** If you don't know, say so. Never fabricate file contents, command output, or test results. Never call work "production-ready" or "fully tested" — the user makes that call.
54
54
  5. **Be concise and scannable.** No marketing language, no filler. If a one-liner answers, a one-liner is the answer. Code blocks for code, backticks for paths, bold for key terms; paragraphs max 3 sentences. (Active modes may override verbosity.)
55
55
  6. **Match the user's language.** Reply in the language the user writes in; if they mix, follow the dominant one.
56
56
  7. **Ask when blocked, proceed when not.** If ambiguity meaningfully changes the approach (unclear file, conflicting requirements), ask. Otherwise pick a reasonable default, state the assumption, and proceed.
57
- 8. **Stay focused, stay native.** Fix only what was asked — no refactoring or reformatting of neighboring code. Match the surrounding code's conventions (naming, imports, error handling) instead of imposing your own, and add a new dependency only when the task requires it and you say so. Comment only to explain *why*, not *what*. Don't lecture about engineering principles unless asked.
57
+ 8. **Stay focused, stay native.** Fix only what was asked — no refactoring or reformatting of neighboring code. When you notice an unrelated problem while working (another bug five lines above the one you were asked to fix, a neighboring broken test, a suspicious call site), do not fix it — name it in your final summary as an observation and leave the decision to the user. Match the surrounding code's conventions (naming, imports, error handling) instead of imposing your own, and add a new dependency only when the task requires it and you say so. Comment only to explain *why*, not *what*. Don't lecture about engineering principles unless asked.
58
58
  9. **The working tree is shared.** Never commit, push, amend, or discard changes unless the user asked for it. Treat destructive commands (recursive delete, hard reset, force push, history rewrites) as requiring an explicit request — never run them as convenience cleanup.
59
59
  10. **Keep helper scripts temporary and contained.** This rule applies to every agent, regardless of role (leader, coordinator, or subagent). Create all ad hoc helper scripts and their temporary inputs/outputs only under `<project-root>/.temp_files/` — never in the repository root or source directories. Write each helper script so its paths, imports, and generated artifacts work from that location. Delete the helper script and any temporary artifacts it created as soon as they are no longer needed, and always before reporting the task complete. Only remove files created for the current task; never delete pre-existing or user-owned contents of `.temp_files/`. This rule does not apply to permanent project scripts explicitly requested by the user.
60
60
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/core",
3
- "version": "0.308.1",
3
+ "version": "0.308.4",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack core: kernel, types, defaults, and shared utilities for the WrongStack CLI agent.",
6
6
  "repository": {
@@ -94,7 +94,8 @@
94
94
  },
95
95
  "./storage": {
96
96
  "types": "./dist/storage/index.d.ts",
97
- "import": "./dist/storage/index.js"
97
+ "import": "./dist/storage/index.js",
98
+ "require": "./dist/storage/index.js"
98
99
  },
99
100
  "./session-catalog": {
100
101
  "types": "./dist/session-catalog/index.d.ts",
@@ -181,8 +182,8 @@
181
182
  "wrongstackApiVersion": "0.1.10",
182
183
  "dependencies": {
183
184
  "zod": "4.4.3",
184
- "@wrongstack/kanban": "0.308.1",
185
- "@wrongstack/persistence": "0.308.1"
185
+ "@wrongstack/persistence": "0.308.4",
186
+ "@wrongstack/kanban": "0.308.4"
186
187
  },
187
188
  "devDependencies": {
188
189
  "@types/node": "^26.2.0",