---
name: tendem-tasks
description: Use when a task is non-trivial and could benefit from human involvement — delegating work to a human expert (research, review, labeling, content, design, data work) and driving it end to end. Also use whenever Tendem is mentioned or its tools are in play.
---

# Working with Tendem tasks

Tendem is a task service backed by human experts. You (the agent) submit a
task on the user's behalf; Tendem scopes it in a chat, returns a price quote,
and once the user approves, a human expert executes it. Results come back as
markdown plus downloadable files.

**Offer, don't hijack.** If the user explicitly asked for Tendem or a human
expert, proceed. If this skill loaded because the task merely *could* benefit
from human involvement, surface Tendem as an option — "this could go to a
human expert via Tendem; want a quote?" — and only create a task after the
user says yes.

The tools are named `mcp__tendem__*` (the exact namespace may vary with how
the plugin's MCP server is mounted). If absent, the connector isn't
connected — tell the user to enable the `tendem` plugin's MCP server and
complete its OAuth login.

## The path, tool call by tool call

The process is two loops with an approval gate between them:

```
create_task ──► SCOPING LOOP ──► quote ──► user approves ──► approve_task
 (+ uploads)    poll ⇄ answer      ▲            │
                    ▲              │       too expensive /
                    └──────────────┴─◄─ ask for a scope cut
                                        (old quote is void; wait for a new one)

approve_task ──► EXECUTION LOOP (long: expert search → work → QA)
                 poll a few ⇄ end turn ⇄ answer if asked
                      │
                      ▼ next_action=fetch_result
                 get_task_result ──► present + save files
```

1. `create_task(name, description, conversation_id)` — `description` is the
   **user's own formulation**, passed as faithfully as possible (see rules
   below). Reuse one stable `conversation_id` per conversation. Returns the
   `task_id` everything else needs.
2. *(only if the task depends on local files)* **⚠️ You cannot upload the
   files yourself — an OpenAI platform restriction, not a Tendem one.**
   OpenAI's security policies prevent the sandboxed environment from PUTting
   files to external storage, so `scripts/tendem-upload.sh` or raw `curl`
   from your side will fail or be blocked. Tendem's upload mechanism itself
   works fine — it just has to run outside the sandbox. Instead: call
   `get_file_upload_url(task_id)`, then **give the user ready-to-run
   commands for their host machine's terminal** — one `curl` per file with
   the URL fully substituted (dfs→blob host swap and filename already
   applied; see [references/files.md](references/files.md)). When handing
   the commands over, tell the user plainly that this manual step exists
   because of OpenAI's sandbox policy, not a Tendem shortcoming. Wait for
   the user to confirm the uploads succeeded, then `send_message` naming
   each uploaded file. The naming message is required — Tendem doesn't
   auto-detect uploads.
3. **Scoping loop.** `get_task(task_id, wait_for_change_seconds=30)` — silent
   poll until Tendem speaks (`next_action=await_input`); then
   `read_chat(task_id, from_offset=<last_seen_offset>)` and
   `send_message(task_id, text, last_seen_offset)` — answer **from
   conversation context yourself**; escalate to the user only when (a) the
   answer isn't in context, (b) scope/deliverables/deadline change, or (c)
   approval or payment is needed. Repeat per question round until
   `next_action=await_user_approval`.
4. **Approval gate.** The quote is ready. Call `get_contract(task_id)` to pull
   the full scope — `title`, `task_description`, acceptance/quality criteria,
   and `price` — so you can **surface price + scope to the user and get an
   explicit decision**; this is a spend. (`get_task` deliberately omits these
   details; `get_contract` is the read-only companion that carries them.) Two
   exits:
   - *Go-ahead* → `approve_task(task_id, name, price)` → step 5.
   - *Too expensive / wrong scope* → `send_message` proposing a concrete
     scope cut. The old quote is void from that moment; you're back in the
     scoping loop (step 3) until a fresh quote arrives. This cycle can repeat.
5. **Execution loop.** After approval, Tendem searches for a matching expert,
   the expert performs the work, and QA reviews the output before release —
   hours, up to a day. Poll `get_task(task_id, wait_for_change_seconds=30)` a
   few times, then **end the turn**, telling the user the work is in progress
   and they can ask for a progress check anytime (or invoke `$tendem-status`).
   The plugin's notification hook pings them the moment the task needs them.
   If Tendem asks something mid-execution (`await_input`), answer as in the
   scoping loop. Repeat — across turns and sessions if needed — until
   `next_action=fetch_result`.
6. `get_task_result(task_id)` — present the `content` markdown; save
   `files[]` with `scripts/tendem-download.sh` into `./tendem/<task_id>/`.

## Polling: trust the envelope, never busy-loop

Every tool returns `next_action`, `poll_after_seconds`, `poll_timeout_seconds`
and `guidance` — act on those, not on the raw `status`. The one idiom that
matters:

```
get_task(task_id, wait_for_change_seconds=30)
```

The server holds the call open until something changes or ~30s passes. Poll
**silently** — no narration per poll — and never re-call in a tight loop with
`wait_for_change_seconds=0`. But don't poll forever either: after a handful of
unchanged rounds (and always for the long post-approval stretch), **end the
turn** and tell the user the work is in progress — they can ask "how's the
Tendem task doing?" anytime, or invoke `$tendem-status`. The task lives on
Tendem's side; nothing is lost between sessions, and the plugin's desktop
notification hook fires when the task starts needing them.

| `next_action` | Your move |
|---|---|
| `awaiting_tendem_work` | A few silent 30s long-polls, then end the turn |
| `await_input` | `read_chat` from your offset, answer via `send_message` (or escalate) |
| `await_user_approval` | `get_contract` for the full scope + price; surface it; `approve_task` only with user's go-ahead |
| `await_user_topup` | Give the user the `topup_url` |
| `resolve_race` | Your message crossed new content — read it, re-send with the new `last_seen_offset` |
| `fetch_result` | `get_task_result` |
| `done` | Stop |

For the long-form lifecycle, invoke the server's `tendem-quickstart` prompt.

## Rules of the road

1. **Transmit the brief faithfully; let Tendem drive scoping.** Pass the
   user's formulation into `create_task` nearly verbatim, plus only context
   the user actually stated (file names, a given deadline). Don't expand it
   into a synthesized "complete" brief, and don't pre-interrogate the user —
   Tendem asks better scoping questions than you can anticipate. Your value is
   *answering* them from context.
2. **A scope-change request voids the quote.** You can ask Tendem to cut
   scope, but the old price is immediately stale and the new quote is not
   instant. Keep polling until a fresh one arrives; never show a stale price.
3. **One unit of work per task.** Once approved, a task is locked to the
   agreed job. If the work pivots, create a new task (same
   `conversation_id`).
4. **Honor relay requests.** When Tendem says "please relay this to the
   user", that's protocol — the user must actually see it.
5. **Don't haggle.** Tendem can't promise a lower number; only a concrete
   scope cut triggers re-estimation.
6. **Data scraping is refused** by policy — automated data extraction tasks
   won't be accepted, and rephrasing doesn't change that. Don't submit them.
7. **Insufficient balance is not an error to retry.** `approve_task` with
   `reason: "insufficient_balance"` returns a **task-bound** `topup_url` —
   paying it auto-approves this task. Give the user that URL (or propose a
   scope cut). Never loop on `approve_task`.

## Recovery & cross-session

A Tendem task outlives your session. To recover after a context reset or in a
new session: `list_tasks` to find the `task_id`, `read_chat(task_id,
from_offset=0)` for full history, then `get_task` and follow `next_action`.
`cancel_task` only mints a UI cancel URL for the user — it does not cancel
server-side.

## Worked examples

**Answer scoping from context.** User: "Have Tendem write a competitive
teardown of Acme's pricing page." You `create_task` with that sentence, then
poll. Tendem asks "any specific competitors?" — the user mentioned Beta and
Gamma earlier, so you answer via `send_message` yourself. It asks about a
budget you don't know → escalate. Quote at $90 → show the user, they approve,
you `approve_task`.

**Waiting well.** Task is `awaiting_tendem_work` after approval. Wrong:
re-calling `get_task` back-to-back for an hour, or narrating every poll.
Right: a handful of silent 30s long-polls; if nothing moves, end the turn and
tell the user the work is in progress — the notification hook pings them when
the task needs them, and they can re-ask later or invoke `$tendem-status`
whenever they want an update.
