Skip to content

feat(runtime): make local tool-batch scheduling resource-aware #4487

Description

@Jarad-z

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:

  1. Validate arguments and derive the resource set before dispatch.
  2. Acquire multiple resource keys in a deterministic order.
  3. Run non-conflicting calls concurrently.
  4. Queue conflicting ordinary calls in provider-returned tool-call order.
  5. Keep the current exclusive_step refusal semantics for permission, interaction, and control-plane boundaries.
  6. Wait for the whole admitted batch to settle before continuing the model loop.
  7. 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:

  • Calls that write the same resource never execute concurrently.
  • Reads and writes of the same declared resource have deterministic ordering.
  • Calls using different resources still run concurrently.
  • Existing filesystem and Runtime Host queues are reused or migrated without creating two competing lock authorities.
  • Same-session Todo/Goal/Plan mutations have deterministic order.
  • User interaction, permission requests, and plan submission cannot share an assistant step with side effects.
  • Stateful UI mutations targeting the same state authority are serialized.
  • MCP/web/provider and agent fan-out have explicit capacity limits.
  • Multi-resource acquisition is deadlock-safe.
  • Abort and failure release permits/keys without wedging later calls.
  • Tests cover reversed call order, same-key conflicts, different-key parallelism, multi-key conflicts, failure, and cancellation.

Open design questions:

  1. Should every ordinary conflict queue in the current batch, or can selected tools reject and require a later model step?
  2. Should workspace scans (Glob/Grep) get snapshot semantics or remain weakly consistent with directory mutations?
  3. What trusted declaration, if any, may narrow Bash below workspace scope?
  4. Which MCP annotations are useful as hints, and which composition layer must convert them into trusted scheduling facts?
  5. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions