@namzu/sdk 14.0.4 → 14.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +77 -0
- package/README.md +20 -10
- package/dist/gateway/__tests__/a-gateway-that-forgets-the-oldest.test.d.ts +2 -0
- package/dist/gateway/__tests__/a-gateway-that-forgets-the-oldest.test.d.ts.map +1 -0
- package/dist/gateway/__tests__/a-gateway-that-forgets-the-oldest.test.js +96 -0
- package/dist/gateway/__tests__/a-gateway-that-forgets-the-oldest.test.js.map +1 -0
- package/dist/gateway/local.d.ts +16 -0
- package/dist/gateway/local.d.ts.map +1 -1
- package/dist/gateway/local.js +35 -0
- package/dist/gateway/local.js.map +1 -1
- package/package.json +1 -1
- package/src/gateway/__tests__/a-gateway-that-forgets-the-oldest.test.ts +130 -0
- package/src/gateway/local.ts +36 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 14.0.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9ee23f1: Stop the package README naming two providers when seven ship
|
|
8
|
+
|
|
9
|
+
"Provider abstraction. OpenRouter and AWS Bedrock today" has been wrong for a
|
|
10
|
+
long time. Seven driver packages ship — and a reader deciding whether this
|
|
11
|
+
kernel can talk to the service they already pay for was being told, on the
|
|
12
|
+
registry page, that it probably cannot.
|
|
13
|
+
|
|
14
|
+
That is the expensive direction for this particular sentence to be wrong in:
|
|
15
|
+
it does not cause a bug, it causes someone to close the tab.
|
|
16
|
+
|
|
17
|
+
Also removed the third-party product names from "What Namzu Is Not". That
|
|
18
|
+
section explained namzu's scope by listing other people's products, which is
|
|
19
|
+
the one thing this repository's own naming rule refuses — a design explained
|
|
20
|
+
by reference to somebody else's has borrowed its shape, and the borrowing
|
|
21
|
+
outlives the sentence. The scope boundaries are unchanged and now stated as
|
|
22
|
+
categories: no front-end framework bindings, no web-framework or edge-runtime
|
|
23
|
+
plumbing, no embedded vector engine.
|
|
24
|
+
|
|
25
|
+
Prose only. No runtime change.
|
|
26
|
+
|
|
27
|
+
## 14.0.5
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- a2a7aed: `LocalTaskGateway` stops remembering every task it ever launched.
|
|
32
|
+
|
|
33
|
+
`trackedTaskIds` and `settledHandles` had `add` and `set` and no removal
|
|
34
|
+
anywhere in the file. The comment above them said "bounded by the number the
|
|
35
|
+
gateway itself launched", which is true and is not a bound: a gateway built per
|
|
36
|
+
run is bounded by that run, but `SupervisorAgentConfig.gateway` lets a host
|
|
37
|
+
supply its own, and a long-lived host reusing one accumulates an id and a
|
|
38
|
+
settled handle for every task it ever launched, for the life of the process.
|
|
39
|
+
|
|
40
|
+
Both are now capped at 1000 and evicted oldest-first. The cap is far above any
|
|
41
|
+
realistic single run — a fan-out is eight, a long supervisory run is dozens —
|
|
42
|
+
so the listing a supervisor reads at the end of its run is unchanged.
|
|
43
|
+
|
|
44
|
+
The two are evicted **together**. Dropping a tracked id while keeping its
|
|
45
|
+
handle would leave memory nothing can reach, since `listTasks` walks the ids;
|
|
46
|
+
dropping a handle while keeping its id would make a task that ran read as one
|
|
47
|
+
that never launched, which is the exact defect the settled-handle map exists to
|
|
48
|
+
fix. Removing either half of the paired delete fails a test.
|
|
49
|
+
|
|
50
|
+
- 688d6af: Stop the package README promising filesystem isolation the sandbox refuses to claim
|
|
51
|
+
|
|
52
|
+
The README that ships inside this tarball — and therefore the page on the
|
|
53
|
+
registry — said tools run "inside an OS-enforced jail with deny-default file
|
|
54
|
+
I/O", and attributed that to both sandbox tiers.
|
|
55
|
+
|
|
56
|
+
`src/sandbox/isolation.ts` says otherwise, and says it deliberately. The
|
|
57
|
+
namespace tier reports `filesystem: false`, because it unshares the mount
|
|
58
|
+
namespace and never remounts anything, so the child still sees the whole host
|
|
59
|
+
filesystem. The comment beside that table is explicit that claiming otherwise
|
|
60
|
+
would reintroduce the exact defect the table exists to end.
|
|
61
|
+
|
|
62
|
+
So the code was careful and the README was not, in the one direction that
|
|
63
|
+
costs something. An overclaimed security control is worse than an absent one:
|
|
64
|
+
a reader who believes a boundary is there stops looking for one, and this
|
|
65
|
+
sentence was reachable from the registry page by anyone deciding whether it
|
|
66
|
+
was safe to run untrusted input through a tool call.
|
|
67
|
+
|
|
68
|
+
Nothing about the runtime changes. What changes is that the README now
|
|
69
|
+
reproduces the isolation table per tier, names the tier that does **not**
|
|
70
|
+
enforce filesystem isolation and why, and points at `assertIsolation`,
|
|
71
|
+
`isolationOf`, `missingIsolation` and `describeIsolation` — which have always
|
|
72
|
+
been exported, and which refuse a run whose required control the host cannot
|
|
73
|
+
supply rather than quietly running it at a weaker tier.
|
|
74
|
+
|
|
75
|
+
No action required on upgrade. If you read that sentence and concluded your
|
|
76
|
+
tool calls were filesystem-confined on a namespace host, they were not — call
|
|
77
|
+
`assertIsolation` with the controls you actually require, and it will refuse
|
|
78
|
+
rather than pretend.
|
|
79
|
+
|
|
3
80
|
## 14.0.4
|
|
4
81
|
|
|
5
82
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
Most "agent frameworks" today are really application frameworks. They ship chat UIs, picking UI layouts, batteries-included hosted dashboards, vendor-specific fast paths, and integration drivers for a handful of databases. You get something you can demo in an hour, and three months later you own a stack where the same framework dictates your frontend, your database, your observability, and your model vendor.
|
|
16
16
|
|
|
17
|
-
We think agent software should be layered
|
|
17
|
+
We think agent software should be layered the way an operating system is. At the bottom there needs to be a **kernel**: something to isolate processes, schedule tool calls, manage memory pressure, propagate signals across a call tree, persist checkpoints so a run can resume after a crash, mediate inter-process communication, and produce an auditable event stream. Above the kernel there is user space — shells, editors, IDEs, voice gateways, web front-ends. The kernel does not care which shell you pick; the shell cannot break the isolation the kernel provides.
|
|
18
18
|
|
|
19
|
-
**Namzu is the kernel.** It runs agents the way
|
|
19
|
+
**Namzu is the kernel.** It runs agents the way an operating system runs processes. It does not render UI, it does not pick your database, it does not favor one LLM vendor. It gives you a surface — typed, versioned, documented — that any UI, any storage backend, and any model can plug into. The surface is small and stable; the guts underneath are deep.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@ We think agent software should be layered like Unix. At the bottom there needs t
|
|
|
24
24
|
|
|
25
25
|
Namzu is a single-process TypeScript kernel with the following responsibilities:
|
|
26
26
|
|
|
27
|
-
- **Process execution and isolation.** Tools run
|
|
27
|
+
- **Process execution and isolation.** Tools run outside the host process, under OS-level containment whose enforced controls vary by tier — and the kernel states per tier which of filesystem, network and process isolation it actually enforces, refusing to start a run whose required control the host cannot supply rather than silently downgrading it. No container runtime, no daemon, no sidecar. See [The Boundary](#1-the-boundary-sandbox-sandbox) for the table.
|
|
28
28
|
- **Agent lifecycle.** Parent/child agent spawn with depth tracking, budget splitting, and causal trace linkage. A supervisor can fork a subtree of agents and get their results back, with each child isolated from its siblings.
|
|
29
29
|
- **Scheduling.** Per-run token, cost, wall-clock, and iteration budgets. Limit checker, task router (cheap model for compaction, expensive for coding), tool tiering (LLM learns to prefer cheaper tools first).
|
|
30
30
|
- **Signals.** `AbortController` tree spanning parent and children. `cancel(taskId)` and `cancelAll(parentRunId)` propagate. Runs can be paused and resumed, aborted cleanly, and emit lifecycle events for every transition.
|
|
@@ -40,7 +40,7 @@ Namzu is a single-process TypeScript kernel with the following responsibilities:
|
|
|
40
40
|
- **Human-in-the-loop.** Structured plan review, per-tool approval with destructiveness flags, typed decision contracts, checkpoint/resume across sessions.
|
|
41
41
|
- **Plugin system.** Lifecycle-hooked plugin loader with MCP contributions, tool contributions, and manifest-driven resolution.
|
|
42
42
|
- **Multi-tenant isolation from day one.** Connector registries, vaults, config, and stores are tenant-scoped. Two organizations can share a process without cross-contamination.
|
|
43
|
-
- **Provider abstraction.**
|
|
43
|
+
- **Provider abstraction.** Seven drivers ship today, each its own package installed only if you use it, plus a scriptable mock pre-registered in the kernel. The `LLMProvider` interface is narrow enough that adding another is an afternoon. BYOK everywhere, no hidden hot paths for any vendor.
|
|
44
44
|
- **Telemetry.** OpenTelemetry-native spans and metrics. Cost accounting (input tokens, output tokens, cached tokens, cache write tokens, cache discount) flows from the provider into per-run, per-tenant rollups.
|
|
45
45
|
- **Prompt cache integration.** Hash-based system-prompt cache per thread, integrated with provider cache controls (OpenRouter `cacheControl` today, more planned), plus full cache telemetry in every run.
|
|
46
46
|
- **Vault.** BYOK credentials and secrets, tenant-scoped, pluggable backend.
|
|
@@ -52,11 +52,11 @@ Every one of those bullets points at code that exists today in `src/`. The archi
|
|
|
52
52
|
|
|
53
53
|
Equally important for scoping expectations:
|
|
54
54
|
|
|
55
|
-
- **Not a chat SDK.**
|
|
55
|
+
- **Not a chat SDK.** No front-end framework bindings, no generative UI components, no ready-made chat hook. Your UI framework is your choice; the kernel hands you a typed event stream.
|
|
56
56
|
- **Not a hosted service.** There is no dashboard, no Namzu Cloud, no billing page. You run it in your own process.
|
|
57
|
-
- **Not a deployment adapter.** No
|
|
57
|
+
- **Not a deployment adapter.** No web-framework or edge-runtime plumbing in the kernel. That belongs in separate packages or your own infra code.
|
|
58
58
|
- **Not a dev studio.** No bundled playground UI. A playground that consumes the kernel's event protocol could exist as a separate tool; it would not live inside `@namzu/sdk`.
|
|
59
|
-
- **Not a vector database.** RAG ships with a pluggable `VectorStore` interface
|
|
59
|
+
- **Not a vector database.** RAG ships with a pluggable `VectorStore` interface; the kernel embeds no vector engine of its own. Bring your own.
|
|
60
60
|
- **Not an LLM router service.** Task routing is an in-process policy, not a hosted service.
|
|
61
61
|
- **Not a prompt management UI.** Personas are code-defined (YAML files in your repo), not database rows behind a web form.
|
|
62
62
|
|
|
@@ -106,9 +106,19 @@ Every folder under `src/` maps to a traditional OS concept. This section walks t
|
|
|
106
106
|
|
|
107
107
|
### 1. The Boundary: Sandbox (`sandbox/`)
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
Tools do not execute in the host process. What that buys you **depends on the tier the host can supply, and the tiers do not all enforce the same controls** — so the kernel keeps an honest table rather than a promise (`sandbox/isolation.ts`):
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
| Environment | Filesystem | Network | Process |
|
|
112
|
+
|---|---|---|---|
|
|
113
|
+
| `macos-seatbelt` | yes | yes | yes |
|
|
114
|
+
| `linux-namespace` | **no** | yes | yes |
|
|
115
|
+
| `basic` | no | no | no |
|
|
116
|
+
|
|
117
|
+
`linux-namespace` reports `filesystem: false` deliberately. It unshares the mount namespace but never remounts anything, so the child still sees the whole host filesystem — a private mount table is not confinement, and saying otherwise here would be the exact defect the table exists to prevent.
|
|
118
|
+
|
|
119
|
+
This matters because the failure it guards against is silent. If a run requires a control the host cannot enforce, `assertIsolation` **refuses to start it** rather than proceeding at a weaker tier: a security control that is accepted and then quietly not applied is worse than one that was never offered, because the caller stops looking. Use `isolationOf`, `missingIsolation` and `describeIsolation` to ask what you are actually getting before you rely on it.
|
|
120
|
+
|
|
121
|
+
The `SandboxProvider` abstraction (`sandbox/factory.ts`, `sandbox/provider/`) lets you supply a stronger provider without touching the rest of the kernel. The kernel enforces memory, timeout, and max-process limits on top of whatever the sandbox gives you.
|
|
112
122
|
|
|
113
123
|
### 2. Interprocess Communication: Bridge (`bridge/`) and Bus (`bus/`)
|
|
114
124
|
|
|
@@ -136,7 +146,7 @@ These exist because the moment you have more than one agent running in parallel
|
|
|
136
146
|
- Emits `agent_pending` on the bus with parent/child/depth metadata
|
|
137
147
|
- Forwards every child event to the parent's run listener so the supervisor sees what its subtree is doing
|
|
138
148
|
|
|
139
|
-
When the parent is cancelled — by HITL, by a limit breach, or by an external signal — `cancelAll(parentRunId)` walks the subtree and aborts every descendant. This is the
|
|
149
|
+
When the parent is cancelled — by HITL, by a limit breach, or by an external signal — `cancelAll(parentRunId)` walks the subtree and aborts every descendant. This is the equivalent of signalling a whole process group.
|
|
140
150
|
|
|
141
151
|
`manager/connector/` manages the lifecycle of external connectors (MCP servers, HTTP connectors). `manager/plan/lifecycle.ts` coordinates HITL plan review. `manager/run/persistence.ts` is the run-level persistence surface, and `manager/run/emergency.ts` is the emergency-save subsystem (see §9 below).
|
|
142
152
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a-gateway-that-forgets-the-oldest.test.d.ts","sourceRoot":"","sources":["../../../src/gateway/__tests__/a-gateway-that-forgets-the-oldest.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { LocalTaskGateway } from '../local.js';
|
|
3
|
+
/**
|
|
4
|
+
* The gateway's two ledgers — `trackedTaskIds` and `settledHandles` — had `add`
|
|
5
|
+
* and `set` and no removal anywhere. The doc called them "bounded by the number
|
|
6
|
+
* the gateway itself launched", which is true and is not a bound: a gateway
|
|
7
|
+
* built per run is bounded by that run, but `SupervisorAgentConfig.gateway`
|
|
8
|
+
* lets a host supply its own, and a long-lived host reusing one accumulates an
|
|
9
|
+
* id and a settled handle for every task it ever launched.
|
|
10
|
+
*
|
|
11
|
+
* Eviction is oldest-first and takes both together. Dropping a tracked id while
|
|
12
|
+
* keeping its handle — or the reverse — would make a task that ran read as one
|
|
13
|
+
* that never launched, which is precisely the defect the settled-handle map was
|
|
14
|
+
* added to fix.
|
|
15
|
+
*/
|
|
16
|
+
const CAP = 1_000;
|
|
17
|
+
class CountingManager {
|
|
18
|
+
n = 0;
|
|
19
|
+
async sendMessage() {
|
|
20
|
+
this.n++;
|
|
21
|
+
return { taskId: `tsk_${this.n}`, state: 'running' };
|
|
22
|
+
}
|
|
23
|
+
async waitForCompletion() { }
|
|
24
|
+
getInstance() {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
cancel() { }
|
|
28
|
+
}
|
|
29
|
+
function context() {
|
|
30
|
+
return {
|
|
31
|
+
parentRunId: 'run_p',
|
|
32
|
+
parentAgentId: 'sup',
|
|
33
|
+
parentAbortController: new AbortController(),
|
|
34
|
+
depth: 0,
|
|
35
|
+
budgetTracker: { total: 1_000_000, remaining: 1_000_000 },
|
|
36
|
+
tenantId: 'tnt_g',
|
|
37
|
+
sessionId: 'ses_g',
|
|
38
|
+
projectId: 'prj_g',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function tracked(gateway) {
|
|
42
|
+
return gateway.trackedTaskIds;
|
|
43
|
+
}
|
|
44
|
+
function settled(gateway) {
|
|
45
|
+
return gateway.settledHandles;
|
|
46
|
+
}
|
|
47
|
+
async function launch(gateway, count) {
|
|
48
|
+
for (let i = 0; i < count; i++) {
|
|
49
|
+
await gateway.createTask({
|
|
50
|
+
agentId: 'w',
|
|
51
|
+
prompt: 'go',
|
|
52
|
+
workingDirectory: '/tmp',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
describe('a gateway that outlives its run forgets the oldest', () => {
|
|
57
|
+
it('keeps every task of a run that never reaches the cap', async () => {
|
|
58
|
+
// The control, and the case that must not change: a supervisor reading
|
|
59
|
+
// its listing at the end of a run sees everything it launched.
|
|
60
|
+
const gateway = new LocalTaskGateway(new CountingManager(), context());
|
|
61
|
+
await launch(gateway, 50);
|
|
62
|
+
expect(tracked(gateway).size).toBe(50);
|
|
63
|
+
});
|
|
64
|
+
it('stops growing once the cap is passed', async () => {
|
|
65
|
+
const gateway = new LocalTaskGateway(new CountingManager(), context());
|
|
66
|
+
await launch(gateway, CAP + 25);
|
|
67
|
+
expect(tracked(gateway).size).toBe(CAP);
|
|
68
|
+
});
|
|
69
|
+
it('drops the oldest, not the newest', async () => {
|
|
70
|
+
// A cap that evicted the most recent would keep the ledger small and
|
|
71
|
+
// answer every question wrongly.
|
|
72
|
+
const gateway = new LocalTaskGateway(new CountingManager(), context());
|
|
73
|
+
await launch(gateway, CAP + 3);
|
|
74
|
+
const ids = tracked(gateway);
|
|
75
|
+
expect(ids.has('tsk_1')).toBe(false);
|
|
76
|
+
expect(ids.has('tsk_3')).toBe(false);
|
|
77
|
+
expect(ids.has(`tsk_${CAP + 3}`)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
it('never keeps a settled handle for an id it has forgotten', async () => {
|
|
80
|
+
// The two must be evicted together. A handle whose id is gone is
|
|
81
|
+
// unreachable through `listTasks`, which walks the ids — so it would be
|
|
82
|
+
// retained memory serving nothing, and the leak would survive its own
|
|
83
|
+
// fix.
|
|
84
|
+
const gateway = new LocalTaskGateway(new CountingManager(), context());
|
|
85
|
+
await launch(gateway, 10);
|
|
86
|
+
const handles = settled(gateway);
|
|
87
|
+
for (let i = 1; i <= 10; i++)
|
|
88
|
+
handles.set(`tsk_${i}`, { id: i });
|
|
89
|
+
await launch(gateway, CAP + 5);
|
|
90
|
+
const ids = tracked(gateway);
|
|
91
|
+
for (const id of handles.keys()) {
|
|
92
|
+
expect({ id, trackedToo: ids.has(id) }).toEqual({ id, trackedToo: true });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=a-gateway-that-forgets-the-oldest.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a-gateway-that-forgets-the-oldest.test.js","sourceRoot":"","sources":["../../../src/gateway/__tests__/a-gateway-that-forgets-the-oldest.test.ts"],"names":[],"mappings":"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"}
|
package/dist/gateway/local.d.ts
CHANGED
|
@@ -59,6 +59,22 @@ export declare class LocalTaskGateway implements TaskGateway {
|
|
|
59
59
|
* Called when the task settles, while the manager still holds it.
|
|
60
60
|
*/
|
|
61
61
|
rememberSettled(taskId: TaskId): void;
|
|
62
|
+
/**
|
|
63
|
+
* Drop the oldest tasks once the ledger passes {@link GATEWAY_TASK_LEDGER_CAP}.
|
|
64
|
+
*
|
|
65
|
+
* A gateway constructed per run is bounded by that run and this never
|
|
66
|
+
* fires. But `SupervisorAgentConfig.gateway` lets a host supply its own,
|
|
67
|
+
* and a long-lived host reusing one accumulates an id and a settled handle
|
|
68
|
+
* per task it ever launched, for the life of the process — the doc above
|
|
69
|
+
* says "bounded by the number the gateway itself launched", which is true
|
|
70
|
+
* and is not a bound when the gateway outlives the run.
|
|
71
|
+
*
|
|
72
|
+
* Both collections are evicted **together and in insertion order**. Losing
|
|
73
|
+
* a tracked id while keeping its handle, or the reverse, would make a task
|
|
74
|
+
* that ran read as one that never launched — which is the exact defect the
|
|
75
|
+
* settled-handle map was added to fix, reintroduced by its own cleanup.
|
|
76
|
+
*/
|
|
77
|
+
private forgetOldestBeyondCap;
|
|
62
78
|
listTasks(): TaskHandle[];
|
|
63
79
|
/**
|
|
64
80
|
* Every event a child emits, reduced to "this one is still alive".
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/gateway/local.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,EACX,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,UAAU,EACV,MAAM,2BAA2B,CAAA;AAClC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAEnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/gateway/local.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,KAAK,EACX,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,EACX,UAAU,EACV,MAAM,2BAA2B,CAAA;AAClC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAEnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAe9D,qBAAa,gBAAiB,YAAW,WAAW;IACnD,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,cAAc,CAAyB;IAE/C,OAAO,CAAC,WAAW,CAAC,CAA2E;IAE/F,OAAO,CAAC,mBAAmB,CAA+C;IAE1E;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,cAAc,CAAqC;IAE3D,OAAO,CAAC,oBAAoB,CAAmC;IAC/D,kCAAkC;IAClC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsC;gBAGvE,YAAY,EAAE,oBAAoB,EAClC,WAAW,EAAE,gBAAgB,EAC7B,QAAQ,CAAC,EAAE,gBAAgB,EAC3B,WAAW,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,sBAAsB,GAAG,gBAAgB,CAAC,EACvF,OAAO,CAAC,EAAE;QAAE,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;KAAE;IAWpD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC;IA8GjE;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,kBAAkB;IA0BpB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAShD,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIhC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAK/C;;;;OAIG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAKrC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,qBAAqB;IAS7B,SAAS,IAAI,UAAU,EAAE;IAgBzB;;;;;;;OAOG;IACH,cAAc,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAO9D,eAAe,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI;CAMnE"}
|
package/dist/gateway/local.js
CHANGED
|
@@ -2,6 +2,16 @@ import { taskFailed } from '../tools/coordinator/outcome.js';
|
|
|
2
2
|
import { createUserMessage } from '../types/message/index.js';
|
|
3
3
|
import { toErrorMessage } from '../utils/error.js';
|
|
4
4
|
import { getRootLogger } from '../utils/logger.js';
|
|
5
|
+
/**
|
|
6
|
+
* How many launched tasks a gateway remembers.
|
|
7
|
+
*
|
|
8
|
+
* High enough that no realistic single run reaches it — a fan-out is eight,
|
|
9
|
+
* a long supervisory run is dozens — so the listing a supervisor reads at the
|
|
10
|
+
* end of its run is always complete. It exists for the host that reuses one
|
|
11
|
+
* gateway across runs, where the alternative is a set and a map that grow for
|
|
12
|
+
* the life of the process.
|
|
13
|
+
*/
|
|
14
|
+
const GATEWAY_TASK_LEDGER_CAP = 1_000;
|
|
5
15
|
export class LocalTaskGateway {
|
|
6
16
|
agentManager;
|
|
7
17
|
taskContext;
|
|
@@ -114,6 +124,7 @@ export class LocalTaskGateway {
|
|
|
114
124
|
});
|
|
115
125
|
launched.id = task.taskId;
|
|
116
126
|
this.trackedTaskIds.add(task.taskId);
|
|
127
|
+
this.forgetOldestBeyondCap();
|
|
117
128
|
this.agentManager
|
|
118
129
|
.waitForCompletion(task.taskId)
|
|
119
130
|
.then(() => {
|
|
@@ -210,6 +221,30 @@ export class LocalTaskGateway {
|
|
|
210
221
|
if (task)
|
|
211
222
|
this.settledHandles.set(taskId, toHandle(task));
|
|
212
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Drop the oldest tasks once the ledger passes {@link GATEWAY_TASK_LEDGER_CAP}.
|
|
226
|
+
*
|
|
227
|
+
* A gateway constructed per run is bounded by that run and this never
|
|
228
|
+
* fires. But `SupervisorAgentConfig.gateway` lets a host supply its own,
|
|
229
|
+
* and a long-lived host reusing one accumulates an id and a settled handle
|
|
230
|
+
* per task it ever launched, for the life of the process — the doc above
|
|
231
|
+
* says "bounded by the number the gateway itself launched", which is true
|
|
232
|
+
* and is not a bound when the gateway outlives the run.
|
|
233
|
+
*
|
|
234
|
+
* Both collections are evicted **together and in insertion order**. Losing
|
|
235
|
+
* a tracked id while keeping its handle, or the reverse, would make a task
|
|
236
|
+
* that ran read as one that never launched — which is the exact defect the
|
|
237
|
+
* settled-handle map was added to fix, reintroduced by its own cleanup.
|
|
238
|
+
*/
|
|
239
|
+
forgetOldestBeyondCap() {
|
|
240
|
+
while (this.trackedTaskIds.size > GATEWAY_TASK_LEDGER_CAP) {
|
|
241
|
+
const oldest = this.trackedTaskIds.values().next().value;
|
|
242
|
+
if (oldest === undefined)
|
|
243
|
+
return;
|
|
244
|
+
this.trackedTaskIds.delete(oldest);
|
|
245
|
+
this.settledHandles.delete(oldest);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
213
248
|
listTasks() {
|
|
214
249
|
const handles = [];
|
|
215
250
|
for (const taskId of this.trackedTaskIds) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/gateway/local.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/gateway/local.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import type { AgentManagerContract } from '../../types/agent/manager.js'
|
|
4
|
+
import type { AgentTask, AgentTaskContext } from '../../types/agent/task.js'
|
|
5
|
+
import type { AgentId, TaskId } from '../../types/ids/index.js'
|
|
6
|
+
import { LocalTaskGateway } from '../local.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The gateway's two ledgers — `trackedTaskIds` and `settledHandles` — had `add`
|
|
10
|
+
* and `set` and no removal anywhere. The doc called them "bounded by the number
|
|
11
|
+
* the gateway itself launched", which is true and is not a bound: a gateway
|
|
12
|
+
* built per run is bounded by that run, but `SupervisorAgentConfig.gateway`
|
|
13
|
+
* lets a host supply its own, and a long-lived host reusing one accumulates an
|
|
14
|
+
* id and a settled handle for every task it ever launched.
|
|
15
|
+
*
|
|
16
|
+
* Eviction is oldest-first and takes both together. Dropping a tracked id while
|
|
17
|
+
* keeping its handle — or the reverse — would make a task that ran read as one
|
|
18
|
+
* that never launched, which is precisely the defect the settled-handle map was
|
|
19
|
+
* added to fix.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const CAP = 1_000
|
|
23
|
+
|
|
24
|
+
class CountingManager {
|
|
25
|
+
private n = 0
|
|
26
|
+
|
|
27
|
+
async sendMessage(): Promise<AgentTask> {
|
|
28
|
+
this.n++
|
|
29
|
+
return { taskId: `tsk_${this.n}` as TaskId, state: 'running' } as AgentTask
|
|
30
|
+
}
|
|
31
|
+
async waitForCompletion(): Promise<void> {}
|
|
32
|
+
getInstance(): AgentTask | undefined {
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
cancel(): void {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function context(): AgentTaskContext {
|
|
39
|
+
return {
|
|
40
|
+
parentRunId: 'run_p' as never,
|
|
41
|
+
parentAgentId: 'sup',
|
|
42
|
+
parentAbortController: new AbortController(),
|
|
43
|
+
depth: 0,
|
|
44
|
+
budgetTracker: { total: 1_000_000, remaining: 1_000_000 },
|
|
45
|
+
tenantId: 'tnt_g' as never,
|
|
46
|
+
sessionId: 'ses_g' as never,
|
|
47
|
+
projectId: 'prj_g' as never,
|
|
48
|
+
} as unknown as AgentTaskContext
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function tracked(gateway: LocalTaskGateway): Set<TaskId> {
|
|
52
|
+
return (gateway as unknown as { trackedTaskIds: Set<TaskId> }).trackedTaskIds
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function settled(gateway: LocalTaskGateway): Map<TaskId, unknown> {
|
|
56
|
+
return (gateway as unknown as { settledHandles: Map<TaskId, unknown> }).settledHandles
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function launch(gateway: LocalTaskGateway, count: number): Promise<void> {
|
|
60
|
+
for (let i = 0; i < count; i++) {
|
|
61
|
+
await gateway.createTask({
|
|
62
|
+
agentId: 'w' as AgentId,
|
|
63
|
+
prompt: 'go',
|
|
64
|
+
workingDirectory: '/tmp',
|
|
65
|
+
} as never)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe('a gateway that outlives its run forgets the oldest', () => {
|
|
70
|
+
it('keeps every task of a run that never reaches the cap', async () => {
|
|
71
|
+
// The control, and the case that must not change: a supervisor reading
|
|
72
|
+
// its listing at the end of a run sees everything it launched.
|
|
73
|
+
const gateway = new LocalTaskGateway(
|
|
74
|
+
new CountingManager() as unknown as AgentManagerContract,
|
|
75
|
+
context(),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
await launch(gateway, 50)
|
|
79
|
+
|
|
80
|
+
expect(tracked(gateway).size).toBe(50)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('stops growing once the cap is passed', async () => {
|
|
84
|
+
const gateway = new LocalTaskGateway(
|
|
85
|
+
new CountingManager() as unknown as AgentManagerContract,
|
|
86
|
+
context(),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
await launch(gateway, CAP + 25)
|
|
90
|
+
|
|
91
|
+
expect(tracked(gateway).size).toBe(CAP)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('drops the oldest, not the newest', async () => {
|
|
95
|
+
// A cap that evicted the most recent would keep the ledger small and
|
|
96
|
+
// answer every question wrongly.
|
|
97
|
+
const gateway = new LocalTaskGateway(
|
|
98
|
+
new CountingManager() as unknown as AgentManagerContract,
|
|
99
|
+
context(),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
await launch(gateway, CAP + 3)
|
|
103
|
+
|
|
104
|
+
const ids = tracked(gateway)
|
|
105
|
+
expect(ids.has('tsk_1' as TaskId)).toBe(false)
|
|
106
|
+
expect(ids.has('tsk_3' as TaskId)).toBe(false)
|
|
107
|
+
expect(ids.has(`tsk_${CAP + 3}` as TaskId)).toBe(true)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('never keeps a settled handle for an id it has forgotten', async () => {
|
|
111
|
+
// The two must be evicted together. A handle whose id is gone is
|
|
112
|
+
// unreachable through `listTasks`, which walks the ids — so it would be
|
|
113
|
+
// retained memory serving nothing, and the leak would survive its own
|
|
114
|
+
// fix.
|
|
115
|
+
const gateway = new LocalTaskGateway(
|
|
116
|
+
new CountingManager() as unknown as AgentManagerContract,
|
|
117
|
+
context(),
|
|
118
|
+
)
|
|
119
|
+
await launch(gateway, 10)
|
|
120
|
+
const handles = settled(gateway)
|
|
121
|
+
for (let i = 1; i <= 10; i++) handles.set(`tsk_${i}` as TaskId, { id: i })
|
|
122
|
+
|
|
123
|
+
await launch(gateway, CAP + 5)
|
|
124
|
+
|
|
125
|
+
const ids = tracked(gateway)
|
|
126
|
+
for (const id of handles.keys()) {
|
|
127
|
+
expect({ id, trackedToo: ids.has(id) }).toEqual({ id, trackedToo: true })
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
})
|
package/src/gateway/local.ts
CHANGED
|
@@ -14,6 +14,17 @@ import type { RunEventListener } from '../types/run/events.js'
|
|
|
14
14
|
import { toErrorMessage } from '../utils/error.js'
|
|
15
15
|
import { getRootLogger } from '../utils/logger.js'
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* How many launched tasks a gateway remembers.
|
|
19
|
+
*
|
|
20
|
+
* High enough that no realistic single run reaches it — a fan-out is eight,
|
|
21
|
+
* a long supervisory run is dozens — so the listing a supervisor reads at the
|
|
22
|
+
* end of its run is always complete. It exists for the host that reuses one
|
|
23
|
+
* gateway across runs, where the alternative is a set and a map that grow for
|
|
24
|
+
* the life of the process.
|
|
25
|
+
*/
|
|
26
|
+
const GATEWAY_TASK_LEDGER_CAP = 1_000
|
|
27
|
+
|
|
17
28
|
export class LocalTaskGateway implements TaskGateway {
|
|
18
29
|
private agentManager: AgentManagerContract
|
|
19
30
|
private taskContext: AgentTaskContext
|
|
@@ -140,6 +151,7 @@ export class LocalTaskGateway implements TaskGateway {
|
|
|
140
151
|
|
|
141
152
|
launched.id = task.taskId
|
|
142
153
|
this.trackedTaskIds.add(task.taskId)
|
|
154
|
+
this.forgetOldestBeyondCap()
|
|
143
155
|
|
|
144
156
|
this.agentManager
|
|
145
157
|
.waitForCompletion(task.taskId)
|
|
@@ -242,6 +254,30 @@ export class LocalTaskGateway implements TaskGateway {
|
|
|
242
254
|
if (task) this.settledHandles.set(taskId, toHandle(task))
|
|
243
255
|
}
|
|
244
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Drop the oldest tasks once the ledger passes {@link GATEWAY_TASK_LEDGER_CAP}.
|
|
259
|
+
*
|
|
260
|
+
* A gateway constructed per run is bounded by that run and this never
|
|
261
|
+
* fires. But `SupervisorAgentConfig.gateway` lets a host supply its own,
|
|
262
|
+
* and a long-lived host reusing one accumulates an id and a settled handle
|
|
263
|
+
* per task it ever launched, for the life of the process — the doc above
|
|
264
|
+
* says "bounded by the number the gateway itself launched", which is true
|
|
265
|
+
* and is not a bound when the gateway outlives the run.
|
|
266
|
+
*
|
|
267
|
+
* Both collections are evicted **together and in insertion order**. Losing
|
|
268
|
+
* a tracked id while keeping its handle, or the reverse, would make a task
|
|
269
|
+
* that ran read as one that never launched — which is the exact defect the
|
|
270
|
+
* settled-handle map was added to fix, reintroduced by its own cleanup.
|
|
271
|
+
*/
|
|
272
|
+
private forgetOldestBeyondCap(): void {
|
|
273
|
+
while (this.trackedTaskIds.size > GATEWAY_TASK_LEDGER_CAP) {
|
|
274
|
+
const oldest = this.trackedTaskIds.values().next().value
|
|
275
|
+
if (oldest === undefined) return
|
|
276
|
+
this.trackedTaskIds.delete(oldest)
|
|
277
|
+
this.settledHandles.delete(oldest)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
245
281
|
listTasks(): TaskHandle[] {
|
|
246
282
|
const handles: TaskHandle[] = []
|
|
247
283
|
for (const taskId of this.trackedTaskIds) {
|