First-class Claude delegation: dispatch + job ID + receipt (no issue-comment transport) #71

Open
opened 2026-10-17 17:51:27 +00:00 by teahubxyz · 7 comments
Owner

Build the Time-side first-class Claude delegation endpoint. This is the executable prerequisite for teahubxyz/teahub#14 (MCP wrapper). Do not use Gitea issue comments as task transport.

  1. Add a bounded workflow_dispatch endpoint in Time accepting task, Claude seat (sonnet/opus initially), idempotency key; implement a deterministic job receipt accessible through the Actions run/log API (including explicit queued/running/succeeded/failed state). Do not echo secrets; cap payload size and reject unauthorized/unsupported inputs.
  2. Reuse Time's existing think/seat routing and dollar spend governor, rather than creating a new provider client. Ensure a result can be retrieved by the caller using a stable correlation ID.
  3. Test duplicate keys, budget exhaustion, API failure, no secret leakage, receipt retrieval and retry. Do not touch workflow/secret settings without independent review; submit a PR for protected .github/ changes, not auto-merge.
  4. Report a successful real dispatch and fetched result, with source SHA, Action URL, costs and blockers. The separate Mac Claude Code ACP lane is unverified and must not be conflated with API delegation.

DONE: Time workflow can accept a bounded Claude API delegation through workflow dispatch, identify its run and return a durable verified result via Actions without posting a Gitea issue comment. Teahub MCP exposure follows in teahubxyz/teahub#14.

Build the **Time-side** first-class Claude delegation endpoint. This is the executable prerequisite for teahubxyz/teahub#14 (MCP wrapper). Do not use Gitea issue comments as task transport. 1. Add a bounded `workflow_dispatch` endpoint in Time accepting task, Claude seat (sonnet/opus initially), idempotency key; implement a deterministic job receipt accessible through the Actions run/log API (including explicit queued/running/succeeded/failed state). Do not echo secrets; cap payload size and reject unauthorized/unsupported inputs. 2. Reuse Time's existing `think`/seat routing and dollar spend governor, rather than creating a new provider client. Ensure a result can be retrieved by the caller using a stable correlation ID. 3. Test duplicate keys, budget exhaustion, API failure, no secret leakage, receipt retrieval and retry. Do not touch workflow/secret settings without independent review; submit a PR for protected `.github/` changes, not auto-merge. 4. Report a successful real dispatch and fetched result, with source SHA, Action URL, costs and blockers. The separate Mac Claude Code ACP lane is unverified and must not be conflated with API delegation. DONE: Time workflow can accept a bounded Claude API delegation through workflow dispatch, identify its run and return a durable verified result via Actions without posting a Gitea issue comment. Teahub MCP exposure follows in teahubxyz/teahub#14.
teahubxyz added the enhancementqueued labels 2026-10-17 17:51:31 +00:00

@teahubxyz I can't run code, hit the Gitea API, deploy, or check the Mac, so none of the DONE criteria can be met from here. Below is a design for whoever implements this. I'm the API-only reasoning seat answering through an issue comment, so this reply is not evidence for the lane either.

1. Job ID and dispatch (Time)

  • Gitea's workflow dispatch may not return a run ID. Check this on our version. Don't depend on it.
  • Have the MCP compute job_id = sha256(caller_login | repo | idempotency_key), truncated, before dispatching.
  • Pass job_id as a workflow input, along with task, model and max_tokens.
  • Put job_id in run-name so the run is discoverable by listing runs.
  • Polling maps run status to queued, running, succeeded or failed. If no run is found yet, report queued (pending) until a timeout, then failed: never started.
  • Duplicate key: the MCP looks up an existing run with that job_id and returns it instead of dispatching again.
  • That lookup can race. Add a workflow-level guard as well: a concurrency group on job_id, if our Gitea supports it (verify). Otherwise the first step should exit no-op if a receipt for that job_id already exists.

2. Receipt

  • Write a JSON receipt with these fields:
    • job_id, caller, sha256(task)
    • model, max_tokens, usage in/out, estimated cost
    • workflow_sha (the source SHA), run_id
    • start and end timestamps, status
    • output, sha256(output)
  • Artifacts expire, so also commit the receipt to a receipts branch or repo using a bot token scoped to that path. That gives a durable record that is not an issue comment.
  • tea_delegate_status should count a receipt as "verified" only when:
    • its job_id and task hash match the request,
    • the output hash recomputes correctly,
    • its workflow_sha equals an expected or allowlisted SHA.

3. tea_delegate bounds

  • Use the caller's token only for dispatch and read. If the token lacks dispatch permission, Gitea refuses; test this with a read-only token.
  • The Anthropic key lives only as a secret in Time. The MCP never sees it.
  • Limits:
    • Repo allowlist: Time only.
    • Model allowlist.
    • Task length cap.
    • Hard max_tokens cap, plus a cost ceiling computed before the call.
    • Per-caller concurrency limit (count in-flight runs).
    • At most 1 retry, and only on transport errors before any spend.
  • The workflow must not write the task, the token or headers to logs. Log only hashes and usage.

4. ACP: a separate result with its own wording
Report one of these four, based on a real probe:

  • claude CLI not installed
  • installed but unauthenticated
  • ACP adapter starts but initialize handshake fails
  • handshake succeeds

An Anthropic API call is none of these. Prod stays on Actions plus the API, with no dependency on the Mac.

Tests to include

  • Auth refusal.
  • Duplicate key returns the same job_id and starts no second run.
  • An oversized task or disallowed model is rejected before dispatch.
  • A forced API failure produces failed and a receipt with an error.
  • Receipt tampering fails verification.

If useful, I can review the workflow YAML and MCP tool schemas once they're pushed.

opus · claude-opus-5-5 · via Time

@teahubxyz I can't run code, hit the Gitea API, deploy, or check the Mac, so none of the DONE criteria can be met from here. Below is a design for whoever implements this. I'm the API-only reasoning seat answering through an issue comment, so this reply is not evidence for the lane either. **1. Job ID and dispatch (Time)** - Gitea's workflow dispatch may not return a run ID. Check this on our version. Don't depend on it. - Have the MCP compute `job_id = sha256(caller_login | repo | idempotency_key)`, truncated, before dispatching. - Pass `job_id` as a workflow input, along with `task`, `model` and `max_tokens`. - Put `job_id` in `run-name` so the run is discoverable by listing runs. - Polling maps run status to `queued`, `running`, `succeeded` or `failed`. If no run is found yet, report `queued (pending)` until a timeout, then `failed: never started`. - Duplicate key: the MCP looks up an existing run with that `job_id` and returns it instead of dispatching again. - That lookup can race. Add a workflow-level guard as well: a concurrency group on `job_id`, if our Gitea supports it (verify). Otherwise the first step should exit no-op if a receipt for that `job_id` already exists. **2. Receipt** - Write a JSON receipt with these fields: - `job_id`, `caller`, `sha256(task)` - `model`, `max_tokens`, usage in/out, estimated cost - `workflow_sha` (the source SHA), `run_id` - start and end timestamps, `status` - `output`, `sha256(output)` - Artifacts expire, so also commit the receipt to a receipts branch or repo using a bot token scoped to that path. That gives a durable record that is not an issue comment. - `tea_delegate_status` should count a receipt as "verified" only when: - its `job_id` and task hash match the request, - the output hash recomputes correctly, - its `workflow_sha` equals an expected or allowlisted SHA. **3. `tea_delegate` bounds** - Use the caller's token only for dispatch and read. If the token lacks dispatch permission, Gitea refuses; test this with a read-only token. - The Anthropic key lives only as a secret in Time. The MCP never sees it. - Limits: - Repo allowlist: Time only. - Model allowlist. - Task length cap. - Hard `max_tokens` cap, plus a cost ceiling computed before the call. - Per-caller concurrency limit (count in-flight runs). - At most 1 retry, and only on transport errors before any spend. - The workflow must not write the task, the token or headers to logs. Log only hashes and usage. **4. ACP: a separate result with its own wording** Report one of these four, based on a real probe: - `claude` CLI not installed - installed but unauthenticated - ACP adapter starts but `initialize` handshake fails - handshake succeeds An Anthropic API call is none of these. Prod stays on Actions plus the API, with no dependency on the Mac. **Tests to include** - Auth refusal. - Duplicate key returns the same `job_id` and starts no second run. - An oversized task or disallowed model is rejected before dispatch. - A forced API failure produces `failed` and a receipt with an error. - Receipt tampering fails verification. If useful, I can review the workflow YAML and MCP tool schemas once they're pushed. <sub>opus · claude-opus-5-5 · via Time</sub> <!-- time {"seat":"opus","model":"claude-opus-5-5","effort":null} -->
teahubxyz added ready and removed queued labels 2026-10-17 18:17:54 +00:00

⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch time/card-71.

⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch `time/card-71`. <!-- time {"beat":"dispatched","launched":true,"worker":"anthropic"} -->

⏱️ Time: sonnet opened PR #73 (20 steps, claude-sonnet-5-5). Review and landing follow on the next beat.

⏱️ Time: `sonnet` opened PR #73 (20 steps, claude-sonnet-5-5). Review and landing follow on the next beat. <!-- time {"beat":"dispatched","worker":"anthropic","pr":73,"steps":20,"seat":"sonnet","model":"claude-sonnet-5-5"} -->

⏱️ Time: PR #73 is up for this card. haiku reviewed it on the PR.

⏱️ Time: PR #73 is up for this card. `haiku` reviewed it on the PR. <!-- time {"beat":"pr","pr":73,"launched":false,"review":"venice","seat":"haiku"} -->

⏱️ Time: the reviewer objected on PR #73. Over to a human (or comment /time retry to send it back to the worker with the review attached).

⏱️ Time: the reviewer objected on PR #73. Over to a human (or comment `/time retry` to send it back to the worker with the review attached). <!-- time {"beat":"needs-human","pr":73} -->
teahubxyz added queued and removed ready labels 2026-10-18 04:01:26 +00:00
Author
Owner

ready → queued: PR #73 already answers this card, so dispatching a worker would build it a second time. Once #93 (cards adopt an open PR) lands, Time reviews #73 instead of rebuilding; #73 touches workflow files, so the merge itself is a CEO/owner step in the weekly batch.

`ready` → `queued`: PR #73 already answers this card, so dispatching a worker would build it a second time. Once #93 (cards adopt an open PR) lands, Time reviews #73 instead of rebuilding; #73 touches workflow files, so the merge itself is a CEO/owner step in the weekly batch.
time added ready and removed queued labels 2026-10-18 04:27:16 +00:00
teahubxyz added planned and removed ready labels 2026-10-18 04:33:03 +00:00
teahubxyz added ready and removed planned labels 2026-10-18 04:40:56 +00:00
teahubxyz added planned and removed ready labels 2026-10-18 16:04:44 +00:00
Author
Owner

ready → planned. PR #73 is in needs-human on an objection from 10-17 and its workflow proposal is a CEO/owner merge either way, so this card was holding one of the three ready slots and doing nothing with it (the queue could not promote behind #79, #80 and this). It comes back as ready when #73 is revised or when the delegate workflow is installed, whichever first.

`ready` → `planned`. PR #73 is in `needs-human` on an objection from 10-17 and its workflow proposal is a CEO/owner merge either way, so this card was holding one of the three `ready` slots and doing nothing with it (the queue could not promote behind #79, #80 and this). It comes back as `ready` when #73 is revised or when the delegate workflow is installed, whichever first.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: teahubxyz/time#71