apache / apache/maka

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

Open
#4,487 1 comment 0 reactions 1 assignee Claimed by @Jarad-z View on GitHub
enhancement
Dominant language
TypeScript
Stars
5.4k
Forks
502
Avg merge
1d 2h
Merged PRs (30d)
715

Description

### Problem

## Current behavior

`AiSdkBackend` settles every client-executed tool call returned by one assistant step with:

```ts
await Promise.allSettled(
returnedToolCalls.map(async toolCall => {
await toolRuntime.settleToolCall(/* ... */)
}),
)
```

`MakaTool.executionSemantics` currently exposes only:

```ts
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:

```ts
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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.