---
title: Missions and tasks
description: How the kernel turns a goal into a graph of tasks, and what it deliberately refuses to know.
url: https://hermesos.thally.app/concepts/missions-and-tasks
---

# Missions and tasks

How the kernel turns a goal into a graph of tasks, and what it deliberately refuses to know.

A **mission** is a goal you hand to HermesOS. A **task** is one node in the
graph the kernel runs to reach it. Understanding the split explains most of the
runtime's behaviour.

## The kernel runs a graph

The kernel compiles a goal into a task graph and executes it. Tasks move
through states as the mission progresses:

| State | Meaning |
| --- | --- |
| Queued | Ready to run, waiting for a slot |
| Active | Running now |
| Completed | Finished successfully |
| Blocked | Waiting on a dependency |
| Failed | Finished unsuccessfully |

## What the kernel refuses to know

The kernel dispatches tools **by name** and refuses to know what a payload
means. It does not read tool tags, parameter schemas, or capability metadata.

That is a deliberate boundary rather than an omission. Routing layers built
above the kernel — the planner, the agent framework — need to interpret that
metadata. The kernel carries it and never looks at it, which is what keeps
those layers replaceable without a kernel change.

> **Note:**
  `@hermes/tools` exists precisely to close the gap this leaves: a kernel tool
  cannot tell a model what arguments it takes. See [Tools](/concepts/tools).

## Tool context

Every tool call receives a `ToolContext` describing the call it belongs to:

| Field | What it carries |
| --- | --- |
| `missionId` | The mission this call belongs to |
| `taskId` | The task within that mission |
| `taskName` | Human-readable task name |
| `attempt` | `1` on the first try, incrementing on retry |
| `signal` | An `AbortSignal` that aborts when the mission is cancelled |
| `clock` | Injected time — `now()` and `sleep(ms, signal)` |
| `logger` | The structured logger for this call |

Two of these deserve attention when you write a tool.

**`signal`** is real, and your tool should honour it. A long-running tool that
never checks its signal will keep working after the mission has been cancelled.

**`clock`** is injected so tests can control time. Reaching for `Date.now()` or
`setTimeout` directly makes a tool untestable and non-deterministic, which is
the property the rest of the runtime works hard to preserve.

## Retries and recovery

A failed task may be retried, and `ctx.attempt` tells a tool which try it is on.
Whether re-running a step is *safe* is not something the runtime guesses — the
recovery policy refuses to decide it, on the grounds that the caller knows their
own tools.

That is what the `idempotent` flag on a tool is for. It is carried, not read by
the tool framework, and consumed by the layer that has to decide whether to run
a step again.

## Next steps

#### [Tools](/concepts/tools)

    How a tool describes itself to a model.

#### [Permissions](/concepts/permissions)

    What a tool is allowed to do.