@wrongstack/core 0.308.1 → 0.308.2

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';
@@ -10481,6 +10481,80 @@ var FLEET_ROSTER_WITHACP = {
10481
10481
  ...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
10482
10482
  };
10483
10483
 
10484
+ // src/coordination/task-boundary.ts
10485
+ var PLACEHOLDER_VALUES = /* @__PURE__ */ new Set([
10486
+ "n/a",
10487
+ "na",
10488
+ "none",
10489
+ "nothing",
10490
+ "tbd",
10491
+ "todo",
10492
+ "unknown",
10493
+ "unspecified",
10494
+ "-",
10495
+ "\u2014",
10496
+ ".",
10497
+ "as above",
10498
+ "same as above",
10499
+ "see above",
10500
+ "see task",
10501
+ "same as task"
10502
+ ]);
10503
+ var isPlaceholder = (value) => PLACEHOLDER_VALUES.has(value.trim().toLowerCase());
10504
+ var MIN_SCOPE_CHARS = 8;
10505
+ var MIN_NON_GOAL_CHARS = 3;
10506
+ function parseTaskBoundary(raw) {
10507
+ const scope = typeof raw.scope === "string" ? raw.scope.trim() : "";
10508
+ if (scope.length < MIN_SCOPE_CHARS) {
10509
+ return {
10510
+ ok: false,
10511
+ error: `\`scope\` is missing or too vague \u2014 state in one concrete sentence what work this task covers (files, components, or commands in-bounds).`,
10512
+ hint: 'Example \u2014 scope: "Audit packages/core/src/parser/*.ts for unhandled token errors and report findings."'
10513
+ };
10514
+ }
10515
+ if (!Array.isArray(raw.outOfScope) || raw.outOfScope.length === 0) {
10516
+ return {
10517
+ ok: false,
10518
+ error: "`outOfScope` must be an array with at least one explicit non-goal \u2014 things the worker must NOT do.",
10519
+ hint: 'Example \u2014 outOfScope: ["Do not modify files outside packages/core", "Do not fix the bugs you find, only report them"].'
10520
+ };
10521
+ }
10522
+ const concrete = raw.outOfScope.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length >= MIN_NON_GOAL_CHARS && !isPlaceholder(item));
10523
+ if (concrete.length === 0) {
10524
+ return {
10525
+ ok: false,
10526
+ 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.',
10527
+ 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.'
10528
+ };
10529
+ }
10530
+ return { ok: true, boundary: { scope, outOfScope: concrete } };
10531
+ }
10532
+ function renderTaskBoundaryBlock(boundary) {
10533
+ return [
10534
+ "\u2500\u2500 TASK BOUNDARY (hard contract \u2014 these lines define your edges) \u2500\u2500",
10535
+ `Scope (what this task covers):
10536
+ ${boundary.scope}`,
10537
+ `Out of scope (explicit non-goals \u2014 do NOT do any of these):
10538
+ ${boundary.outOfScope.map((item) => `- ${item}`).join("\n")}`
10539
+ ].join("\n");
10540
+ }
10541
+ function composeBoundedTaskDescription(objective, boundary) {
10542
+ return `${objective.trim()}
10543
+
10544
+ ${renderTaskBoundaryBlock(boundary)}`;
10545
+ }
10546
+ var taskBoundarySchemaProperties = {
10547
+ scope: {
10548
+ type: "string",
10549
+ description: "REQUIRED. One concrete sentence stating what work this task covers \u2014 the in-bounds. The call is rejected without it."
10550
+ },
10551
+ outOfScope: {
10552
+ type: "array",
10553
+ items: { type: "string", minLength: 1 },
10554
+ 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.'
10555
+ }
10556
+ };
10557
+
10484
10558
  // src/coordination/delegate-tool.ts
10485
10559
  function createDelegateTool(opts) {
10486
10560
  const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
@@ -10490,8 +10564,9 @@ function createDelegateTool(opts) {
10490
10564
  properties: {
10491
10565
  task: {
10492
10566
  type: "string",
10493
- description: "What the subagent should do \u2014 natural language, complete sentence(s)."
10567
+ 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
10568
  },
10569
+ ...taskBoundarySchemaProperties,
10495
10570
  role: {
10496
10571
  type: "string",
10497
10572
  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 +10624,12 @@ function createDelegateTool(opts) {
10549
10624
  description: "Max fresh-worker continuations after budget exhaustion. Default 1. Each gets the prior partial report."
10550
10625
  }
10551
10626
  },
10552
- required: ["task"]
10627
+ required: ["task", "scope", "outOfScope"]
10553
10628
  };
10554
10629
  return {
10555
10630
  name: "delegate",
10556
10631
  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.",
10632
+ 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
10633
  permission: "auto",
10559
10634
  mutating: false,
10560
10635
  managesOwnTimeout: true,
@@ -10574,6 +10649,14 @@ function createDelegateTool(opts) {
10574
10649
  error: "Delegation cancelled before spawn \u2014 the run was interrupted."
10575
10650
  };
10576
10651
  }
10652
+ const boundary = parseTaskBoundary(i);
10653
+ if (!boundary.ok) {
10654
+ return {
10655
+ ok: false,
10656
+ error: `delegate rejected \u2014 task boundary incomplete: ${boundary.error}`,
10657
+ hint: boundary.hint
10658
+ };
10659
+ }
10577
10660
  const target = i.role ?? i.name ?? "subagent";
10578
10661
  const launchModePreface = [
10579
10662
  "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 +10727,8 @@ function createDelegateTool(opts) {
10644
10727
  const dir = director;
10645
10728
  const maxHandoffs = Math.min(8, Math.max(0, Math.floor(i.maxHandoffs ?? 1)));
10646
10729
  const handoffs = [];
10647
- let delegatedTask = i.task;
10730
+ const baseBrief = composeBoundedTaskDescription(i.task, boundary.boundary);
10731
+ let delegatedTask = baseBrief;
10648
10732
  let handoffCount = 0;
10649
10733
  for (; ; ) {
10650
10734
  const attemptConfig = (() => {
@@ -10784,7 +10868,7 @@ function createDelegateTool(opts) {
10784
10868
  remainingWork: continuation.remainingWork
10785
10869
  });
10786
10870
  handoffCount += 1;
10787
- delegatedTask = buildHandoffTask(i.task, continuation, handoffCount, maxHandoffs);
10871
+ delegatedTask = buildHandoffTask(baseBrief, continuation, handoffCount, maxHandoffs);
10788
10872
  continue;
10789
10873
  }
10790
10874
  const incomplete = result.report?.completion === "partial";
@@ -12688,8 +12772,9 @@ function makeAssignTool(director) {
12688
12772
  description: {
12689
12773
  type: "string",
12690
12774
  minLength: 1,
12691
- description: "The task in natural language \u2014 what you want this subagent to do."
12775
+ 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
12776
  },
12777
+ ...taskBoundarySchemaProperties,
12693
12778
  maxToolCalls: {
12694
12779
  type: "number",
12695
12780
  minimum: 1,
@@ -12697,20 +12782,28 @@ function makeAssignTool(director) {
12697
12782
  },
12698
12783
  timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
12699
12784
  },
12700
- required: ["subagentId", "description"]
12785
+ required: ["subagentId", "description", "scope", "outOfScope"]
12701
12786
  };
12702
12787
  return {
12703
12788
  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.",
12789
+ 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
12790
  permission: "auto",
12706
12791
  mutating: false,
12707
12792
  capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
12708
12793
  inputSchema,
12709
12794
  async execute(input) {
12710
12795
  const i = input;
12796
+ const boundary = parseTaskBoundary(i);
12797
+ if (!boundary.ok) {
12798
+ return {
12799
+ ok: false,
12800
+ error: `assign_task rejected \u2014 task boundary incomplete: ${boundary.error}`,
12801
+ hint: boundary.hint
12802
+ };
12803
+ }
12711
12804
  const task = {
12712
12805
  id: randomUUID8(),
12713
- description: i.description,
12806
+ description: composeBoundedTaskDescription(i.description, boundary.boundary),
12714
12807
  subagentId: i.subagentId,
12715
12808
  maxToolCalls: i.maxToolCalls,
12716
12809
  timeoutMs: i.timeoutMs
@@ -20503,8 +20596,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
20503
20596
  withNickname(subagent, subagentId) {
20504
20597
  const role = subagent.role ?? "subagent";
20505
20598
  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;
20599
+ const isPlaceholder2 = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
20600
+ if (!isPlaceholder2) return subagent;
20508
20601
  const { key, display } = assignNickname(role, this.usedNicknames);
20509
20602
  this.usedNicknames.add(key);
20510
20603
  this.subagentNicknames.set(subagentId, key);
@@ -31367,6 +31460,7 @@ export {
31367
31460
  clearProjectSkillAugmentation,
31368
31461
  collabInjectMiddleware,
31369
31462
  collabPauseMiddleware,
31463
+ composeBoundedTaskDescription,
31370
31464
  composeDirectorPrompt,
31371
31465
  composeSubagentPrompt,
31372
31466
  consolidatedDocumentPath,
@@ -31451,6 +31545,7 @@ export {
31451
31545
  parseMailboxAckInput,
31452
31546
  parseMailboxQueryInput,
31453
31547
  parseMailboxSendInput,
31548
+ parseTaskBoundary,
31454
31549
  phaseForRole,
31455
31550
  quarantinePath,
31456
31551
  rankRoleSkills,
@@ -31467,6 +31562,7 @@ export {
31467
31562
  refreshProjectAgentIdentity,
31468
31563
  release,
31469
31564
  renderSkillAugmentation,
31565
+ renderTaskBoundaryBlock,
31470
31566
  resetCaptureWindow,
31471
31567
  resetCaptureWindows,
31472
31568
  resetProjectAgentIdentity,
@@ -31497,6 +31593,7 @@ export {
31497
31593
  startPackageOutdatedWatcher,
31498
31594
  startTechStackConsumer,
31499
31595
  subagentNeedsWorktree,
31596
+ taskBoundarySchemaProperties,
31500
31597
  terminalPolicyDecision,
31501
31598
  unwrapWholeDocumentFence,
31502
31599
  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
@@ -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";
@@ -8503,8 +8587,9 @@ function makeAssignTool(director) {
8503
8587
  description: {
8504
8588
  type: "string",
8505
8589
  minLength: 1,
8506
- description: "The task in natural language \u2014 what you want this subagent to do."
8590
+ 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
8591
  },
8592
+ ...taskBoundarySchemaProperties,
8508
8593
  maxToolCalls: {
8509
8594
  type: "number",
8510
8595
  minimum: 1,
@@ -8512,20 +8597,28 @@ function makeAssignTool(director) {
8512
8597
  },
8513
8598
  timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
8514
8599
  },
8515
- required: ["subagentId", "description"]
8600
+ required: ["subagentId", "description", "scope", "outOfScope"]
8516
8601
  };
8517
8602
  return {
8518
8603
  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.",
8604
+ 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
8605
  permission: "auto",
8521
8606
  mutating: false,
8522
8607
  capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
8523
8608
  inputSchema,
8524
8609
  async execute(input) {
8525
8610
  const i = input;
8611
+ const boundary = parseTaskBoundary(i);
8612
+ if (!boundary.ok) {
8613
+ return {
8614
+ ok: false,
8615
+ error: `assign_task rejected \u2014 task boundary incomplete: ${boundary.error}`,
8616
+ hint: boundary.hint
8617
+ };
8618
+ }
8526
8619
  const task = {
8527
8620
  id: randomUUID6(),
8528
- description: i.description,
8621
+ description: composeBoundedTaskDescription(i.description, boundary.boundary),
8529
8622
  subagentId: i.subagentId,
8530
8623
  maxToolCalls: i.maxToolCalls,
8531
8624
  timeoutMs: i.timeoutMs
@@ -19228,8 +19321,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
19228
19321
  withNickname(subagent, subagentId) {
19229
19322
  const role = subagent.role ?? "subagent";
19230
19323
  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;
19324
+ const isPlaceholder2 = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
19325
+ if (!isPlaceholder2) return subagent;
19233
19326
  const { key, display } = assignNickname(role, this.usedNicknames);
19234
19327
  this.usedNicknames.add(key);
19235
19328
  this.subagentNicknames.set(subagentId, key);
@@ -22497,6 +22590,7 @@ function normalizeTargetLoad(targetLoad, thresholds) {
22497
22590
 
22498
22591
  // src/types/context-window.ts
22499
22592
  var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
22593
+ var LARGE_WINDOW_DEEP_MODE_THRESHOLD = 1e6;
22500
22594
  var DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES = Object.freeze({
22501
22595
  archival: "balanced"
22502
22596
  });
@@ -22504,7 +22598,7 @@ var CONTEXT_WINDOW_MODES = Object.freeze([
22504
22598
  {
22505
22599
  id: "balanced",
22506
22600
  name: "Balanced",
22507
- description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
22601
+ description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed. Windows of 1M+ tokens default to Deep.",
22508
22602
  thresholds: { warn: 0.55, soft: 0.7, hard: 0.85 },
22509
22603
  aggressiveOn: "soft",
22510
22604
  preserveK: 8,
@@ -22555,9 +22649,11 @@ function getContextWindowMode(id) {
22555
22649
  function isContextWindowModeId(id) {
22556
22650
  return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
22557
22651
  }
22558
- function resolveContextWindowPolicy(config = {}, overrideMode) {
22652
+ function resolveContextWindowPolicy(config = {}, overrideMode, maxContext) {
22559
22653
  const requested = overrideMode ?? config.mode ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
22560
- const mode = getContextWindowMode(requested) ?? expectDefined(getContextWindowMode(DEFAULT_CONTEXT_WINDOW_MODE_ID));
22654
+ const normalized = normalizeContextWindowModeId(requested) ?? DEFAULT_CONTEXT_WINDOW_MODE_ID;
22655
+ const baseId = normalized === DEFAULT_CONTEXT_WINDOW_MODE_ID && typeof maxContext === "number" && maxContext >= LARGE_WINDOW_DEEP_MODE_THRESHOLD ? "deep" : normalized;
22656
+ const mode = expectDefined(getContextWindowMode(baseId));
22561
22657
  return {
22562
22658
  ...mode,
22563
22659
  thresholds: {
@@ -22972,7 +23068,7 @@ function readContextWindowPolicy(ctx) {
22972
23068
  function installSubagentAutoCompaction(pipelines, ctx, contextConfig, events) {
22973
23069
  const maxContext = ctx.provider?.capabilities?.maxContext ?? 0;
22974
23070
  if (!(maxContext > 0)) return void 0;
22975
- const policy = resolveContextWindowPolicy(contextConfig ?? {});
23071
+ const policy = resolveContextWindowPolicy(contextConfig ?? {}, void 0, maxContext);
22976
23072
  ctx.meta ??= {};
22977
23073
  ctx.meta["contextWindowPolicy"] = policy;
22978
23074
  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: {
@@ -31216,6 +31220,82 @@ function makeLLMClassifier(complete2) {
31216
31220
  // src/coordination/director-basic-tools.ts
31217
31221
  import { randomUUID as randomUUID12 } from "node:crypto";
31218
31222
  init_error();
31223
+
31224
+ // src/coordination/task-boundary.ts
31225
+ var PLACEHOLDER_VALUES = /* @__PURE__ */ new Set([
31226
+ "n/a",
31227
+ "na",
31228
+ "none",
31229
+ "nothing",
31230
+ "tbd",
31231
+ "todo",
31232
+ "unknown",
31233
+ "unspecified",
31234
+ "-",
31235
+ "\u2014",
31236
+ ".",
31237
+ "as above",
31238
+ "same as above",
31239
+ "see above",
31240
+ "see task",
31241
+ "same as task"
31242
+ ]);
31243
+ var isPlaceholder = (value) => PLACEHOLDER_VALUES.has(value.trim().toLowerCase());
31244
+ var MIN_SCOPE_CHARS = 8;
31245
+ var MIN_NON_GOAL_CHARS = 3;
31246
+ function parseTaskBoundary(raw) {
31247
+ const scope = typeof raw.scope === "string" ? raw.scope.trim() : "";
31248
+ if (scope.length < MIN_SCOPE_CHARS) {
31249
+ return {
31250
+ ok: false,
31251
+ error: `\`scope\` is missing or too vague \u2014 state in one concrete sentence what work this task covers (files, components, or commands in-bounds).`,
31252
+ hint: 'Example \u2014 scope: "Audit packages/core/src/parser/*.ts for unhandled token errors and report findings."'
31253
+ };
31254
+ }
31255
+ if (!Array.isArray(raw.outOfScope) || raw.outOfScope.length === 0) {
31256
+ return {
31257
+ ok: false,
31258
+ error: "`outOfScope` must be an array with at least one explicit non-goal \u2014 things the worker must NOT do.",
31259
+ hint: 'Example \u2014 outOfScope: ["Do not modify files outside packages/core", "Do not fix the bugs you find, only report them"].'
31260
+ };
31261
+ }
31262
+ const concrete = raw.outOfScope.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length >= MIN_NON_GOAL_CHARS && !isPlaceholder(item));
31263
+ if (concrete.length === 0) {
31264
+ return {
31265
+ ok: false,
31266
+ 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.',
31267
+ 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.'
31268
+ };
31269
+ }
31270
+ return { ok: true, boundary: { scope, outOfScope: concrete } };
31271
+ }
31272
+ function renderTaskBoundaryBlock(boundary) {
31273
+ return [
31274
+ "\u2500\u2500 TASK BOUNDARY (hard contract \u2014 these lines define your edges) \u2500\u2500",
31275
+ `Scope (what this task covers):
31276
+ ${boundary.scope}`,
31277
+ `Out of scope (explicit non-goals \u2014 do NOT do any of these):
31278
+ ${boundary.outOfScope.map((item) => `- ${item}`).join("\n")}`
31279
+ ].join("\n");
31280
+ }
31281
+ function composeBoundedTaskDescription(objective, boundary) {
31282
+ return `${objective.trim()}
31283
+
31284
+ ${renderTaskBoundaryBlock(boundary)}`;
31285
+ }
31286
+ var taskBoundarySchemaProperties = {
31287
+ scope: {
31288
+ type: "string",
31289
+ description: "REQUIRED. One concrete sentence stating what work this task covers \u2014 the in-bounds. The call is rejected without it."
31290
+ },
31291
+ outOfScope: {
31292
+ type: "array",
31293
+ items: { type: "string", minLength: 1 },
31294
+ 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.'
31295
+ }
31296
+ };
31297
+
31298
+ // src/coordination/director-basic-tools.ts
31219
31299
  function makeAssignTool(director) {
31220
31300
  const inputSchema = {
31221
31301
  type: "object",
@@ -31224,8 +31304,9 @@ function makeAssignTool(director) {
31224
31304
  description: {
31225
31305
  type: "string",
31226
31306
  minLength: 1,
31227
- description: "The task in natural language \u2014 what you want this subagent to do."
31307
+ 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
31308
  },
31309
+ ...taskBoundarySchemaProperties,
31229
31310
  maxToolCalls: {
31230
31311
  type: "number",
31231
31312
  minimum: 1,
@@ -31233,20 +31314,28 @@ function makeAssignTool(director) {
31233
31314
  },
31234
31315
  timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
31235
31316
  },
31236
- required: ["subagentId", "description"]
31317
+ required: ["subagentId", "description", "scope", "outOfScope"]
31237
31318
  };
31238
31319
  return {
31239
31320
  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.",
31321
+ 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
31322
  permission: "auto",
31242
31323
  mutating: false,
31243
31324
  capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
31244
31325
  inputSchema,
31245
31326
  async execute(input) {
31246
31327
  const i = input;
31328
+ const boundary = parseTaskBoundary(i);
31329
+ if (!boundary.ok) {
31330
+ return {
31331
+ ok: false,
31332
+ error: `assign_task rejected \u2014 task boundary incomplete: ${boundary.error}`,
31333
+ hint: boundary.hint
31334
+ };
31335
+ }
31247
31336
  const task = {
31248
31337
  id: randomUUID12(),
31249
- description: i.description,
31338
+ description: composeBoundedTaskDescription(i.description, boundary.boundary),
31250
31339
  subagentId: i.subagentId,
31251
31340
  maxToolCalls: i.maxToolCalls,
31252
31341
  timeoutMs: i.timeoutMs
@@ -39083,8 +39172,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
39083
39172
  withNickname(subagent, subagentId) {
39084
39173
  const role = subagent.role ?? "subagent";
39085
39174
  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;
39175
+ const isPlaceholder2 = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
39176
+ if (!isPlaceholder2) return subagent;
39088
39177
  const { key, display } = assignNickname(role, this.usedNicknames);
39089
39178
  this.usedNicknames.add(key);
39090
39179
  this.subagentNicknames.set(subagentId, key);
@@ -43320,8 +43409,9 @@ function createDelegateTool(opts) {
43320
43409
  properties: {
43321
43410
  task: {
43322
43411
  type: "string",
43323
- description: "What the subagent should do \u2014 natural language, complete sentence(s)."
43412
+ 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
43413
  },
43414
+ ...taskBoundarySchemaProperties,
43325
43415
  role: {
43326
43416
  type: "string",
43327
43417
  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 +43469,12 @@ function createDelegateTool(opts) {
43379
43469
  description: "Max fresh-worker continuations after budget exhaustion. Default 1. Each gets the prior partial report."
43380
43470
  }
43381
43471
  },
43382
- required: ["task"]
43472
+ required: ["task", "scope", "outOfScope"]
43383
43473
  };
43384
43474
  return {
43385
43475
  name: "delegate",
43386
43476
  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.",
43477
+ 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
43478
  permission: "auto",
43389
43479
  mutating: false,
43390
43480
  managesOwnTimeout: true,
@@ -43404,6 +43494,14 @@ function createDelegateTool(opts) {
43404
43494
  error: "Delegation cancelled before spawn \u2014 the run was interrupted."
43405
43495
  };
43406
43496
  }
43497
+ const boundary = parseTaskBoundary(i);
43498
+ if (!boundary.ok) {
43499
+ return {
43500
+ ok: false,
43501
+ error: `delegate rejected \u2014 task boundary incomplete: ${boundary.error}`,
43502
+ hint: boundary.hint
43503
+ };
43504
+ }
43407
43505
  const target = i.role ?? i.name ?? "subagent";
43408
43506
  const launchModePreface = [
43409
43507
  "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 +43572,8 @@ function createDelegateTool(opts) {
43474
43572
  const dir = director;
43475
43573
  const maxHandoffs = Math.min(8, Math.max(0, Math.floor(i.maxHandoffs ?? 1)));
43476
43574
  const handoffs = [];
43477
- let delegatedTask = i.task;
43575
+ const baseBrief = composeBoundedTaskDescription(i.task, boundary.boundary);
43576
+ let delegatedTask = baseBrief;
43478
43577
  let handoffCount = 0;
43479
43578
  for (; ; ) {
43480
43579
  const attemptConfig = (() => {
@@ -43614,7 +43713,7 @@ function createDelegateTool(opts) {
43614
43713
  remainingWork: continuation.remainingWork
43615
43714
  });
43616
43715
  handoffCount += 1;
43617
- delegatedTask = buildHandoffTask(i.task, continuation, handoffCount, maxHandoffs);
43716
+ delegatedTask = buildHandoffTask(baseBrief, continuation, handoffCount, maxHandoffs);
43618
43717
  continue;
43619
43718
  }
43620
43719
  const incomplete = result.report?.completion === "partial";
@@ -58285,7 +58384,7 @@ function readContextWindowPolicy(ctx) {
58285
58384
  function installSubagentAutoCompaction(pipelines, ctx, contextConfig, events) {
58286
58385
  const maxContext = ctx.provider?.capabilities?.maxContext ?? 0;
58287
58386
  if (!(maxContext > 0)) return void 0;
58288
- const policy = resolveContextWindowPolicy(contextConfig ?? {});
58387
+ const policy = resolveContextWindowPolicy(contextConfig ?? {}, void 0, maxContext);
58289
58388
  ctx.meta ??= {};
58290
58389
  ctx.meta["contextWindowPolicy"] = policy;
58291
58390
  const compactor = new HybridCompactor({
@@ -97079,6 +97178,7 @@ export {
97079
97178
  COMPLETED_WORK_LEDGER_MARKER,
97080
97179
  CONFIG_BEHAVIOR_DEFAULTS,
97081
97180
  CONTEXT_WINDOW_MODES,
97181
+ CONTEXT_WINDOW_MODE_PINNED_META_KEY,
97082
97182
  CORE_RECONSTRUCT_EVENTS,
97083
97183
  COUNCIL_JUDGE_PROMPT_PATH,
97084
97184
  COUNCIL_REFUSAL_OPTION_ID,
@@ -97270,6 +97370,7 @@ export {
97270
97370
  KNOWN_TOKEN_GROUPS,
97271
97371
  KNOWN_TOKEN_NAMES,
97272
97372
  KnowledgeGraph,
97373
+ LARGE_WINDOW_DEEP_MODE_THRESHOLD,
97273
97374
  LAYER_1_IDENTITY,
97274
97375
  LEADER_MODEL_SET_TOOL_NAME,
97275
97376
  LEARNED_HARD_LIMIT,
@@ -97546,6 +97647,7 @@ export {
97546
97647
  compileUserRegex,
97547
97648
  completeBrainLlm,
97548
97649
  completePartialObject,
97650
+ composeBoundedTaskDescription,
97549
97651
  composeDirectorPrompt,
97550
97652
  composeSubagentPrompt,
97551
97653
  computeMessageTokens,
@@ -97963,6 +98065,7 @@ export {
97963
98065
  parseReviewSeverity,
97964
98066
  parseSkillFrontmatter,
97965
98067
  parseSkillRef,
98068
+ parseTaskBoundary,
97966
98069
  peekQueuedMessages,
97967
98070
  pendingBtwCount,
97968
98071
  persistReviewReport,
@@ -98021,6 +98124,7 @@ export {
98021
98124
  renderPrometheus,
98022
98125
  renderPrompt,
98023
98126
  renderSkillAugmentation,
98127
+ renderTaskBoundaryBlock,
98024
98128
  repairConfigDefaults,
98025
98129
  repairToolUseAdjacency,
98026
98130
  repeatedReadPressure,
@@ -98185,6 +98289,7 @@ export {
98185
98289
  syncReportCompletion,
98186
98290
  syncReportReopen,
98187
98291
  takeHeapSample,
98292
+ taskBoundarySchemaProperties,
98188
98293
  terminalPolicyDecision,
98189
98294
  tightenHqRedactionPolicy,
98190
98295
  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,
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/core",
3
- "version": "0.308.1",
3
+ "version": "0.308.2",
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.2",
186
+ "@wrongstack/kanban": "0.308.2"
186
187
  },
187
188
  "devDependencies": {
188
189
  "@types/node": "^26.2.0",