refactor(codemode): remove tool concurrency cap (#36545)
This commit is contained in:
parent
b2bdab24e6
commit
3c60470269
6 changed files with 13 additions and 27 deletions
|
|
@ -177,8 +177,8 @@ No limit has a default, on purpose: execution budgets are host policy. A host wi
|
|||
interruption should set `maxOutputBytes` and `timeoutMs`. Limits are safe integers; invalid configuration throws a
|
||||
`RangeError` at construction. Exceeding `maxOutputBytes` never fails the execution - oversized output is truncated
|
||||
with an in-band marker. The timeout interrupts in-flight tool fibers and pure busy loops alike; a value the program
|
||||
already returned survives a cleanup timeout as a success with a `TimeoutExceeded` warning. Two internals are fixed
|
||||
constants, not knobs: at most 8 concurrent tool calls, and 32 levels of data nesting at boundaries.
|
||||
already returned survives a cleanup timeout as a success with a `TimeoutExceeded` warning. CodeMode does not limit
|
||||
tool-call concurrency. Data nesting at boundaries is limited to 32 levels.
|
||||
|
||||
## Boundaries and Non-Goals
|
||||
|
||||
|
|
|
|||
|
|
@ -77,15 +77,15 @@ order, and a combinator settles one reaction turn after its deciding member - wi
|
|||
parity beyond that. At normal completion CodeMode interrupts everything still running - race losers,
|
||||
fail-fast `Promise.all` stragglers, and fire-and-forget calls alike: the program has returned, so no future await can
|
||||
exist, and work whose completion matters must be awaited by the program. Waiting for any class of leftover instead
|
||||
would let it hold the execution open, or deadlock it when queued work needs tool-call permits the leftovers occupy.
|
||||
would let it hold the execution open indefinitely.
|
||||
Rejections that settled un-awaited before the return become `Success.warnings` diagnostics. A fatal program failure or
|
||||
host interruption closes the execution promise scope and interrupts its active fibers instead. A timeout does the
|
||||
same, except that a value the program already returned is preserved alongside a `TimeoutExceeded` warning rather than
|
||||
discarded. At most eight tool calls execute concurrently.
|
||||
discarded. CodeMode does not limit tool-call concurrency.
|
||||
|
||||
The public execution-policy knobs are `timeoutMs`, `maxToolCalls`, and `maxOutputBytes`. The package supplies no
|
||||
defaults because budgets are host policy. The interpreter also enforces fixed internal boundaries for tool-call
|
||||
concurrency and data nesting depth. `maxOutputBytes` bounds retained payload bytes, not the complete rendered message;
|
||||
defaults because budgets are host policy. The interpreter also enforces a fixed internal data nesting depth.
|
||||
`maxOutputBytes` bounds retained payload bytes, not the complete rendered message;
|
||||
warning diagnostics have an equal separate budget so a large value cannot starve them, and fixed truncation notices and
|
||||
host-added framing are intentionally outside the budgets.
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ ultimate source of truth.
|
|||
- [x] Tool calls through the host-provided `tools` tree only.
|
||||
- [x] The global `search(...)` built-in: synchronous tool discovery that counts as an admitted tool call and is
|
||||
shadowable by program declarations like other globals.
|
||||
- [x] Cooperative timeout, tool-call accounting, output bounding, and a maximum of eight concurrent tool calls.
|
||||
- [x] Cooperative timeout, an optional total tool-call limit, output bounding, and unrestricted tool-call concurrency.
|
||||
- [ ] Full JavaScript or TypeScript compatibility. CodeMode is a bounded orchestration language.
|
||||
|
||||
## Values and literals
|
||||
|
|
@ -291,7 +291,6 @@ ultimate source of truth.
|
|||
These are actionable implementation items. Check them off only when behavior and direct tests land.
|
||||
|
||||
- [x] Return real promises from `Promise.all`, `Promise.allSettled`, and `Promise.race`.
|
||||
- [ ] Bound pending tool-call admission/allocation in addition to execution concurrency.
|
||||
- [ ] Guarantee every advertised tool path is executable, including dotted and blocked path segments.
|
||||
- [ ] Define safe outbound handling for non-finite numbers and `undefined` so invalid values cannot silently become
|
||||
`null` in render-only or OpenAPI tool calls.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Cause, Effect, Semaphore } from "effect"
|
||||
import { Cause, Effect } from "effect"
|
||||
import { isBlockedMember, ToolReference, ToolRuntimeError, type SafeObject } from "../tool-runtime.js"
|
||||
import {
|
||||
type AstNode,
|
||||
|
|
@ -50,7 +50,7 @@ import { dateMethods } from "../stdlib/date.js"
|
|||
import { mathConstants } from "../stdlib/math.js"
|
||||
import { numberConstants, numberMethods, numberStatics } from "../stdlib/number.js"
|
||||
import { objectMethodsPreservingIdentity } from "../stdlib/object.js"
|
||||
import { promiseStatics, TOOL_CALL_CONCURRENCY } from "../stdlib/promise.js"
|
||||
import { promiseStatics } from "../stdlib/promise.js"
|
||||
import { escapeRegexHint, regexpMethods, regexpProperties, regexFailureReason } from "../stdlib/regexp.js"
|
||||
import { stringMethods, stringStatics } from "../stdlib/string.js"
|
||||
import {
|
||||
|
|
@ -150,7 +150,6 @@ export class Interpreter<R> {
|
|||
private readonly invokeSearch: (args: Array<unknown>) => Effect.Effect<unknown, unknown, R>
|
||||
private readonly toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>
|
||||
private readonly logs: Array<string>
|
||||
private readonly callPermits: Semaphore.Semaphore
|
||||
private readonly promises: PromiseRuntime<R>
|
||||
private readonly runner: CallbackRunner<R> = {
|
||||
invokeFunction: (fn, args) => this.invokeFunction(fn, args),
|
||||
|
|
@ -163,7 +162,6 @@ export class Interpreter<R> {
|
|||
toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>,
|
||||
promises: PromiseRuntime<R>,
|
||||
logs: Array<string> = [],
|
||||
callPermits: Semaphore.Semaphore = Semaphore.makeUnsafe(TOOL_CALL_CONCURRENCY),
|
||||
) {
|
||||
const globalScope = new Map<string, Binding>()
|
||||
this.scopes = new ScopeStack([globalScope])
|
||||
|
|
@ -171,7 +169,6 @@ export class Interpreter<R> {
|
|||
this.invokeSearch = invokeSearch
|
||||
this.toolKeys = toolKeys
|
||||
this.logs = logs
|
||||
this.callPermits = callPermits
|
||||
this.promises = promises
|
||||
globalScope.set("tools", { mutable: false, value: new ToolReference([]) })
|
||||
globalScope.set("search", { mutable: false, value: new SearchFunction() })
|
||||
|
|
@ -239,7 +236,7 @@ export class Interpreter<R> {
|
|||
path: ReadonlyArray<string>,
|
||||
args: Array<unknown>,
|
||||
): Effect.Effect<SandboxPromise, never, R> {
|
||||
return this.createPromise(this.callPermits.withPermit(Effect.suspend(() => this.invokeTool(path, args))))
|
||||
return this.createPromise(Effect.suspend(() => this.invokeTool(path, args)))
|
||||
}
|
||||
|
||||
private createPromise(effect: Effect.Effect<unknown, unknown, R>): Effect.Effect<SandboxPromise, never, R> {
|
||||
|
|
@ -1496,14 +1493,7 @@ export class Interpreter<R> {
|
|||
}
|
||||
|
||||
private invokeFunction(fn: CodeModeFunction, args: Array<unknown>): Effect.Effect<unknown, unknown, R> {
|
||||
const invocation = new Interpreter(
|
||||
this.invokeTool,
|
||||
this.invokeSearch,
|
||||
this.toolKeys,
|
||||
this.promises,
|
||||
this.logs,
|
||||
this.callPermits,
|
||||
)
|
||||
const invocation = new Interpreter(this.invokeTool, this.invokeSearch, this.toolKeys, this.promises, this.logs)
|
||||
invocation.scopes = new ScopeStack([...fn.capturedScopes, new Map()])
|
||||
const run = Effect.gen(function* () {
|
||||
// Seed all parameters first so defaults cannot fall through to same-named outer bindings.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import type { PromiseMethodName } from "../interpreter/model.js"
|
||||
|
||||
export const promiseStatics = new Set<PromiseMethodName>(["all", "allSettled", "race", "any", "resolve", "reject"])
|
||||
|
||||
export const TOOL_CALL_CONCURRENCY = 8
|
||||
|
|
|
|||
|
|
@ -560,7 +560,7 @@ describe("Promise.all over arbitrary arrays", () => {
|
|||
expect(trace.maxActive).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
test("caps live tool-call concurrency at the fixed internal constant (8)", async () => {
|
||||
test("does not cap live tool-call concurrency", async () => {
|
||||
const trace = makeTrace()
|
||||
const result = await value(
|
||||
`
|
||||
|
|
@ -572,8 +572,7 @@ describe("Promise.all over arbitrary arrays", () => {
|
|||
{ trace },
|
||||
)
|
||||
expect(result).toBe(20)
|
||||
expect(trace.maxActive).toBeGreaterThan(1)
|
||||
expect(trace.maxActive).toBeLessThanOrEqual(8)
|
||||
expect(trace.maxActive).toBe(20)
|
||||
})
|
||||
|
||||
test("resolves the empty array", async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue