@namzu/sdk 12.0.1 → 12.2.0

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/agents/AbstractAgent.d.ts +20 -0
  3. package/dist/agents/AbstractAgent.d.ts.map +1 -1
  4. package/dist/agents/AbstractAgent.js +32 -0
  5. package/dist/agents/AbstractAgent.js.map +1 -1
  6. package/dist/agents/lock.d.ts.map +1 -1
  7. package/dist/agents/lock.js +8 -1
  8. package/dist/agents/lock.js.map +1 -1
  9. package/dist/manager/agent/__tests__/a-fan-out-to-one-agent-id.test.d.ts +2 -0
  10. package/dist/manager/agent/__tests__/a-fan-out-to-one-agent-id.test.d.ts.map +1 -0
  11. package/dist/manager/agent/__tests__/a-fan-out-to-one-agent-id.test.js +101 -0
  12. package/dist/manager/agent/__tests__/a-fan-out-to-one-agent-id.test.js.map +1 -0
  13. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  14. package/dist/manager/agent/lifecycle.js +21 -1
  15. package/dist/manager/agent/lifecycle.js.map +1 -1
  16. package/dist/runtime/query/__tests__/the-other-approval-surface-names-the-agent.test.d.ts +2 -0
  17. package/dist/runtime/query/__tests__/the-other-approval-surface-names-the-agent.test.d.ts.map +1 -0
  18. package/dist/runtime/query/__tests__/the-other-approval-surface-names-the-agent.test.js +81 -0
  19. package/dist/runtime/query/__tests__/the-other-approval-surface-names-the-agent.test.js.map +1 -0
  20. package/dist/runtime/query/index.d.ts.map +1 -1
  21. package/dist/runtime/query/index.js +1 -0
  22. package/dist/runtime/query/index.js.map +1 -1
  23. package/dist/runtime/query/iteration/phases/plan.d.ts.map +1 -1
  24. package/dist/runtime/query/iteration/phases/plan.js +1 -0
  25. package/dist/runtime/query/iteration/phases/plan.js.map +1 -1
  26. package/dist/tools/coordinator/__tests__/an-approver-is-never-shown-an-impossible-step.test.d.ts +2 -0
  27. package/dist/tools/coordinator/__tests__/an-approver-is-never-shown-an-impossible-step.test.d.ts.map +1 -0
  28. package/dist/tools/coordinator/__tests__/an-approver-is-never-shown-an-impossible-step.test.js +115 -0
  29. package/dist/tools/coordinator/__tests__/an-approver-is-never-shown-an-impossible-step.test.js.map +1 -0
  30. package/dist/tools/coordinator/index.d.ts.map +1 -1
  31. package/dist/tools/coordinator/index.js +36 -0
  32. package/dist/tools/coordinator/index.js.map +1 -1
  33. package/dist/types/agent/core.d.ts +21 -0
  34. package/dist/types/agent/core.d.ts.map +1 -1
  35. package/dist/types/agent/factory.d.ts +18 -0
  36. package/dist/types/agent/factory.d.ts.map +1 -1
  37. package/dist/types/hitl/index.d.ts +16 -0
  38. package/dist/types/hitl/index.d.ts.map +1 -1
  39. package/dist/types/hitl/index.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/agents/AbstractAgent.ts +35 -0
  42. package/src/agents/lock.ts +10 -1
  43. package/src/manager/agent/__tests__/a-fan-out-to-one-agent-id.test.ts +118 -0
  44. package/src/manager/agent/lifecycle.ts +21 -1
  45. package/src/runtime/query/__tests__/the-other-approval-surface-names-the-agent.test.ts +95 -0
  46. package/src/runtime/query/index.ts +1 -0
  47. package/src/runtime/query/iteration/phases/plan.ts +1 -0
  48. package/src/tools/coordinator/__tests__/an-approver-is-never-shown-an-impossible-step.test.ts +153 -0
  49. package/src/tools/coordinator/index.ts +40 -0
  50. package/src/types/agent/core.ts +22 -0
  51. package/src/types/agent/factory.ts +20 -0
  52. package/src/types/hitl/index.ts +18 -0
@@ -122,6 +122,22 @@ export interface PlanApprovalData {
122
122
  id: string;
123
123
  description: string;
124
124
  toolName?: string;
125
+ /**
126
+ * Which agent the step is to be delegated to, when it is delegated.
127
+ *
128
+ * `PlanStep` gained this so an approver could see WHICH agent a step
129
+ * goes to rather than only THAT it delegates — approving "delegate
130
+ * this" is not approving "delegate this to the agent with shell
131
+ * access". It reached `PlanApprovalRequest`, which is the shape a host
132
+ * sees when it installs its own handler on `PlanManager`.
133
+ *
134
+ * It did not reach here, and this is the ordinary path: every host
135
+ * using `resumeHandler` is served by this type, and both mappers that
136
+ * build it copy field by field. So the fix landed on one of the two
137
+ * approval surfaces and the busier one kept showing
138
+ * `toolName: 'create_task'` and nothing else.
139
+ */
140
+ agentId?: string;
125
141
  dependsOn: string[];
126
142
  order: number;
127
143
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/hitl/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AACpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AAC1E,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD,YAAY,EAAE,YAAY,EAAE,CAAA;AAE5B,MAAM,MAAM,kBAAkB,GAC3B;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC3C;IACA,MAAM,EAAE,eAAe,CAAA;IACvB;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC3B,GACD;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,aAAa,EAAE,gBAAgB,EAAE,CAAA;CAAE,GAC7D;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5C;IACA,MAAM,EAAE,iBAAiB,CAAA;IACzB,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CAClB,GACD;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtC,MAAM,MAAM,mBAAmB,GAC5B;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;IAAC,YAAY,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;IAAC,YAAY,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,eAAe,EAAE,CAAA;CAAE,GAC/F;IACA,IAAI,EAAE,sBAAsB,CAAA;IAC5B,KAAK,EAAE,KAAK,CAAA;IACZ,YAAY,EAAE,YAAY,CAAA;IAC1B,OAAO,EAAE,iBAAiB,CAAA;CACzB,GACD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC;IAAC,YAAY,EAAE,YAAY,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAA;CAAE,CAAA;AAElG,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAA;AAEzF,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,aAAa,EAAE,OAAO,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;IACrC,aAAa,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,kBAAkB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,kBAAkB,EAAE,CAAA;IAC7B,WAAW,EAAE,OAAO,CAAA;IACpB,aAAa,EAAE,OAAO,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,KAAK,CAAC;QACZ,EAAE,EAAE,MAAM,CAAA;QACV,WAAW,EAAE,MAAM,CAAA;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,MAAM,EAAE,CAAA;QACnB,KAAK,EAAE,MAAM,CAAA;KACb,CAAC,CAAA;IACF,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,QAAQ,CAAA;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAA;IACrC,wCAAwC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;CACtC;AAED,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,YAAY,CAAA;IAChB,KAAK,EAAE,KAAK,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,QAAQ,CAAA;IAClB;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,UAAU,CAAA;IAEvB;;;OAGG;IACH,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,UAAU,EAAE;QACX,cAAc,EAAE,MAAM,CAAA;QACtB,SAAS,EAAE,MAAM,CAAA;KACjB,CAAA;IACD,SAAS,EAAE,MAAM,CAAA;IAEjB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEzC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAA;IAEnC;;;;;;;;;;;;;OAaG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAA;CACpC;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwB5F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/hitl/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/hitl/index.ts"],"names":[],"mappings":"AA0OA,MAAM,UAAU,kBAAkB,CAAC,OAA4B;IAC9D,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,eAAe;YACnB,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAA;QACnD,KAAK,aAAa;YACjB,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAA;QACpD,KAAK,sBAAsB;YAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAA;QAC/C,KAAK,eAAe;YACnB,2DAA2D;YAC3D,yDAAyD;YACzD,6DAA6D;YAC7D,uCAAuC;YACvC,OAAO,OAAO,CAAC,OAAO,CAAC;gBACtB,MAAM,EAAE,iBAAiB;gBACzB,iBAAiB,EAAE,EAAE;gBACrB,QAAQ,EAAE,mEAAmE;gBAC7E,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,UAAU;aACvC,CAAC,CAAA;QACH,OAAO,CAAC,CAAC,CAAC;YACT,MAAM,WAAW,GAAU,OAAO,CAAA;YAClC,MAAM,IAAI,KAAK,CAAC,gCAAiC,WAAmC,CAAC,IAAI,EAAE,CAAC,CAAA;QAC7F,CAAC;IACF,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/hitl/index.ts"],"names":[],"mappings":"AA4PA,MAAM,UAAU,kBAAkB,CAAC,OAA4B;IAC9D,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,eAAe;YACnB,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAA;QACnD,KAAK,aAAa;YACjB,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAA;QACpD,KAAK,sBAAsB;YAC1B,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAA;QAC/C,KAAK,eAAe;YACnB,2DAA2D;YAC3D,yDAAyD;YACzD,6DAA6D;YAC7D,uCAAuC;YACvC,OAAO,OAAO,CAAC,OAAO,CAAC;gBACtB,MAAM,EAAE,iBAAiB;gBACzB,iBAAiB,EAAE,EAAE;gBACrB,QAAQ,EAAE,mEAAmE;gBAC7E,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,UAAU;aACvC,CAAC,CAAA;QACH,OAAO,CAAC,CAAC,CAAC;YACT,MAAM,WAAW,GAAU,OAAO,CAAA;YAClC,MAAM,IAAI,KAAK,CAAC,gCAAiC,WAAmC,CAAC,IAAI,EAAE,CAAC,CAAA;QAC7F,CAAC;IACF,CAAC;AACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namzu/sdk",
3
- "version": "12.0.1",
3
+ "version": "12.2.0",
4
4
  "description": "Open-source AI agent SDK with a built-in runtime. Nothing between you and your agents.",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "type": "module",
@@ -55,6 +55,41 @@ export abstract class AbstractAgent<
55
55
 
56
56
  abstract run(input: AgentInput, config: TConfig, listener?: RunEventListener): Promise<TResult>
57
57
 
58
+ /**
59
+ * A fresh shell of this agent, for a run that must not share one.
60
+ *
61
+ * See {@link Agent.forRun}. An agent is a shell around metadata — every
62
+ * per-run decision arrives in `config` and `input` — so a second instance
63
+ * costs one object and gives the run its own abort controller and run id,
64
+ * which is precisely what the invocation lock is protecting.
65
+ *
66
+ * Rebuilt from `this.constructor` and `this.metadata`, which covers every
67
+ * agent in this package: they all take metadata and nothing else. A
68
+ * subclass with a different constructor signature will throw here, and the
69
+ * answer to that is `this` — the caller then shares the shell and gets the
70
+ * existing refusal on a concurrent run, which is the behaviour before this
71
+ * existed. Losing parallelism is a worse outcome than not having it; losing
72
+ * the run is not on the table.
73
+ *
74
+ * A host whose agent needs real construction arguments supplies
75
+ * `AgentDefinition.createAgent` instead, which wins over this.
76
+ */
77
+ forRun(): this {
78
+ try {
79
+ const Ctor = this.constructor as unknown as new (metadata: AgentMetadata) => this
80
+ return new Ctor(this.metadata)
81
+ } catch (err) {
82
+ this.log.warn(
83
+ 'Could not build a per-run shell; concurrent runs of this agent will still be refused',
84
+ {
85
+ agentId: this.metadata.id,
86
+ error: err instanceof Error ? err.message : String(err),
87
+ },
88
+ )
89
+ return this
90
+ }
91
+ }
92
+
58
93
  /**
59
94
  * Acquire the invocation lock to prevent concurrent execution.
60
95
  * Returns a Disposable that must be disposed to release the lock.
@@ -11,7 +11,16 @@ export class ConcurrentInvocationError extends Error {
11
11
  readonly agentId: string
12
12
 
13
13
  constructor(agentId: string) {
14
- super(`Agent ${agentId} is already processing. Concurrent invocations are not allowed.`)
14
+ // Names the remedy, because the refusal alone sent readers looking for a
15
+ // concurrency bug in their own code. An agent instance holds per-run
16
+ // state — an abort controller and the run id — so two overlapping runs
17
+ // on one shell would cancel each other; the answer is a second shell,
18
+ // not a second attempt. Delegated spawns get one automatically via
19
+ // `Agent.forRun`, so reaching this from a fan-out means the agent
20
+ // could not be rebuilt and wants `AgentDefinition.createAgent`.
21
+ super(
22
+ `Agent ${agentId} is already processing. Concurrent invocations of one instance are not allowed, because its abort controller and run id are instance state and two runs would cancel each other. Run a second instance instead — or, for a delegated spawn, give its AgentDefinition a \`createAgent\` factory so each child gets its own.`,
23
+ )
15
24
  this.name = 'ConcurrentInvocationError'
16
25
  this.agentId = agentId
17
26
  }
@@ -0,0 +1,118 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { ReactiveAgent } from '../../../agents/ReactiveAgent.js'
4
+ import { ToolRegistry } from '../../../registry/index.js'
5
+ import type { AgentDefinition } from '../../../types/agent/factory.js'
6
+
7
+ /**
8
+ * A fan-out naming the same agent four times ran one child and lost three.
9
+ *
10
+ * `AgentRegistry` hands out ONE `typedAgent` per registered id, and an instance
11
+ * refuses a second concurrent `run` — correctly, because its abort controller
12
+ * and run id are instance state and two overlapping runs would cancel each
13
+ * other. So four `create_task` calls at one specialist produced one result and
14
+ * three `ConcurrentInvocationError`s.
15
+ *
16
+ * The prescribed remedy already existed in the docs — "a host that wants
17
+ * parallelism constructs a second instance" — and was unreachable from
18
+ * delegation, where the definition owns the instance and the caller has only an
19
+ * id. Observed live on published 12.0.1: four launches, three lost.
20
+ *
21
+ * These cover the shell itself. That the manager USES it per spawn is covered
22
+ * where the manager is driven; what has to hold here is that asking for a
23
+ * per-run shell gives you a genuinely separate one.
24
+ */
25
+
26
+ const metadata = {
27
+ id: 'worker',
28
+ name: 'worker',
29
+ version: '1.0.0',
30
+ category: 'general',
31
+ description: 'a worker',
32
+ }
33
+
34
+ describe('an agent can hand out a shell a single run has to itself', () => {
35
+ it('returns a different instance', () => {
36
+ const agent = new ReactiveAgent(metadata)
37
+
38
+ expect(agent.forRun()).not.toBe(agent)
39
+ })
40
+
41
+ it('keeps the identity, because it is the same agent', () => {
42
+ const agent = new ReactiveAgent(metadata)
43
+ const shell = agent.forRun()
44
+
45
+ expect(shell.metadata.id).toBe('worker')
46
+ expect(shell.type).toBe(agent.type)
47
+ expect(shell.getCapabilities()).toEqual(agent.getCapabilities())
48
+ })
49
+
50
+ it('gives each shell its own invocation lock, which is the whole point', async () => {
51
+ // Locking one must not lock the other. Asserted through the public
52
+ // surface: a run that never settles holds the lock, and a second run on
53
+ // a SEPARATE shell must still be admitted.
54
+ // ONE registered agent, two shells — the registry's shape, and the
55
+ // shape the fan-out actually hits.
56
+ const registered = new ReactiveAgent(metadata)
57
+ const first = registered.forRun()
58
+ const second = registered.forRun()
59
+
60
+ // A provider that starts and never finishes, so each run holds its
61
+ // shell's lock for the duration of the assertion.
62
+ const provider = {
63
+ // biome-ignore lint/correctness/useYield: it never produces anything, on purpose
64
+ async *chatStream() {
65
+ await new Promise<never>(() => {})
66
+ },
67
+ }
68
+ const config = {
69
+ model: 'mock',
70
+ tokenBudget: 10_000,
71
+ timeoutMs: 10_000,
72
+ maxIterations: 2,
73
+ provider,
74
+ tools: new ToolRegistry(),
75
+ systemPrompt: 'hold',
76
+ sessionId: 'ses_fan' as never,
77
+ threadId: 'thd_fan' as never,
78
+ projectId: 'prj_fan' as never,
79
+ tenantId: 'tnt_fan' as never,
80
+ }
81
+
82
+ // Start one run on each shell; neither resolves, and neither should
83
+ // refuse. A shared shell would reject the second synchronously.
84
+ const a = first.run({ messages: [], workingDirectory: '/tmp' } as never, config as never)
85
+ const b = second.run({ messages: [], workingDirectory: '/tmp' } as never, config as never)
86
+
87
+ await expect(
88
+ Promise.race([
89
+ Promise.all([a, b]).then(() => 'settled'),
90
+ new Promise((r) => setTimeout(() => r('still running'), 50)),
91
+ ]),
92
+ ).resolves.toBe('still running')
93
+
94
+ void a.catch(() => {})
95
+ void b.catch(() => {})
96
+ })
97
+
98
+ it('a definition may override the shell with its own factory', () => {
99
+ // The escape hatch for an agent that needs real construction
100
+ // arguments, which `forRun`'s metadata-only rebuild cannot supply.
101
+ let built = 0
102
+ const definition: AgentDefinition = {
103
+ info: { ...metadata, tools: [], defaults: {} } as never,
104
+ typedAgent: new ReactiveAgent(metadata) as never,
105
+ createAgent: () => {
106
+ built += 1
107
+ return new ReactiveAgent(metadata) as never
108
+ },
109
+ }
110
+
111
+ const one = definition.createAgent?.()
112
+ const two = definition.createAgent?.()
113
+
114
+ expect(built).toBe(2)
115
+ expect(one).not.toBe(two)
116
+ expect(one).not.toBe(definition.typedAgent)
117
+ })
118
+ })
@@ -123,7 +123,27 @@ export class AgentManager {
123
123
  )
124
124
  }
125
125
 
126
- const agent = this.registry.resolve(options.agentId)
126
+ // A shell this task has to itself, not the registry's shared instance.
127
+ //
128
+ // `resolve` returns one `typedAgent` per registered id, and an instance
129
+ // refuses a second concurrent `run` because it holds per-run state. So
130
+ // a fan-out naming the same `agent_id` four times drove four runs at one
131
+ // shell: one worked and three died with `ConcurrentInvocationError` —
132
+ // while `create_task`'s own description tells the model that this
133
+ // fan-out is the thing to do. Observed live on 12.0.1, four launches,
134
+ // three lost.
135
+ //
136
+ // The remedy was already written down — "a host that wants parallelism
137
+ // constructs a second instance" — and was unreachable here, because the
138
+ // definition owns the instance and this path only has an id.
139
+ //
140
+ // Nothing else about the child is shared: its abort signal is the task's
141
+ // own (`input.signal` below), its config is rebuilt per spawn by
142
+ // `configBuilder`, and the manager cancels through the task rather than
143
+ // the agent. The shell was the only shared thing left.
144
+ const definitionForSpawn = this.registry.getOrThrow(options.agentId)
145
+ const sharedAgent = definitionForSpawn.typedAgent
146
+ const agent = definitionForSpawn.createAgent?.() ?? sharedAgent.forRun?.() ?? sharedAgent
127
147
 
128
148
  const childAbortController = createChildAbortController(context.parentAbortController)
129
149
 
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import type { PlanManager } from '../../../manager/plan/lifecycle.js'
4
+ import { MockLLMProvider, registerMock } from '../../../provider/index.js'
5
+ import { ToolRegistry } from '../../../registry/index.js'
6
+ import type { HITLResumeDecision, PlanApprovalData } from '../../../types/hitl/index.js'
7
+ import {
8
+ generateProjectId,
9
+ generateSessionId,
10
+ generateTenantId,
11
+ generateThreadId,
12
+ } from '../../../utils/id.js'
13
+ import { drainQuery } from '../index.js'
14
+
15
+ /**
16
+ * There are TWO approval surfaces, and the fix landed on one of them.
17
+ *
18
+ * `PlanStep.agentId` was added so an approver could see WHICH agent a step goes
19
+ * to rather than only THAT it delegates. It reached `PlanApprovalRequest` — the
20
+ * shape a host sees when it installs its own handler on `PlanManager` — and the
21
+ * test written at the time asserted on exactly that, by constructing a
22
+ * `PlanManager` directly.
23
+ *
24
+ * It did not reach `PlanApprovalData`, which is what every `resumeHandler` host
25
+ * receives, because that type declared its own step shape and both mappers copy
26
+ * field by field. So the busier surface kept showing `toolName: 'create_task'`
27
+ * and nothing else — the precise behaviour the change was supposed to end.
28
+ *
29
+ * A live run did not catch it either: the run observes `plan_ready`, which
30
+ * carries whole `PlanStep`s and therefore always had `agentId`. Watching the
31
+ * event stream confirmed the field existed somewhere, which is not the same
32
+ * question as whether the approver gets it.
33
+ */
34
+
35
+ registerMock()
36
+
37
+ async function agentIdSeenByResumeHandler(): Promise<PlanApprovalData['steps']> {
38
+ let seen: PlanApprovalData['steps'] | undefined
39
+
40
+ await drainQuery({
41
+ provider: new MockLLMProvider({ responses: [{ content: 'done' }] } as never),
42
+ tools: new ToolRegistry(),
43
+ agentId: 'a',
44
+ agentName: 'A',
45
+ messages: [{ role: 'user', content: 'go' }],
46
+ workingDirectory: process.cwd(),
47
+ runConfig: { model: 'mock', tokenBudget: 100_000, timeoutMs: 30_000, maxIterations: 4 },
48
+ projectId: generateProjectId(),
49
+ sessionId: generateSessionId(),
50
+ threadId: generateThreadId(),
51
+ tenantId: generateTenantId(),
52
+ // The ordinary host path: a resumeHandler, not a hand-installed
53
+ // PlanManager handler.
54
+ resumeHandler: async (request: { type: string; plan?: PlanApprovalData }) => {
55
+ if (request.type === 'plan_approval' && request.plan) seen = request.plan.steps
56
+ return { action: 'approve_plan' } as HITLResumeDecision
57
+ },
58
+ onContextCreated: ({ planManager }: { planManager: PlanManager }) => {
59
+ planManager.startGenerating('the work')
60
+ planManager.addStep({
61
+ id: 'step_1',
62
+ description: 'delegated work',
63
+ agentId: 'shell-runner',
64
+ toolName: 'create_task',
65
+ dependsOn: [],
66
+ order: 1,
67
+ })
68
+ planManager.addStep({ id: 'step_2', description: 'my own work', dependsOn: [], order: 2 })
69
+ planManager.markReady()
70
+ void planManager.requestApproval()
71
+ },
72
+ } as never)
73
+
74
+ if (!seen) throw new Error('the resume handler never received a plan approval')
75
+ return seen
76
+ }
77
+
78
+ describe('the resumeHandler approval surface names the agent too', () => {
79
+ it('carries agentId through to the host', async () => {
80
+ const steps = await agentIdSeenByResumeHandler()
81
+
82
+ expect(steps.map((s) => s.agentId)).toEqual(['shell-runner', undefined])
83
+ })
84
+
85
+ it('still distinguishes the two steps by more than toolName', async () => {
86
+ // The defect in the shape that matters: without agentId both delegated
87
+ // steps reach the approver identical, and an orchestrator-owned step is
88
+ // told apart only by an absent toolName.
89
+ const steps = await agentIdSeenByResumeHandler()
90
+ const delegated = steps.find((s) => s.id === 'step_1')
91
+
92
+ expect(delegated?.toolName).toBe('create_task')
93
+ expect(delegated?.agentId).toBe('shell-runner')
94
+ })
95
+ })
@@ -527,6 +527,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
527
527
  id: s.id,
528
528
  description: s.description,
529
529
  toolName: s.toolName,
530
+ agentId: s.agentId,
530
531
  dependsOn: s.dependsOn,
531
532
  order: s.order ?? i + 1,
532
533
  })),
@@ -29,6 +29,7 @@ export async function* runPlanGate(ctx: IterationContext): AsyncGenerator<RunEve
29
29
  id: s.id,
30
30
  description: s.description,
31
31
  toolName: s.toolName,
32
+ agentId: s.agentId,
32
33
  dependsOn: s.dependsOn,
33
34
  order: s.order,
34
35
  })),
@@ -0,0 +1,153 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { PlanManager } from '../../../manager/plan/lifecycle.js'
4
+ import type { TaskGateway } from '../../../types/agent/gateway.js'
5
+ import type { RunId } from '../../../types/ids/index.js'
6
+ import type { PlanApprovalRequest } from '../../../types/plan/index.js'
7
+ import type { ToolContext } from '../../../types/tool/index.js'
8
+ import { buildCoordinatorTools } from '../index.js'
9
+
10
+ /**
11
+ * A plan could name an agent the launch would then refuse.
12
+ *
13
+ * `create_task` constrains `agent_id` with a closed enum; `approve_plan` typed
14
+ * the same field as a bare string. So a model could propose, and a human could
15
+ * approve, "delegate to X" for an X that `create_task` rejects at schema-parse
16
+ * time — the run then burns a turn on a step a human had already blessed.
17
+ *
18
+ * The check lives in `execute`, not in the schema, and that is deliberate.
19
+ * `approve_plan` is mounted even with an EMPTY roster, because planning with no
20
+ * delegates and a human channel is a supported configuration — and `z.enum([])`
21
+ * renders as `{"not":{}}`, the shape `delegateSchema` already refuses because a
22
+ * strict tool-schema validator rejects the whole request over it rather than
23
+ * the one tool. `create_task` escapes that by being withheld entirely; this
24
+ * tool cannot be. Enforcing in `execute` as well is the precedent the canonical
25
+ * `Agent` tool set for complete mediation.
26
+ *
27
+ * It runs BEFORE `startGenerating`, so the refusal costs no half-built plan and
28
+ * the human is never shown the bad step at all.
29
+ */
30
+
31
+ const RUN = 'run_roster' as RunId
32
+
33
+ function unusedGateway(): TaskGateway {
34
+ return {
35
+ async createTask() {
36
+ throw new Error('this test never launches')
37
+ },
38
+ async waitForTask() {
39
+ throw new Error('this test never waits')
40
+ },
41
+ async continueTask() {},
42
+ cancelTask() {},
43
+ getTask() {
44
+ return undefined
45
+ },
46
+ listTasks() {
47
+ return []
48
+ },
49
+ onTaskCompleted() {
50
+ return () => {}
51
+ },
52
+ }
53
+ }
54
+
55
+ function ctx(): ToolContext {
56
+ return {
57
+ runId: RUN,
58
+ workingDirectory: '/tmp/test',
59
+ abortSignal: new AbortController().signal,
60
+ env: {},
61
+ log: () => {},
62
+ }
63
+ }
64
+
65
+ /** Run `approve_plan`, reporting both its result and what the approver saw. */
66
+ async function approve(
67
+ roster: string[],
68
+ steps: Array<{ description: string; agent_id?: string }>,
69
+ ): Promise<{ result: Awaited<ReturnType<ReturnType<typeof build>>>; seen?: PlanApprovalRequest }> {
70
+ let seen: PlanApprovalRequest | undefined
71
+ const pm = new PlanManager(RUN, async (request) => {
72
+ seen = request
73
+ return { approved: true }
74
+ })
75
+ const run = build(roster, pm)
76
+ const result = await run(steps)
77
+ return { result, ...(seen ? { seen } : {}) }
78
+ }
79
+
80
+ function build(roster: string[], pm: PlanManager) {
81
+ const tools = buildCoordinatorTools({
82
+ gateway: unusedGateway(),
83
+ workingDirectory: '/tmp/test',
84
+ allowedAgentIds: roster,
85
+ getPlanManager: () => pm,
86
+ })
87
+ const approvePlan = tools.find((t) => t.name === 'approve_plan')
88
+ if (!approvePlan) throw new Error('approve_plan missing from coordinator builder')
89
+ return (steps: Array<{ description: string; agent_id?: string }>) =>
90
+ approvePlan.execute({ title: 'do it', summary: 'a plan', steps }, ctx())
91
+ }
92
+
93
+ describe('a plan may only name an agent the run can actually launch', () => {
94
+ it('refuses a step naming an agent outside the roster', async () => {
95
+ const { result } = await approve(
96
+ ['researcher', 'writer'],
97
+ [{ description: 'audit the deps', agent_id: 'security-auditor' }],
98
+ )
99
+
100
+ expect(result.success).toBe(false)
101
+ expect(result.error).toContain('security-auditor')
102
+ })
103
+
104
+ it('names the roster, so the model can correct itself in one turn', async () => {
105
+ const { result } = await approve(
106
+ ['researcher', 'writer'],
107
+ [{ description: 'audit', agent_id: 'nobody' }],
108
+ )
109
+
110
+ expect(result.error).toContain('researcher')
111
+ expect(result.error).toContain('writer')
112
+ })
113
+
114
+ it('never shows the approver the step it refused', async () => {
115
+ // The whole point of checking before `startGenerating`: a human must
116
+ // not be asked to approve work that cannot run.
117
+ const { seen } = await approve(
118
+ ['researcher'],
119
+ [{ description: 'audit', agent_id: 'security-auditor' }],
120
+ )
121
+
122
+ expect(seen).toBeUndefined()
123
+ })
124
+
125
+ it('leaves no half-built plan behind', async () => {
126
+ const pm = new PlanManager(RUN, async () => ({ approved: true }))
127
+ const run = build(['researcher'], pm)
128
+
129
+ await run([{ description: 'audit', agent_id: 'ghost' }])
130
+
131
+ expect(pm.active).toBeNull()
132
+ })
133
+
134
+ it('says something different when the run has no delegates at all', async () => {
135
+ // `approve_plan` is mounted on an empty roster on purpose — planning
136
+ // without delegation is supported — so the message has to explain that
137
+ // rather than list an empty set.
138
+ const { result } = await approve([], [{ description: 'audit', agent_id: 'anyone' }])
139
+
140
+ expect(result.success).toBe(false)
141
+ expect(result.error).toContain('no delegates')
142
+ })
143
+
144
+ it('still admits a plan that delegates only within the roster', async () => {
145
+ const { result, seen } = await approve(
146
+ ['researcher', 'writer'],
147
+ [{ description: 'gather', agent_id: 'researcher' }, { description: 'summarize' }],
148
+ )
149
+
150
+ expect(result.success).toBe(true)
151
+ expect(seen?.steps.map((s) => s.agentId)).toEqual(['researcher', undefined])
152
+ })
153
+ })
@@ -1015,6 +1015,46 @@ export function buildCoordinatorTools(opts: CoordinatorToolsOptions): ToolDefini
1015
1015
  return { success: false, output: '', error: dependencies.error }
1016
1016
  }
1017
1017
 
1018
+ // The roster, checked here rather than in the schema, and BEFORE
1019
+ // the plan is built — so a human is never shown a step naming an
1020
+ // agent that cannot run it.
1021
+ //
1022
+ // `create_task` constrains the same field with a closed enum, so
1023
+ // a plan could name an agent the launch would then refuse. The
1024
+ // mismatch used to be invisible because the name was dropped on
1025
+ // the way to the approver; now that a step carries it, an
1026
+ // approver could read "delegate to X" for an X that does not
1027
+ // exist.
1028
+ //
1029
+ // NOT closed in the schema, deliberately. `approve_plan` is
1030
+ // mounted even with an empty roster — planning with no delegates
1031
+ // and a human channel is a supported configuration — and
1032
+ // `z.enum([])` renders as `{"not":{}}`, the shape `delegateSchema`
1033
+ // already refuses because a strict tool-schema validator rejects
1034
+ // the whole request over it rather than the one tool.
1035
+ // `create_task` escapes that by being withheld entirely; this
1036
+ // tool cannot be.
1037
+ //
1038
+ // Enforcing in `execute` as well as the schema is the precedent
1039
+ // the canonical `Agent` tool set for complete mediation.
1040
+ const unknownAgents = [
1041
+ ...new Set(
1042
+ steps
1043
+ .map((s) => s.agent_id)
1044
+ .filter((id): id is string => Boolean(id) && !agentIds.includes(id as string)),
1045
+ ),
1046
+ ]
1047
+ if (unknownAgents.length > 0) {
1048
+ return {
1049
+ success: false,
1050
+ output: '',
1051
+ error:
1052
+ agentIds.length === 0
1053
+ ? `This plan delegates to ${unknownAgents.join(', ')}, but this run has no delegates. Plan the work as your own steps and omit agent_id.`
1054
+ : `No such agent: ${unknownAgents.join(', ')}. Delegate only to ${agentIds.join(', ')}, or omit agent_id for a step you carry out yourself.`,
1055
+ }
1056
+ }
1057
+
1018
1058
  pm.startGenerating(title)
1019
1059
  for (let i = 0; i < steps.length; i++) {
1020
1060
  const step = steps[i]
@@ -17,6 +17,28 @@ export interface Agent<
17
17
 
18
18
  run(input: AgentInput, config: TConfig, listener?: RunEventListener): Promise<TResult>
19
19
 
20
+ /**
21
+ * A shell of this agent that one run may have to itself.
22
+ *
23
+ * An agent instance holds per-run state — an abort controller, the id of
24
+ * the run in flight — and refuses a second concurrent `run` because of it.
25
+ * That refusal is correct for a host calling `run` twice on purpose: two
26
+ * overlapping runs would share one abort controller, so cancelling either
27
+ * kills both.
28
+ *
29
+ * It is wrong for delegation, which is why this exists. `AgentRegistry`
30
+ * hands out ONE instance per registered id, so a fan-out naming the same
31
+ * `agent_id` four times drove four runs at one shell: one worked and three
32
+ * died with `ConcurrentInvocationError`. The prescribed remedy — "construct
33
+ * a second instance" — was unreachable from there, because the definition
34
+ * owns the instance and the caller only has an id.
35
+ *
36
+ * OPTIONAL, and absence is safe: a manager that cannot get a fresh shell
37
+ * falls back to the shared one and the refusal stands, which is loud rather
38
+ * than wrong. `AbstractAgent` implements it for every agent built on it.
39
+ */
40
+ forRun?(): Agent<TConfig, TResult>
41
+
20
42
  cancel(): Promise<void>
21
43
  getCapabilities(): AgentCapabilities
22
44
  }
@@ -8,6 +8,26 @@ export type { AgentContextLevel } from './base.js'
8
8
  export interface AgentDefinition {
9
9
  info: AgentInfo
10
10
  typedAgent: Agent<BaseAgentConfig, BaseAgentResult>
11
+
12
+ /**
13
+ * Build a fresh agent for a single spawn.
14
+ *
15
+ * `typedAgent` is ONE instance, and an instance refuses a second
16
+ * concurrent run because it holds per-run state. So a delegation fan-out
17
+ * naming the same `agent_id` four times ran one child and lost three to
18
+ * `ConcurrentInvocationError` — while `create_task`'s own description tells
19
+ * a model that exactly this fan-out is the thing to do.
20
+ *
21
+ * The manager prefers this over `typedAgent` for every spawn. Supply it
22
+ * when your agent needs real construction arguments; agents built on
23
+ * `AbstractAgent` already get a working default from `Agent.forRun`, so
24
+ * most hosts need nothing here.
25
+ *
26
+ * `configBuilder` is not a substitute: it produces a fresh CONFIG per
27
+ * spawn, and the config was never the part being shared.
28
+ */
29
+ createAgent?: () => Agent<BaseAgentConfig, BaseAgentResult>
30
+
11
31
  configBuilder?: (options: AgentFactoryOptions) => BaseAgentConfig | Promise<BaseAgentConfig>
12
32
 
13
33
  contextLevel?: AgentContextLevel
@@ -109,6 +109,24 @@ export interface PlanApprovalData {
109
109
  id: string
110
110
  description: string
111
111
  toolName?: string
112
+
113
+ /**
114
+ * Which agent the step is to be delegated to, when it is delegated.
115
+ *
116
+ * `PlanStep` gained this so an approver could see WHICH agent a step
117
+ * goes to rather than only THAT it delegates — approving "delegate
118
+ * this" is not approving "delegate this to the agent with shell
119
+ * access". It reached `PlanApprovalRequest`, which is the shape a host
120
+ * sees when it installs its own handler on `PlanManager`.
121
+ *
122
+ * It did not reach here, and this is the ordinary path: every host
123
+ * using `resumeHandler` is served by this type, and both mappers that
124
+ * build it copy field by field. So the fix landed on one of the two
125
+ * approval surfaces and the busier one kept showing
126
+ * `toolName: 'create_task'` and nothing else.
127
+ */
128
+ agentId?: string
129
+
112
130
  dependsOn: string[]
113
131
  order: number
114
132
  }>