---
url: >-
  https://adk.nht.io/api/batteries/orchestration/executor/functions/executePlan.md
---

# Function: executePlan()

```ts
function executePlan(
  store: PlanStore,
  planId: string,
  options: RunOptions,
): Promise<RunProjection>;
```

Defined in: [src/batteries/orchestration/executor.ts:157](https://github.com/NHTIO/ADK/blob/v1.20260906.0/src/src/batteries/orchestration/executor.ts#L157)

Execute a plan against external input and return the projection folded from the durable event
log. External input is materialised as the entry frame's `node_settled` before any other node
runs, so it is addressable by `NodeRef` exactly like any other node output and is rebuilt from
events on resume.

## Parameters

| Parameter | Type                                                                                          | Description                              |
| --------- | --------------------------------------------------------------------------------------------- | ---------------------------------------- |
| `store`   | [`PlanStore`](../../../../@nhtio/adk/batteries/orchestration/interfaces/PlanStore.md)         | The plan store backing the run.          |
| `planId`  | `string`                                                                                      | The id of the plan to execute.           |
| `options` | [`RunOptions`](../../../../@nhtio/adk/batteries/orchestration/types/interfaces/RunOptions.md) | Per-run input and override dependencies. |

## Returns

`Promise`<[`RunProjection`](../../../../@nhtio/adk/batteries/orchestration/types/interfaces/RunProjection.md)>

The [RunProjection](../../../../@nhtio/adk/batteries/orchestration/types/interfaces/RunProjection.md) folded from the run's event log.

## Remarks

**ORDER OF OPERATIONS, because it is observable and load-bearing.** Everything that can refuse
a request happens BEFORE `claimRun`: the plan is read and folded, the entry node is located,
and `RunOptions.input` is validated against its `DeclaredField[]`. Only then is the run claimed.

That ordering is not an optimisation. `claimRun` is irreversible by design — a plan admits one
run EVER and the store exposes no release — so a check that ran after it would burn the plan's
only run on a request that never invoked a single tool, leaving it permanently
`run_already_claimed` with `clonePlan` the only recovery. A rejected request must cost the plan
nothing.

So a caller can rely on this: **if this function throws on invalid input, the plan is still
runnable.** Fix the input and call again.

The budget is the PLAN'S `bounds.maxSteps`, not a library constant — bounds are plan content,
digested and approved by the operator. Exhausting it settles the run `halted` with
`budget_exhausted{settled}`, never `process_death`: the executor knows why it stopped, and a
resume re-reports the same cause rather than looping, because the bound has not changed.

## Throws

If `RunOptions.input` violates the entry node's declared fields — before any claim.
