> ## Documentation Index
> Fetch the complete documentation index at: https://opentrain.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Project To-dos

> Safely create, assign, and track job actions with explicit audiences, no-write preflights, idempotency, and normalized results.

Project To-dos are the required actions an employer assigns to hired AI trainers on one job: acknowledgements, external training, native forms and quizzes, and work submitted for employer review. The developer surface manages the same list the employer web app shows — there is no separate agent-only store.

Everything below applies identically to the [HTTP API](/docs/developers/api-reference/project-todos/overview), the [CLI](/docs/developers/cli/project-todos), and the [MCP tools](/docs/developers/mcp/project-todos).

## The data model

* An **item** is one logical to-do on a job, organized under an optional category.
* Every edit publishes a new immutable **version**. History is never rewritten.
* An **assignment** is one person's obligation for one item version, with an append-only event and evidence trail.
* Quizzes are to-do items pinned to one immutable published form version.

## Audiences are always explicit

Every create, assign, or reissue names its audience. There is no default: omitting `audience` fails with `400` instead of assigning anyone.

| Audience            | Current people                 | Future hires          |
| ------------------- | ------------------------------ | --------------------- |
| `ACTIVE`            | All active contracts now       | No                    |
| `FUTURE`            | Nobody now                     | Auto-assigned at hire |
| `ACTIVE_AND_FUTURE` | All active contracts now       | Auto-assigned at hire |
| `SELECTED`          | Exactly the contracts you list | Never                 |

`SELECTED` fails closed. The list must be non-empty and duplicate-free, and every ID must be an active contract of that exact job. An unknown, ended, or foreign ID rejects the whole request with each offender named — nothing is silently dropped, and `SELECTED` can never turn on future-hire auto-assignment.

## Preflight receipts for broad assignments

`ACTIVE`, `FUTURE`, and `ACTIVE_AND_FUTURE` fan out beyond an explicit list, so they require a two-step confirmation:

1. Call the no-write **preflight** with the exact request you intend to send. It returns the live recipients (masked names, contract IDs, and counts), future-hire semantics, projected due and blocking behavior, duplicate candidates, and a signed `receipt`.
2. Send the mutation unchanged with `preflightReceipt` set to that token.

The receipt is signed and short-lived (about 15 minutes) and is bound to your token's account, the job, the action, the audience, the exact request payload, and the live roster. If anything changes — the payload, the roster, the audience — the mutation fails with `409` and a `PREFLIGHT_RECEIPT_*` reason, and you preflight again. Roster changes that land between the receipt check and the write itself also fail closed with `409 RECIPIENTS_CHANGED`.

`SELECTED` does not need a receipt: the explicit ID list is already the exact confirmation.

## Idempotency is bound to the whole request

Every mutation requires an `idempotencyKey` (8–200 characters). For item creation the key is bound to the complete request — definition, audience, selected contracts, placement, and future-hire semantics:

* The same key with the same request **replays**: you get the committed result with `replayed: true`, and no new assignments are created. A replay never re-resolves the roster, so a person hired after the original create is not silently added.
* The same key with a changed request fails with `409` — a reused key can never widen a fan-out.
* A different key with an identical active request fails with `409 LIKELY_DUPLICATE` unless you explicitly pass `allowDuplicate: true`.

Item creation and its assignment fan-out commit in one transaction: you never get an item without its assignments or a partial fan-out.

<Warning>
  `update_item` publishes a new immutable version on every call and is not retry-convergent. Do not blind-retry it; verify the result with a read instead.
</Warning>

## Normalized results

Mutations return an explicit, machine-readable outcome: the item and version IDs, the audience echo with future-hire semantics, a fan-out summary (`matchedContracts`, `createdAssignments`, `assignmentIds`), the masked recipients the request was verified against, a `replayed` flag, and `warnings`. Archiving returns the exact number of open assignments it closed, and that archiving is not reversible through this surface while history is retained.

## Privacy

Agent responses identify people by contract ID, user ID, and a masked display name (first name plus last initial). Personal emails never appear in list responses, and the CSV export serves the `worker_email` column blank.

## Permissions

Reads require `project_todos:read`; every mutation requires `project_todos:write`, employer access to the job, and a claimed account. Both scopes are available on fine-grained keys and included in full-access keys. The account must also have the default-off `project_todos_integrations` agent-surface family enabled; a full-access key does not bypass that separate gate. See [Scopes and capabilities](/docs/developers/concepts/scopes-and-capabilities).

## Where to go next

* [API reference](/docs/developers/api-reference/project-todos/overview) — endpoints, actions, and errors.
* [Quiz surface](/docs/developers/api-reference/project-todos/quizzes/overview) — authoring, assigning, grading, and retakes.
* [CLI](/docs/developers/cli/project-todos) — preview-first `opentrain todos` commands.
* [MCP tools](/docs/developers/mcp/project-todos) — the same safety contract for MCP agents.
