# Action Coordination

Course: [AI Agents](https://www.devtutorials.ai/learn/ai-agents) · Section: Tool Orchestration
Source: https://www.devtutorials.ai/learn/ai-agents/tools/action-coordination

> How an agent runs actions in the right order, runs independent actions in parallel, avoids clashes over shared files, and checks that everything is done before it finishes.

Think about cooking a big breakfast.

Some jobs must happen in order. You crack the eggs before you fry them. You cannot butter toast until it has been toasted.

Other jobs do not depend on each other. The kettle can boil while the toast is toasting and the eggs are frying. Doing those tasks at the same time saves time.

Some things are shared. There is only one frying pan. If two people try to use it at the same time, the food gets ruined.

And you serve the meal only when everything is ready.

An agent with multiple actions faces the same kinds of decisions.

**The main idea:** An agent coordinates its actions by answering three questions: What must happen first? What can run at the same time? Is everything finished and correct before I report success?

## A Real-World Example

The Brightgrid team has a rule. Before the ticket page can be merged, three checks must pass:

1. **Lint:** the JavaScript follows the style rules.
2. **Tests:** every test passes.
3. **Accessibility (a11y):** every image on the page has alt text so screen readers can describe it.

Right now, two checks fail. The tests fail because large numbers are displayed without commas. The accessibility check fails because the chart image has no alt text.

The agent must fix two files. It must then run the checks again and finish only when all three checks pass in the same run.

**In simple words:** Do tasks in the correct order, run independent tasks together, and verify everything before finishing.

## What You Will Learn

In this lesson, you will learn:

1. How to run actions in order and why dependencies matter.
2. How to run independent actions in parallel.
3. Why shared resources can cause conflicts and how to avoid them.
4. How to check completion, execution order, and preconditions.

The **Agent Lab** includes four scenarios for this lesson: the three checks run one at a time, the same work run in parallel, a write that happens before a read, and a batch that mixes a write with a test run. Open the **Budget** tab to compare the time taken by the serial and parallel runs.

## Sequential Actions

### Ordered Execution

**Ordered execution** means running actions one after another in a fixed sequence. An action starts only after the previous action has finished.

Order is important when one action depends on the result of another. An agent cannot fix a file before reading it. It cannot know whether a fix worked until it runs the tests again.

### Dependency Checks

A **dependency** is a relationship where one action requires another action to happen first. A **dependency check** asks a simple question: does this action already have everything it needs in order to run?

```js runnable title="Find a safe order from the dependencies"
const needs = {
  "run checks": [],
  "read format.js": ["run checks"],
  "read ticket.html": ["run checks"],
  "write format.js": ["read format.js"],
  "write ticket.html": ["read ticket.html"],
  "run checks again": ["write format.js", "write ticket.html"],
};

const done = [];
while (done.length < Object.keys(needs).length) {
  const ready = Object.keys(needs).filter((a) => !done.includes(a) && needs[a].every((d) => done.includes(d)));
  console.log("ready now: " + ready.join(", "));
  done.push(...ready);
}
```

Expected output:

```text
ready now: run checks
ready now: read format.js, read ticket.html
ready now: write format.js, write ticket.html
ready now: run checks again
```

Each line is one **wave**: a group of actions whose dependencies are already complete. You first met waves in Lesson 6. Actions in the same wave do not depend on each other, so they can run at the same time.

This coordinator runs only one action per step. It runs lint, tests, and a11y checks.

Then it reads both files, writes both fixes, and runs all three checks again. The whole process takes 11 steps.

## Parallel Actions

### Concurrent Execution

**Concurrent execution** means running multiple actions at the same time. The agent sends them together as a **batch**. The application runs every action in the batch and then returns all the results in a single observation.

A batch takes as long as its **slowest** action, not the sum of all the actions.

```js runnable title="Serial time versus parallel time"
const timeMs = { lint: 300, run_tests: 800, check_a11y: 400 };
const checks = ["lint", "run_tests", "check_a11y"];

const serial = checks.reduce((sum, c) => sum + timeMs[c], 0);
const parallel = Math.max(...checks.map((c) => timeMs[c]));

console.log("one at a time: " + serial + " ms");
console.log("in parallel: " + parallel + " ms");
console.log("saved: " + (serial - parallel) + " ms");
```

Expected output:

```text
one at a time: 1500 ms
in parallel: 800 ms
saved: 700 ms
```

The tests take 800 ms, so the whole batch takes 800 ms. Lint and a11y finish while the tests are still running.

The same work now takes 5 steps. Step 1 runs all three checks. Step 2 reads both files. Step 3 writes both fixes. Step 4 runs all three checks again. Step 5 finishes.

In the Budget tab, the serial run uses 16.7 s of example time. The parallel run uses 7.9 s. Fewer steps also mean fewer model calls, so it uses fewer tokens as well.

The serial run's Time bar turns red because 16.7 s is above the tab's 15 s example limit. This is not an error in the Lab. It shows how much slower the one-at-a-time approach is.

### Resource Sharing

A **shared resource** is something that multiple actions use, such as a file. Reading a file and checking it at the same time is usually safe because neither action changes anything.

The danger comes from a **write**. If one action changes a file while another action reads it, the reader might see the old content, the new content, or a mixture of both. This is called a **race** because the result depends on which action finishes first.

Because of that, the application has a rule for batches: a write cannot be grouped with any other action that uses the same file. Tools such as `run_tests` read every file, so they can never share a batch with a write.

```js runnable title="Check a batch for conflicts"
const readsAll = ["run_tests", "lint", "check_a11y"];

function conflict(batch) {
  const writes = batch.filter((b) => b.tool === "write_file").map((b) => b.path);
  if (new Set(writes).size < writes.length) return "CONFLICT: two writes to one file";
  if (writes.length && batch.some((b) => readsAll.includes(b.tool))) return "CONFLICT: a check reads a file being written";
  return "ok";
}

console.log(conflict([{ tool: "lint" }, { tool: "run_tests" }]));
console.log(conflict([{ tool: "write_file", path: "web/format.js" }, { tool: "write_file", path: "web/ticket.html" }]));
console.log(conflict([{ tool: "write_file", path: "web/format.js" }, { tool: "run_tests" }]));
console.log(conflict([{ tool: "write_file", path: "web/format.js" }, { tool: "write_file", path: "web/format.js" }]));
```

Expected output:

```text
ok
ok
CONFLICT: a check reads a file being written
CONFLICT: two writes to one file
```

Two checks together are fine. Two writes to different files are fine. A write together with a test run, or two writes to the same file, are rejected.

At step 3, the agent tries to save time by sending a file write and a test run in the same batch.

The application rejects the batch with a clear **CONFLICT** error. At step 4, the agent performs the write first. At step 5, it runs the tests.

Only run actions in parallel when they are truly independent. "It would be faster" is not enough. If one action changes something that another action uses, run them in order.

## Action Validation

Before the agent reports **done**, it performs three checks.

### Completion Checks

A **completion check** asks: did every required action finish successfully?

For the merge rule, that means lint, tests, and a11y must all pass.

They must pass in the **same round**, after the final change. A result from before a write does not count because the write may have introduced a new problem.

### Order Verification

**Order verification** checks that actions happened in a safe and valid order.

For example:

- Every write happened after reading the same file.
- The final validation checks happened after the last write.

```js runnable title="Verify the order of a finished run"
const run = ["checks", "read format.js", "write format.js", "write ticket.html", "checks"];

function verify(steps) {
  const problems = [];
  steps.forEach((s, i) => {
    if (s.startsWith("write ")) {
      const file = s.slice(6);
      if (!steps.slice(0, i).includes("read " + file)) problems.push("wrote " + file + " without reading it");
    }
  });
  const lastWrite = steps.map((s) => s.startsWith("write ")).lastIndexOf(true);
  if (steps.lastIndexOf("checks") < lastWrite) problems.push("no checks after the last write");
  return problems.length ? problems.join("; ") : "order is safe";
}

console.log(verify(run));
console.log(verify(["checks", "read format.js", "write format.js", "checks"]));
```

Expected output:

```text
wrote ticket.html without reading it
order is safe
```

The first run changed `ticket.html` without ever reading it. It may have overwritten content that it never saw.

### Precondition Validation

A **precondition** is something that must be true *before* an action is allowed to run. The check happens when the action is requested, so a bad action is blocked before it can cause problems.

In this lesson, the app has one precondition for `write_file`: the agent must read the file before changing it.

At step 1, the agent is confident that it already knows the fix and tries to write the file immediately. The precondition check rejects the action:

`PRECONDITION FAILED: read web/format.js before you change it`

At step 2, the agent returns to the correct order: test, read, write, test. It completes the task in 6 steps.

```js runnable title="A precondition on write_file"
const readFiles = [];

function writeFile(path) {
  if (!readFiles.includes(path)) return "PRECONDITION FAILED: read " + path + " first";
  return "Saved " + path;
}

console.log(writeFile("web/format.js"));
readFiles.push("web/format.js");
console.log(writeFile("web/format.js"));
```

Expected output:

```text
PRECONDITION FAILED: read web/format.js first
Saved web/format.js
```

**Easy Way to Remember:** **Order** for things that need each other. **Together** for things that don't. **Check all** before you say done.

## Common Mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Everything in order | "One at a time is always safe." | Run independent actions together. | The run takes longer and uses more model calls. |
| Everything in parallel | "Parallel is always faster." | Batch only actions that do not depend on each other. | Results depend on which action wins the race. |
| Write plus check in one batch | "It saves a step." | Write first, then run the check. | The check may read the old file. |
| Old pass counts | "The tests passed earlier." | Re-run every check after the last change. | A later change breaks something without being detected. |
| Write without read | "I already know what is in the file." | Read the file before writing it. | Content the agent never saw can be lost. |

## Exercises

Each exercise ends with a value on the last line. Press **Check** to compare it with the expected answer.

## What to Remember

- **Order** for actions that depend on each other. **Together** for actions that do not depend.
- A parallel batch takes as long as its slowest action.
- Never put a write in the same batch as another action that uses the same file.
- Check preconditions at the moment an action is called, not afterwards.
- Finish only when every required check passes in the same round, after the last change.

Next: **Working Memory**, how an agent keeps track of what it knows while the history grows.
