# Orchestrating missions

A mission is a dependency graph of features grouped into milestones — parallel where it can be, gated where it must be.

## What a mission is

A mission describes a body of work as **milestones**, each containing **features**. A feature names an objective and the agent that will do it; a feature can declare that it depends on others, which is what turns a list into a graph.

The difference from a [loop](/docs/guides/loops) is the shape, not the size. A loop is a pipeline that repeats on a cadence. A mission is a graph that runs once, to completion, with gates between milestones.

## The shape

ts 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

```
await client.mission.upsert({
  name: "search-v2",
  brief: "Replace the current search with a ranked index, without changing the public API.",
  milestones: [
    {
      id: "m1",
      name: "Indexing",
      validation: "scrutiny",
      features: [
        {
          id: "schema",
          name: "Index schema",
          objective: "Design the index schema and write the migration. No query changes yet.",
          agent: "plan",
        },
        {
          id: "writer",
          name: "Index writer",
          objective: "Write documents into the index on every mutation.",
          agent: "build",
          dependsOn: ["schema"],
        },
      ],
    },
  ],
})
```

`brief` is the whole mission's context: every feature is executed with it in scope, so constraints that apply throughout — "without changing the public API" — belong there rather than repeated in each objective.

## Features and dependencies

| Field | Required | What it does |
| --- | --- | --- |
| `id` | yes | What other features name in `dependsOn`. |
| `name` | yes | Human label in the UI and in exec history. |
| `objective` | yes | The prompt for this unit of work. |
| `agent` | yes | Which agent runs it, and so which tools and permissions apply. |
| `model` | no | Overrides the model for this feature. |
| `tokenBudget` | no | Caps this feature's spend. |
| `dependsOn` | no | Feature ids that must finish first. |
| `status` | no | `pending`, `running`, `done`, `blocked`, `skipped`, `error`. |

Features with no unmet dependency are free to run together. Declaring a dependency you do not actually have is the most common way to make a mission needlessly serial.

Depend on outputs, not on order

`dependsOn` should mean "this cannot be written until that exists", not "I would rather this happened first". Two features touching different files usually have no dependency at all, even when a human would do them in order.

## Validation gates

A milestone can declare how it is checked before the next one starts.

| Value | Meaning |
| --- | --- |
| `scrutiny` | The work is reviewed before the milestone is accepted. |
| `user-test` | A human tests it; the mission waits. |
| `none` | Roll straight into the next milestone. |

user-test blocks until someone answers

A mission gated on `user-test` and left unattended is stopped, not failed. In unattended runs use `scrutiny`, and keep `user-test` for the milestones where a person really is going to look.

## Running one

ts 

1

2

3

4

5

6

```
await client.mission.start({ id: mission.id })
await client.mission.pause({ id: mission.id })
await client.mission.cancel({ id: mission.id })

const { data: list } = await client.mission.list()
const { data: one } = await client.mission.get({ id: mission.id })
```

As with loops, `generate` drafts a mission from prose and `templates` returns the built-in starting points.

ts 

```
const { data: draft } = await client.mission.generate({
  description: "Add OAuth login with Google and GitHub, behind a feature flag.",
})
```

## Watching progress

ts 

```
const { data: execs } = await client.mission.execs({ id: mission.id })
const { data: recent } = await client.mission.recentExecs({ limit: 20 })
```

Feature `status` is the thing to render: `blocked` means a dependency has not finished, `skipped` means the mission moved past it, and `error` carries the reason in `error`.

## Intervening mid-run

A mission is editable while it runs. `mission.feature.mutate` changes one feature's status, records an error, or adds a dependency that was not obvious when the graph was written.

ts 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

```
// this turned out to need the schema after all
await client.mission.feature.mutate({
  id: mission.id,
  featureID: "writer",
  appendDependsOn: ["schema"],
})

// we handled this one by hand
await client.mission.feature.mutate({
  id: mission.id,
  featureID: "legacy-shim",
  status: "skipped",
})

// unblock a feature that failed for an external reason
await client.mission.feature.mutate({
  id: mission.id,
  featureID: "writer",
  status: "pending",
  error: undefined,
})
```

This is the escape hatch that makes missions usable

Graphs written up front are wrong in small ways. Being able to add a dependency or skip a feature without tearing the mission down is what keeps a long-running one alive.

## A complete mission

search-v2.ts

ts 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

```
import { createNikcliClient } from "@nikcli-ai/sdk/httpapi"

const client = createNikcliClient({
  baseUrl: "http://127.0.0.1:4096",
  directory: process.cwd(),
})

const { data: mission, error } = await client.mission.upsert({
  name: "search-v2",
  brief:
    "Replace the current substring search with a ranked index. " +
    "The public search API must not change shape; existing callers keep working.",
  milestones: [
    {
      id: "m1",
      name: "Indexing",
      validation: "scrutiny",
      features: [
        {
          id: "schema",
          name: "Index schema",
          objective: "Design the index schema and write the migration. Do not touch query code.",
          agent: "plan",
          tokenBudget: 40_000,
        },
        {
          id: "writer",
          name: "Index writer",
          objective: "Write documents into the index on every mutation, with tests.",
          agent: "build",
          dependsOn: ["schema"],
          tokenBudget: 120_000,
        },
        {
          id: "backfill",
          name: "Backfill",
          objective: "Backfill the index from existing rows. Idempotent and resumable.",
          agent: "build",
          dependsOn: ["schema"],
          tokenBudget: 80_000,
        },
      ],
    },
    {
      id: "m2",
      name: "Querying",
      validation: "user-test",
      features: [
        {
          id: "ranker",
          name: "Ranking",
          objective: "Rank results by relevance. Keep the response shape identical.",
          agent: "build",
          dependsOn: ["writer", "backfill"],
          tokenBudget: 120_000,
        },
      ],
    },
  ],
})
if (!mission) throw error

await client.mission.start({ id: mission.id })
```

`writer` and `backfill` both depend only on `schema`, so they are free to run together once it lands; `ranker` waits for both. That is the whole point of writing it as a graph rather than a list.

## Failure modes

| Symptom | Cause |
| --- | --- |
| Everything runs one at a time | A `dependsOn` chain that encodes preference rather than necessity. |
| Feature stuck on `blocked` | A dependency errored or was never started; check its `status` and `error`. |
| Mission stops and nothing happens | A `user-test` gate waiting on a human. |
| `dependsOn` appears to be ignored | It names a feature `name` instead of its `id`. |
| Features contradict each other | Shared constraints live in the objectives instead of the `brief`. |
| One feature consumes the whole budget | No `tokenBudget` on a broad objective. |

## Related

-   [Missions The mission subsystem itself.](/docs/missions)
-   [Automating with loops The other shape: a pipeline on a cadence.](/docs/guides/loops)
-   [Missions API Every mission endpoint and payload.](/docs/api/mission)
-   [Agents The agents features name.](/docs/agents)

---

Source: https://nikcli-ai.dev/docs/guides/missions
