Problem
Current behavior
AiSdkBackend settles every client-executed tool call returned by one assistant step with:
await Promise.allSettled(
returnedToolCalls.map(async toolCall => {
await toolRuntime.settleToolCall(/* ... */)
}),
)
MakaTool.executionSemantics currently exposes only:
executionSemantics?: "parallel" | "exclusive_step"
exclusive_step is useful for causal boundaries: it refuses sibling calls and asks the model to retry them in a later step. It does not queue ordinary calls that conflict on a shared resource. Everything else is admitted as parallel.
A batch has no tool-result data dependency—the model emitted every call before seeing any result—but that does not imply resource independence. Examples include:
Read(a) with Edit(a) or apply_patch(a)
Bash("npm install") with a file tool touching package-lock.json
- two full-replacement
todo_write calls for one session
update_plan(...) with cancel_plan(...)
AskUserQuestion with a side-effecting sibling call
- two stateful UI actions targeting one computer/browser session
- two mutating or stateful MCP calls sharing one server/session
Existing partial coordination
Maka already has good resource-specific precedents:
filesystem-executor.ts serializes Write, Edit, FormatJson, and apply_patch by a canonical target key through withFileWriteLock.
runtime-resource-coordinator.ts serializes PTY control operations by (sessionId, ref) while allowing different resources to proceed independently.
- several subsystems use admission gates, operation IDs, CAS checks, or observation leases.
Those mechanisms solve important local cases, but they are not a tool-batch scheduling contract. The resource identity is hidden downstream, so ToolRuntime cannot coordinate across tool families. The file lock explicitly cannot cover Bash; reads do not participate in its write queue; Todo/Goal/Plan tools do not declare a session resource; AskUserQuestion and SubmitPlan are direct_only but not exclusive_step; MCP tools expose no trusted server capacity or mutation resource through MakaTool.
The result is fragmented safety: some conflicts are serialized, some are detected only after dispatch, and others are left to the implementation or remote service. Adding another ad-hoc lock for each new tool also risks duplicate authorities and inconsistent ordering.
Scope
This is about local/client-executed tool batches. Provider-executed hosted tools have their own provider-side execution contract, but any local continuation or proxy tool should still enter the same admission model.
Relevant code:
packages/runtime/src/ai-sdk-backend.ts
packages/runtime/src/tool-runtime.ts
packages/runtime/src/filesystem-executor.ts
packages/runtime/src/file-write-lock.ts
packages/runtime/src/session-todo-tools.ts
packages/runtime/src/goal-tools.ts
packages/runtime/src/plan-tools.ts
packages/runtime/src/ask-user-question-tool.ts
packages/runtime/src/computer-use-tools.ts
packages/runtime/src/mcp-tools.ts
packages/runtime-host/src/server/runtime-resource-coordinator.ts
Desired outcome
Introduce one Runtime-owned execution contract that can express both bounded parallelism and resource-level conflicts, while preserving exclusive_step for true assistant-step boundaries.
A possible shape is:
type ToolExecutionSemantics =
| {
mode: "parallel"
maxConcurrency?: number
}
| {
mode: "exclusive_step"
}
| {
mode: "keyed"
resources: (
args: unknown,
ctx: ToolContext,
) => Array<{
key: string
access: "read" | "write"
}>
maxConcurrency?: number
}
Expected scheduling flow:
- Validate arguments and derive the resource set before dispatch.
- Acquire multiple resource keys in a deterministic order.
- Run non-conflicting calls concurrently.
- Queue conflicting ordinary calls in provider-returned tool-call order.
- Keep the current
exclusive_step refusal semantics for permission, interaction, and control-plane boundaries.
- Wait for the whole admitted batch to settle before continuing the model loop.
- Make abort, failure, queue wait, and admission refusal distinct in tracing/results.
Initial mappings could be:
- filesystem reads: read access on a canonical path
- filesystem mutations: write access on every affected canonical path
- opaque Bash: workspace write/exclusive by default, with a future trusted declaration for narrower resources
- Todo/Goal/Plan mutations: write access on the session or execution key
- user interaction and plan submission:
exclusive_step
- computer/browser mutations: write access on a computer session, window, browser session, or tab
- MCP read-only tools: bounded by server capacity; unknown/mutating tools conservatively serialized by server/session/resource
- web/provider calls and agent spawning: bounded parallel capacity without global serialization
Acceptance criteria:
Open design questions:
- Should every ordinary conflict queue in the current batch, or can selected tools reject and require a later model step?
- Should workspace scans (
Glob/Grep) get snapshot semantics or remain weakly consistent with directory mutations?
- What trusted declaration, if any, may narrow Bash below workspace scope?
- Which MCP annotations are useful as hints, and which composition layer must convert them into trusted scheduling facts?
- Should the scheduler live directly in
ToolRuntime or as a shared Runtime Host coordination service?
Alternatives or workarounds
- Serialize every tool globally. Safe, but it unnecessarily removes parallel reads, independent searches, separate terminal resources, and bounded agent fan-out.
- Keep adding downstream locks. This is the current workaround and already protects file writes and PTY control, but it cannot coordinate cross-tool conflicts such as Bash versus file tools and creates fragmented ordering authorities.
- Mark every stateful tool
exclusive_step. This prevents overlap by refusing sibling calls, but burns additional model turns and cannot preserve parallelism across unrelated resources.
- Rely on prompts/tool descriptions. The model may avoid obvious conflicts, but this is not an enforceable Runtime invariant and does not cover third-party providers or MCP tools.
The incremental path can start by making AskUserQuestion/SubmitPlan exclusive, adding session keys for state mutations, and lifting the existing filesystem/PTy keying patterns into the common contract before handling opaque Bash and MCP policy.
Problem
Current behavior
AiSdkBackendsettles every client-executed tool call returned by one assistant step with:MakaTool.executionSemanticscurrently exposes only:exclusive_stepis useful for causal boundaries: it refuses sibling calls and asks the model to retry them in a later step. It does not queue ordinary calls that conflict on a shared resource. Everything else is admitted as parallel.A batch has no tool-result data dependency—the model emitted every call before seeing any result—but that does not imply resource independence. Examples include:
Read(a)withEdit(a)orapply_patch(a)Bash("npm install")with a file tool touchingpackage-lock.jsontodo_writecalls for one sessionupdate_plan(...)withcancel_plan(...)AskUserQuestionwith a side-effecting sibling callExisting partial coordination
Maka already has good resource-specific precedents:
filesystem-executor.tsserializesWrite,Edit,FormatJson, andapply_patchby a canonical target key throughwithFileWriteLock.runtime-resource-coordinator.tsserializes PTY control operations by(sessionId, ref)while allowing different resources to proceed independently.Those mechanisms solve important local cases, but they are not a tool-batch scheduling contract. The resource identity is hidden downstream, so ToolRuntime cannot coordinate across tool families. The file lock explicitly cannot cover Bash; reads do not participate in its write queue; Todo/Goal/Plan tools do not declare a session resource;
AskUserQuestionandSubmitPlanaredirect_onlybut notexclusive_step; MCP tools expose no trusted server capacity or mutation resource throughMakaTool.The result is fragmented safety: some conflicts are serialized, some are detected only after dispatch, and others are left to the implementation or remote service. Adding another ad-hoc lock for each new tool also risks duplicate authorities and inconsistent ordering.
Scope
This is about local/client-executed tool batches. Provider-executed hosted tools have their own provider-side execution contract, but any local continuation or proxy tool should still enter the same admission model.
Relevant code:
packages/runtime/src/ai-sdk-backend.tspackages/runtime/src/tool-runtime.tspackages/runtime/src/filesystem-executor.tspackages/runtime/src/file-write-lock.tspackages/runtime/src/session-todo-tools.tspackages/runtime/src/goal-tools.tspackages/runtime/src/plan-tools.tspackages/runtime/src/ask-user-question-tool.tspackages/runtime/src/computer-use-tools.tspackages/runtime/src/mcp-tools.tspackages/runtime-host/src/server/runtime-resource-coordinator.tsDesired outcome
Introduce one Runtime-owned execution contract that can express both bounded parallelism and resource-level conflicts, while preserving
exclusive_stepfor true assistant-step boundaries.A possible shape is:
Expected scheduling flow:
exclusive_steprefusal semantics for permission, interaction, and control-plane boundaries.Initial mappings could be:
exclusive_stepAcceptance criteria:
Open design questions:
Glob/Grep) get snapshot semantics or remain weakly consistent with directory mutations?ToolRuntimeor as a shared Runtime Host coordination service?Alternatives or workarounds
exclusive_step. This prevents overlap by refusing sibling calls, but burns additional model turns and cannot preserve parallelism across unrelated resources.The incremental path can start by making
AskUserQuestion/SubmitPlanexclusive, adding session keys for state mutations, and lifting the existing filesystem/PTy keying patterns into the common contract before handling opaque Bash and MCP policy.