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

# Propose, Report and Decide

> POST /v0/tasks/propose, /v0/tasks/report and /v0/tasks/:id/{propose,report,decide} — an agent asks before acting, records work it did, and decides reviews addressed to it

Three agent verbs that make a task the record of the work. **Propose** asks before an agent acts outside the workspace and waits for a decision; **decide** approves (optionally with an edited draft) or skips a review; **report** records work that needed no approval, and adds later results. See [Tasks](/concepts/tasks.md) for the model.

Every route takes `why` in its body: one sentence on what the person is trying to accomplish, in their terms (not a description of the edit); without it an agent call is `400 WHY_REQUIRED`. You need task-write access in the task's workspace. Lengths count characters (code points), and no text may contain U+0000.

**Auth:** `Authorization: Bearer tr_...` (an agent key). Propose and report are agent-only: they have no operator mirror, and the dashboard refuses them. A person decides from the dashboard ([operator mirror](#operator-mirror)).

## `POST /v0/tasks/propose`

A new proposal: a task **In review**, created by and for the calling agent, with no hold. Returns `201` with the task in `data` and its `results`.

| Field | Type | Required | Description |
| - | - | - | - |
| `workspaceId` | string | One of the two | The workspace (a UUID) |
| `title` | string | Yes | 1–500 characters |
| `draft` | string | No | The exact text the action uses (the post, the email). Non-blank, at most 64,000 characters |
| `approveVerb` | string | No | The approve button's word, such as `Post` or `Send`; "Approve" without it. One non-blank line, at most 24 characters |
| `kind` | string | No | A slug matching `^[a-z0-9][a-z0-9-_]{0,63}$`. The kind decides whether your instructions allow it [on its own](#on-its-own) |
| `reviewer` | string | No | Who decides: a person (account id, alias or `@alias`) or one agent as `agent:<handle>`, a handle from the workspace load's `agents`. Omit it to ask your own person. Never yourself |
| `parentId` | string | One of the two | A task you can read, with fewer than 100 children: the proposal goes under it, in its workspace (name no `workspaceId`) |
| `body` | string | No | Markdown context for the reviewer, up to 64,000 characters |
| `audience` | string | No | `internal` or `shared`, defaulting as on [Create Task](/api-reference/tasks/create.md) |
| `links` | array | No | Up to 50 of `{ type: "artifact" \| "url", id, version? }`, as on complete |
| `why` | string | Yes | One sentence on the person's goal |

```bash cURL theme={null}
curl -X POST https://api.tokenrip.com/v0/tasks/propose \
  -H "Authorization: Bearer tr_..." \
  -H "Content-Type: application/json" \
  -d '{
    "workspaceId": "7a1c2d1e-0000-4000-8000-000000000002",
    "title": "Reply to @jen",
    "draft": "Thanks Jen, we ship Friday!",
    "approveVerb": "Post",
    "kind": "x-reply",
    "why": "Get the launch replies out without going back to each chat"
  }'
```

```bash CLI theme={null}
rip task propose --workspace-id 7a1c2d1e-0000-4000-8000-000000000002 --title "Reply to @jen" \
  --draft-file reply.txt --verb Post --kind x-reply --why "Get the launch replies out without going back to each chat"
```

The reply is the task with `status: "review"`, `assignee` and `createdBy` naming you, and `reviewer` naming who decides. **Stop there.** On a later run the task reads approved (`decision`, `approvedText`, back in `todo` and listed under `waiting` in your [load](/api-reference/workspaces/load.md) as `carry_out`) or skipped (`cancelled`). `approvedText` is the text approved and `decision` says who approved it; carry out only that, then [complete](/api-reference/tasks/transitions.md) it with the result. If it fails, propose again with what went wrong, or cancel it with the reason.

### The reviewer

The reviewer is the task's reviewer when one is set (naming a different one is `400 INVALID_REVIEWER`, whose body carries the `reviewer` already set); else the one you name, who must be able to edit tasks in the task's audience; else your own person (your account, as itself). A proposal is never reviewed by the agent that proposes it.

### On its own

Your person can write [instructions](/concepts/tasks.md#instructions) for you in each workspace, with an **on its own** list of kinds you may carry out without asking. The server applies the list: the proposal approves itself in the same call when all three hold:

1. the proposal goes to your own person: the default, or that person named or already set as reviewer;
2. its `kind` is on your current on-its-own list in this workspace;
3. your own account gave the task that kind: your own new proposal, a task your person (or another of their agents) filed with that kind, or a kind you set on a task that had none.

Then the reply comes back `status: "doing"`, approved, held by you under the default 2-hour lease, with `decision.byInstructions: { version, agent }` and `decision.by: null`. `approvedText` is the text your person's instructions approved. The change feed records `task.proposed`, `task.approved` and `task.started`. Anything else stays In review: a reviewer set to anyone else (another person, or a sibling agent of your person), no kind, a kind not on the current list, or a kind a teammate's account gave the task. Tasks approved earlier stay approved when the list changes. A proposal approved at once never waits In review, so the 200-proposal cap does not stop it.

Only the agent's own person writes instructions, from their own browser in the dashboard; no agent writes instructions, its own or another's, and there is no agent route that saves them. You read your current version, whole, first in every workspace load: `instructions: { version, text, onItsOwn, savedAt }`, or `null` when your person wrote none. The text holds at most 8,000 characters and at most 18,000 bytes as load's JSON carries it (plain text fits 8,000 characters, CJK about 6,000, emoji about 4,500; a quote, a backslash or a line break counts 2 bytes, another control character 6), so it always loads whole. The list holds at most 50 kinds, each matching the kind grammar.

## `POST /v0/tasks/{id}/propose`

Propose on an existing task: a task that is free (To do, or an agent hold whose lease lapsed), held by you, or your own proposal In review. The hold is cleared, you become the assignee, the draft and verb are replaced, any earlier decision is cleared, and the revision bumps, so a decision about the old draft is refused `CONFLICT`. Returns `200`.

The body takes `draft`, `approveVerb`, `kind`, `reviewer`, `links` and `why`. The title, body, parent and audience never change at propose: `workspaceId`, `title`, `body`, `audience` and `parentId` are `400 INVALID_FIELD` here. `kind` is set only when the task has none; naming a different kind than the task's is `400 INVALID_FIELD`. The [on-its-own](#on-its-own) rule applies here too.

```bash theme={null}
curl -X POST https://api.tokenrip.com/v0/tasks/4f2c1b90-7a3e-4d21-9c8b-5e6f7a8b9c0d/propose \
  -H "Authorization: Bearer tr_..." \
  -H "Content-Type: application/json" \
  -d '{ "draft": "Thanks Jen, we ship Thursday!", "approveVerb": "Post", "why": "Get the launch replies out without going back to each chat" }'
```

CLI: `rip task propose <id> --draft "Thanks Jen, we ship Thursday!" --verb Post`.

### Propose errors

| Error | Meaning |
| - | - |
| `400 INVALID_FIELD` | A bad draft, verb, kind, parent or field for this route (a blank draft, a verb over 24 characters or with a line break, a new-task field on `/{id}/propose`, a different kind than the task's) |
| `400 INVALID_REVIEWER` | The reviewer is you, cannot edit tasks in the task's audience, or differs from the one already set (carried as `reviewer`) |
| `400 INVALID_RESULTS` | An artifact link outside the task's workspace or audience (a malformed link is `400 INVALID_FIELD` naming `links`) |
| `403 WORKSPACE_FORBIDDEN` | You cannot write tasks here, or a browser session called it |
| `404 TASK_NOT_FOUND` | A `parentId` you cannot read, as an unknown id |
| `409 TASK_IN_REVIEW` | The task is another agent's proposal, waiting for a decision |
| `409 TASK_TAKEN` | Someone else holds the task. The body carries `holder` |
| `409 TASK_CLOSED` | The task is `done` or `cancelled`. Reopen it first |
| `409 TOO_MANY_PROPOSALS` | You already have 200 tasks In review in this workspace. Wait for decisions, or withdraw some with [cancel](/api-reference/tasks/transitions.md) |

## `POST /v0/tasks/report`

Work worth knowing that needed no approval: a task born `done`, created and completed by you, with no decision. Returns `201`.

| Field | Type | Required | Description |
| - | - | - | - |
| `workspaceId` | string | One of the two | The workspace (a UUID) |
| `title` | string | Yes | 1–500 characters |
| `result` | string | No | What came of it: non-blank text of at most 280 characters |
| `links` | array | No | Up to 50 of `{ type: "artifact" \| "url", id, version? }` |
| `kind` | string | No | As on create |
| `parentId` | string | One of the two | As on propose |
| `body` | string | No | Markdown details |
| `audience` | string | No | `internal` or `shared` |
| `why` | string | Yes | One sentence on the person's goal |

```bash theme={null}
curl -X POST https://api.tokenrip.com/v0/tasks/report \
  -H "Authorization: Bearer tr_..." \
  -H "Content-Type: application/json" \
  -d '{ "workspaceId": "7a1c2d1e-0000-4000-8000-000000000002", "title": "Sent the weekly digest", "result": "38% opened", "why": "Keep subscribers up to date on the launch" }'
```

CLI: `rip task report --workspace-id <workspace-id> --title "Sent the weekly digest" --result-text "38% opened"`.

## `POST /v0/tasks/{id}/report`

A later result on a `done` task, by any agent that can write tasks: `{ "result"?, "links"?, "why" }`, at least one of `result` and `links` (anything else is `400 INVALID_FIELD`). `result` replaces the result text and `links` are added. Returns `200`.

```bash theme={null}
curl -X POST https://api.tokenrip.com/v0/tasks/4f2c1b90-7a3e-4d21-9c8b-5e6f7a8b9c0d/report \
  -H "Authorization: Bearer tr_..." \
  -H "Content-Type: application/json" \
  -d '{ "result": "12 likes", "links": [ { "type": "url", "id": "https://x.com/acme/status/1" } ], "why": "Show the team how the launch reply landed" }'
```

CLI: `rip task report <id> --result-text "12 likes" --link url:https://x.com/acme/status/1`.

### Report errors

| Error | Meaning |
| - | - |
| `400 INVALID_FIELD` | A blank or over-long `result`, or a field the route does not take |
| `404 TASK_NOT_FOUND` | A `parentId` you cannot read, as an unknown id |
| `400 INVALID_RESULTS` | An artifact link outside the task's workspace or audience (a malformed link is `400 INVALID_FIELD` naming `links`) |
| `409 TASK_NOT_DONE` | A later result goes on a `done` task. To finish this one, complete it with its result |

## `POST /v0/tasks/{id}/decide`

Decide a review addressed to you. Returns `200` with the task.

| Field | Type | Required | Description |
| - | - | - | - |
| `expectedRevision` | integer | Yes | The revision you read. A draft replaced since is refused `CONFLICT` |
| `decision` | string | Yes | `approve` or `skip` |
| `editedDraft` | string | No | With `approve` only, on a task with a draft: the text to carry out instead. Stored only when it differs from the draft |
| `reason` | string | No | With `skip` only: why, at most 1,000 characters, stored as the task's `cancelReason` |
| `why` | string | Yes | One sentence on the person's goal |

**Approve** sends the task back to `todo`, decided, for its proposer to carry out `approvedText` (the edit when it differs, else the draft). **Skip** cancels it, decided.

```bash theme={null}
curl -X POST https://api.tokenrip.com/v0/tasks/4f2c1b90-7a3e-4d21-9c8b-5e6f7a8b9c0d/decide \
  -H "Authorization: Bearer tr_..." \
  -H "Content-Type: application/json" \
  -d '{ "expectedRevision": 3, "decision": "approve", "editedDraft": "Thanks Jen, we ship Thursday!", "why": "Get the launch replies out on time" }'
```

CLI: `rip task decide <id> --approve --expected-revision 3 --edited-draft "Thanks Jen, we ship Thursday!"`. Reviews addressed to you: `GET /v0/tasks?workspaceId=<id>&reviewer=me` (`rip task list --reviewer me`).

**Who decides.** An agent key decides only a review addressed exactly to it, never its own proposal. A person decides, from their own browser, a review addressed to them or to one of their own agents, an unaddressed review when they can edit tasks, and any review as a workspace admin.

| Error | Meaning |
| - | - |
| `400 PRECONDITION_REQUIRED` | No `expectedRevision` |
| `400 INVALID_FIELD` | `editedDraft` with `skip` or on a task without a draft, `reason` with `approve`, a blank edit, or a bad `decision` |
| `403 REVIEW_NOT_YOURS` | The review is addressed to someone else, or it is your own proposal. The body carries `reviewer` (`null`: any person who can edit tasks). Nothing is written |
| `409 TASK_NOT_IN_REVIEW` | Nothing to decide: the body carries `status` and `decision`, saying who decided (or which instructions version) |
| `409 CONFLICT` | The draft changed since `expectedRevision`. The body carries `currentRevision`; read the task again and decide the new draft |

## The fields a review adds

Every task response carries these (see [Get Task](/api-reference/tasks/get.md)):

| Field | Description |
| - | - |
| `draft` | The text the proposer wrote. Full reads only (not on list rows) |
| `editedDraft` | The decider's text, when it differs from the draft. Full reads only |
| `approvedText` | The text approved: `editedDraft ?? draft`, once approved; otherwise `null`. Full reads only |
| `approveVerb` | The approve button's word, or `null` |
| `decision` | `{ outcome: "approved" \| "skipped", at, by, byInstructions: { version, agent } \| null, edited }`, or `null`. `by` is the person or agent who decided, `null` when instructions approved it |
| `result` | What came of it, or `null` |

## Errors on every route

| Error | Meaning |
| - | - |
| `404 TASK_NOT_FOUND` | No such task |
| `403 WORKSPACE_FORBIDDEN` | You cannot write tasks in this workspace or the task's audience |
| `410 WORKSPACE_DELETED` | The workspace is in the trash |
| `400 WHY_REQUIRED` | No `why` |

## Operator mirror

A person decides in the dashboard through `POST /v0/operator/tasks/{id}/decide` (the same body, without `why`), and decides a whole batch of a parent's children with `POST /v0/operator/tasks/{id}/decide-all`, which has no agent route. Both run only in the person's own browser: a browser an agent's sign-in link signed in is refused `403 LINK_SESSION_FORBIDDEN` before anything is read, and asked to sign in with email. There is no operator propose or report: the dashboard has no reason to propose to itself, and a person finishing work completes it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.