Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs-site/static/search-index.json
Original file line number Diff line number Diff line change
Expand Up @@ -8368,7 +8368,7 @@
{"objectID":"549e7b7594cf096b8e419d967e9cfa997bb10defbc439635f2152bc7a919b3e5","title":"5. Behavioral contract (acceptance spec)","url":"/docs/plans/2026-07-27-isolated-agent-runner-rfc#5-behavioral-contract-acceptance-spec","content":"Lifecycle — worker instance from N3, disposed in — including on the leg\n path when the handle expires (TTL) or is stopped.\nResearch pass runs with tools under the turn budget ( + wrap-up nudge\nstall watchdog — the machinery from , not a reimplementation). NEVER a\n bare wall-clock abort: a budget-capped run ends with the model consolidating, an honest\n , and . (Leashed is fed in as \n so leg ends are consolidations too; only and waste trips end a leg by\n abort, and both preserve the records.)\nExtraction pass always runs (when is configured), tools disabled, on\n its OWN timeout — never carved out of the research budget — fed from the tool-execution\n records, so a research generate that died on a provider error still extracts from the\n records instead of losing the run. In leashed mode extraction runs on terminal legs;\n intermediate legs return summaries instead (per the O2 pattern —\n extracting every leg would burn the budget the leash exists to protect).\nStructured recovery built in — candidates in order: provider → raw\n JSON object → coerceschemamaxRetriesshapeDocstatus: 'error' | 'partial'abortSignalstopReason: \"aborted\"onEventstarttoolcalltoolresultphasewrapupleg_endwastecompleteerrortoolExecutionCapture.onRecordnextPlanwasteSignalsduplicateCallLimit: 2emptyResultStreakLimit: 3errorStreakLimit: 3noNewResultsLimit: 8`).","hierarchy":{"lvl0":"Plans","lvl1":"RFC: `runIsolatedAgent()` — production sub-agent runner + host-loop delegation","lvl2":"5. Behavioral contract (acceptance spec)","lvl3":""}},
{"objectID":"47dc3f7e12e98eb3908b16a7756ff38b9981a247958227b5a8737bd23da37fbf","title":"6. Field validation matrix","url":"/docs/plans/2026-07-27-isolated-agent-runner-rfc#6-field-validation-matrix","content":"Every public field mapped against the three reference implementations; fields that don't\nmap to all three are justified below the table.\n\n| Field | (a) research worker | (b) log-analysis worker | (c) Yama ContextExplorer |\n| ------------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------ |\n| | persona + code-search MCP filter | persona + log tools | persona + repo tools |\n| (lenient) | evidence list validator | findings validator | context-pack validator |\n| (strict) | provider-attached; no defaults/catch | same | same |\n| | corrective re-ask shape doc | same | same |\n| | wraps bare top-level arrays | normalizes evidence rows | wraps arrays |\n| | parent turn cancels worker | same | same |\n| | O2 defaults | internal-caller overrides (the named co","hierarchy":{"lvl0":"Plans","lvl1":"RFC: `runIsolatedAgent()` — production sub-agent runner + host-loop delegation","lvl2":"6. Field validation matrix","lvl3":""}},
{"objectID":"97996d2242f9f46d729177904ae2aad1103120154667acc5dc5a9f3990601621","title":"6b. Migration note — GenerateResult.toolExecutions shape change","url":"/docs/plans/2026-07-27-isolated-agent-runner-rfc#6b-migration-note-generateresulttoolexecutions-shape-change","content":"The historical stub entries were (with fabricated\n). They are replaced by real s. Consumers\nreading the old fields get — update reads as follows:\n\n| Old field | New field | Notes |\n| ---------- | ------------ | ------------------------------------------------- |\n| | | |\n| | | as parsed by the loop |\n| | | serialized + bounded (~8KB default; cap raisable) |\n| | | real wall-clock, no longer always 0 |\n| — | | new: thrown errors AND error-shaped results |\n| — | | new: epoch ms |\n\n is deliberately unchanged (legacy\n summaries) — the stream-side contract is a\nseparate surface and migrating it is out of scope here.","hierarchy":{"lvl0":"Plans","lvl1":"RFC: `runIsolatedAgent()` — production sub-agent runner + host-loop delegation","lvl2":"6b. Migration note — GenerateResult.toolExecutions shape change","lvl3":""}},
{"objectID":"e6a7791de93faea3f0d6b1bcad40aa5de97cfdfefacd9b9e8a7d2965f946b67e","title":"6c. Known limitations (follow-up work)","url":"/docs/plans/2026-07-27-isolated-agent-runner-rfc#6c-known-limitations-follow-up-work","content":"Log-bridge attribution: the NeuroLink logger is process-global with a\n single active emitter, so a worker's bridge receives all NeuroLink\n log events in the process, stamped with the bridge's tag. Per-instance\n attribution requires per-instance logger routing.\n/ events fire post-execution (driven by the\n capture record) — a pre-execution hook on the recorder wrapper is the\n natural extension when live in-flight status is needed.\nNested delegations bypass the concurrency pool (the outer delegation\n already holds a slot; queueing nested work behind a full pool would\n deadlock it). Nested fan-out is therefore bounded by the outer slots ×\n per-run step caps, not by the pool directly. standalone\n delegations are always top-level and stay pooled.","hierarchy":{"lvl0":"Plans","lvl1":"RFC: `runIsolatedAgent()` — production sub-agent runner + host-loop delegation","lvl2":"6c. Known limitations (follow-up work)","lvl3":""}},
{"objectID":"e6a7791de93faea3f0d6b1bcad40aa5de97cfdfefacd9b9e8a7d2965f946b67e","title":"6c. Known limitations (follow-up work)","url":"/docs/plans/2026-07-27-isolated-agent-runner-rfc#6c-known-limitations-follow-up-work","content":"~~Log-bridge attribution~~ — resolved. The logger now routes per\n instance: // run their bodies inside an\n AsyncLocalStorage scope carrying the emitting instance's id, and a worker's\n bridge is subscribed to that id instead of the host's process-wide\n emitter. Residual scope gaps, documented on :\n logs emitted while a consumer drains a returned stream run in the\n consumer's context, and logs emitted outside any call (construction,\n background MCP reconnects) stay unattributed.\n/ events fire post-execution (driven by the\n capture record) — a pre-execution hook on the recorder wrapper is the\n natural extension when live in-flight status is needed.\nNested delegations bypass the concurrency pool (the outer delegation\n already holds a slot; queueing nested work behind a full pool would\n deadlock it). Nested fan-out is therefore bounded by the outer slots ×\n per-run step caps, not by the pool directly. standalone\n delegations are always top-level and stay pooled.","hierarchy":{"lvl0":"Plans","lvl1":"RFC: `runIsolatedAgent()` — production sub-agent runner + host-loop delegation","lvl2":"6c. Known limitations (follow-up work)","lvl3":""}},
{"objectID":"98a6085557f7953269c6b8e5c9e872254ce137f4b6d4933dcca1db421e1fc7d8","title":"7. Implementation notes","url":"/docs/plans/2026-07-27-isolated-agent-runner-rfc#7-implementation-notes","content":"N2 capture rides a per-call \n () attached to the request options and wrapped at\n the two central tool-assembly points (, ) —\n BEFORE tool discovery, so mid-turn hydration inserts wrapped tools. All\n loops (AI-SDK, native Vertex Gemini/Claude, AI Studio) obtain tools through those\n points, so one wrapper covers every path with no per-loop double-recording.\nN3 sampling strip is registry-driven ( /\n in ): an explicit\n on a registry entry wins; otherwise known rejecting-family\n patterns (Sonnet 5, Opus 4.7+, Opus 5, Fable/Mythos) decide. Applied at every\n request-build site whose object retry paths spread ( on both Vertex\n Claude loops, on the OpenAI-compatible path, both direct-Anthropic builders),\n so retries/fallbacks inherit the strip; the reactive\n retry remains as the safety net. A debug log is emitted\n whenever params are stripped.\nN5 turn counting uses AsyncLocalStorage entered at the public when\n agent tools are registered; nested/internal generates share the top-level turn's\n counters ( returns null inside an active scope).\nThe stretch item (neutralizing instruction-shaped patterns in agent reports) is NOT part\n of this change, per the work plan (separate optional PR).","hierarchy":{"lvl0":"Plans","lvl1":"RFC: `runIsolatedAgent()` — production sub-agent runner + host-loop delegation","lvl2":"7. Implementation notes","lvl3":""}},
{"objectID":"bdfafc0f3a8fd37a6271bd7957aff7145529894007854c3fe8ddf4556f2f759f","title":"8. PR slicing","url":"/docs/plans/2026-07-27-isolated-agent-runner-rfc#8-pr-slicing","content":"The work lands as one PR per work item: N1 (agent plumbing), N2 (toolExecutions records),\nN3 (worker factory + sampling strip), N4 (runIsolatedAgent + this RFC), N5 (host-loop\ndelegation + AgentNetwork composition). N4/N5 ship with this RFC in-tree.","hierarchy":{"lvl0":"Plans","lvl1":"RFC: `runIsolatedAgent()` — production sub-agent runner + host-loop delegation","lvl2":"8. PR slicing","lvl3":""}},
{"objectID":"f0fee3d8005091330b88abba8bf41bc4b4e1b5f294425e4f0a250f93bb8f165d","title":"Completing the ai-sdk removal","url":"/docs/plans/2026-09-03-completing-the-ai-sdk-removal","content":"Completing the ai-sdk removal\n\nStatus: done. All 8 items landed. Item 7 — deleting —\nwas the last, in . and are gone from\nand , and keeps\nthem out.\n\nWhat is left, how each piece is solved, and the order forced by their\ndependencies. Every claim here was checked against the source or the installed\npackage, not inferred.\n\nThe rule that governs all of it\n\nRun on every step, not just the live matrix. The live\nmatrix has 40 cells across six providers and it passed a change that broke ten\nof them, because every provider reachable from this machine supports streaming\nand the mocked gate is the only thing that serves a non-streaming body. The\nlive matrix proves behaviour; the mocked gate proves the wire.\nAnthropic native generate\n\n hardcodes when it calls\n (), which is why routing generate through\nit changed the wire.\n\nSolution is the shape already proven for the OpenAI-compatible family: loop over\nthe provider's own delegating-model , which issues a non-streaming\n. Wrap each step in , funnel failures\nthrough , run the turn inside\n (now protected), and call .\n\nCarry over the two fixes found the first time. A schema arriving with no tools\nmust declare as the turn's only tool and pin to it,\nbecause declines an empty tool list. And must\nbe fired explicitly.\nSageMaker\n\n makes one call and already\nreturns ; no streaming is involved, so the wire hazard does not\napply. Same loop shape as above.\n\nThis machine has no SageMaker endpoint or credentials. Its single-step\nbehaviour is identical by construction because it is the same \ncall; the multi-step branch is the new code and needs a live endpoint before it\nis trusted. Say so in the commit rather than implying coverage.\nGuardrails filter and video-analysis formatting\n\nBoth want a single no-tool turn. did this cleanly before\nthe revert took it with everything else: it calls directly and\nreads the text out of the v3 content array. Re-apply unchanged.\nwrapLanguageModel\n\nUpstream is about fifty lines: reverse the middleware array, reduce it, and\nreturn an object that keeps , , and\n while routing and through\n plus the optional / hooks. One\nconsumer, . Reimplement directly.\n\nRecord while doing it that never runs today, because every\nstreaming path is native and bypasses the wrapped model.\nThe tool and schema type algebra\n\nThis is the one that must move as a unit, and the reason the first attempt\nfailed: was replaced while still came from , so the\nreplacement had to satisfy a type it no longer matched.\n\nThe algebra is small and fully specified in the installed package:\nis .\nis the union of , , and\n .\nis a conditional chain over those four.\ncarries , which is\n what ties 's first parameter to the schema.\n\nDeclare all of it in , repoint at the local\ndeclarations, and only then implement , and in\n. is identity upstream and is\n; the work is entirely in the types.\n\n should keep stamping . Nothing in\nthis repo reads it — looks for the plain \nproperty — but keeping it costs nothing and preserves recognition by anything\nthat does.\nThe rest of the public type surface\n\nThree files hand-declare structurally: (the message and part\ntypes), (the model, usage and finish-reason types) and\n (the middleware contract plus the protocol\ntypes from ).\n\nThe non-obvious one is , which embeds inferred\n references. No source edit removes those; they\ndisappear only once itself is local. Verify by hiding the package and\nre-running a consumer typecheck, which is how the leak was confirmed in the\nfirst place.\nGenerationHandler\n\n1409 lines, and its is unreachable once Anthropic and\nSageMaker are native. Four result-formatting helpers on it are still called from\n, but they take the result shape, so they die with\nit. Confirm by trapping the seam and running the full matrix plus the mocked\ngate, then delete. and lose their last consumers here.\nBrowser bundle\n\n still re-exports , ,\n and through the seam. No consumer was found for\nthem. Either drop them or map them onto NeuroLink's own equivalents; this is a\npublic-subpath decision, not a technical one.\n\nOrder\n\nType algebra last among the type work but before the package drop; everything\nelse is independent.\nAnthropic, SageMaker, guardrails and video-analysis — the last runtime\n callers of .\n.\nDelete the dead GenerationHandler path.\nThe tool/schema algebra together with and .\nThe remaining public types.\nBrowser re-exports.\nDrop and , extend , and rewrite the\n documentation snippets that still tell users to import from directly.","hierarchy":{"lvl0":"Plans","lvl1":"Completing the ai-sdk removal","lvl2":"","lvl3":""}},
Expand Down
1 change: 1 addition & 0 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3077,6 +3077,7 @@ console.log(result.content);
- [APIValidationResult](type-aliases/APIValidationResult.md)
- [ParsedProxyConfig](type-aliases/ParsedProxyConfig.md)
- [LogEntry](type-aliases/LogEntry.md)
- [LogEventEmitter](type-aliases/LogEventEmitter.md)
- [Logger](type-aliases/Logger.md)
- [StructuredError](type-aliases/StructuredError.md)
- [EnhancementType](type-aliases/EnhancementType.md)
Expand Down
34 changes: 34 additions & 0 deletions docs/api/type-aliases/LogEventEmitter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[**NeuroLink API Reference**](../README.md)

---

[NeuroLink API Reference](../README.md) / LogEventEmitter

# Type Alias: LogEventEmitter

> **LogEventEmitter** = `object`

Minimal emitter shape the logger forwards `"log-event"` payloads to.

Structurally satisfied by `node:events`' `EventEmitter` and by NeuroLink's
own typed emitter, so a sink can be either.

## Properties

### emit

> **emit**: (`event`, ...`args`) => `boolean`

#### Parameters

##### event

`string`

##### args

...`unknown`[]

#### Returns

`boolean`
10 changes: 7 additions & 3 deletions docs/api/type-aliases/Logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
Logger interface matching the logger object shape
Used for SDK tool contexts and other components that need a logger

Deliberately a subset of the real logger: the per-instance routing methods
(`runInInstanceScope`, `addScopedEventEmitter`, …) are internal plumbing
between the SDK entry points and the logger, and this type is a structural
contract a caller can satisfy — `SDKToolContext.logger`. Adding required
members here would break anyone constructing that context themselves.

## Properties

### debug
Expand Down Expand Up @@ -159,9 +165,7 @@ Used for SDK tool contexts and other components that need a logger

##### emitter

###### emit

(`event`, ...`args`) => `boolean`
[`LogEventEmitter`](LogEventEmitter.md)

#### Returns

Expand Down
24 changes: 17 additions & 7 deletions docs/api/type-aliases/WorkerInstanceOptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,23 @@ Tag stamped on every forwarded log event (default "worker").
Log bridge sink. Fire-and-forget: listener errors never disrupt the
worker.

KNOWN LIMITATION: the underlying NeuroLink logger is process-global with
a single active emitter, so this bridge receives ALL NeuroLink log
events emitted in the process (host, other workers, MCP) for the
worker's lifetime, each stamped with this worker's `tag`. The tag
identifies which bridge forwarded the event, not which instance emitted
it. Per-instance log attribution requires per-instance logger routing —
tracked as follow-up work in the RFC.
Receives only log events this worker emitted. The logger routes per
instance: the worker's `generate` / `stream` / `generateText` run their
bodies inside an AsyncLocalStorage scope carrying the worker's id, and
this bridge is subscribed to that id — so a sibling worker's, the host's
or a background MCP reconnect's logs never arrive here, and `tag` is
true attribution rather than "whichever bridge forwarded it".

Two things stay outside the scope, by construction:

- Logs emitted while a **consumer drains a returned stream**. Iteration
happens in the consumer's async context, after `stream()` resolved.
- Logs emitted **outside any call** — construction, background MCP
reconnects, module init. These are unattributed rather than charged to
an arbitrary instance.

A process-wide sink (`logger.setEventEmitter`) still sees everything, so
a host bridge is unaffected by this narrowing.

#### Parameters

Expand Down
88 changes: 83 additions & 5 deletions docs/api/variables/logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,7 @@ Features:

##### emitter

###### emit

(`event`, ...`args`) => `boolean`
[`LogEventEmitter`](../type-aliases/LogEventEmitter.md)

#### Returns

Expand All @@ -207,9 +205,89 @@ Features:

##### ifEmitter?

###### emit
[`LogEventEmitter`](../type-aliases/LogEventEmitter.md)

#### Returns

`void`

### runInInstanceScope

> **runInInstanceScope**: \<`T`\>(`instanceId`, `fn`) => `T`

#### Type Parameters

##### T

`T`

#### Parameters

##### instanceId

`string`

##### fn

() => `T`

#### Returns

`T`

### getInstanceScope

> **getInstanceScope**: () => `string` \| `undefined`

#### Returns

`string` \| `undefined`

### addScopedEventEmitter

> **addScopedEventEmitter**: (`instanceId`, `emitter`) => `void`

#### Parameters

##### instanceId

`string`

##### emitter

[`LogEventEmitter`](../type-aliases/LogEventEmitter.md)

#### Returns

`void`

### removeScopedEventEmitter

> **removeScopedEventEmitter**: (`instanceId`, `emitter`) => `void`

#### Parameters

##### instanceId

`string`

##### emitter

[`LogEventEmitter`](../type-aliases/LogEventEmitter.md)

#### Returns

`void`

### clearScopedEventEmitters

> **clearScopedEventEmitters**: (`instanceId`) => `void`

#### Parameters

##### instanceId

(`event`, ...`args`) => `boolean`
`string`

#### Returns

Expand Down
Loading
Loading