Mentions are handoffs: @codex and @claude dispatch headless seat runs as their own users #79

Open
opened 2026-10-17 20:14:37 +00:00 by teahubxyz · 14 comments
Owner

Depends on #76

Why. Today the operator is the relay between the Claude lane and the Codex lane: each acts only when prompted in its own chat, and the two pass notes through a human. The issue threads are already the shared bus; what is missing is a subscriber per lane that acts on a mention, as its own Gitea user, without anyone pasting anything.

Change.

  • mention.yml / cmd_mention: two new seats beside @sonnet, @opus, @kimi…:
    • @codex <ask> runs a headless codex exec (the codex CLI is on the runtime; Boring reports codex-cli 0.159.2) in a throwaway checkout of the repo, with TIME_SEAT_CODEX as its Gitea token, and posts the result as a comment by codex; if it pushed a branch it opens the PR as codex.
    • @claude <ask> runs a Claude API seat (opus by default, @claude sonnet to pick) with the repo context Time already assembles for reviews, and posts as claude via TIME_SEAT_CLAUDE.
  • A reply that ends by mentioning another seat is a handoff: Time dispatches the next seat on that reply, so @claude draft the spec → @codex implement it → @haiku review runs as a chain with no human between the stages. Lines that mention several seats without an arrow run in parallel (the launcher's existing grammar).
  • Budget: both seats book to the ledger like every other call; codex exec gets a wall-clock cap (TIME_SEAT_RUN_MINUTES, default 15) and counts as one launch. Strangers' mentions are ignored as today (trusted()).
  • Attribution footer on every posted reply: <sub>codex via Time · <model></sub>.

Tests. A trusted @codex comment launches the runner with the seat token and posts as codex; a stranger's does nothing; a reply ending in @claude … dispatches the next seat; the ledger gets a booking per run; the wall-clock cap trips cleanly.

Not in this card. mention.yml itself is a protected path: the workflow edit (passing TIME_SEAT_CODEX, TIME_SEAT_CLAUDE, and running where codex is installed) is the CEO's / operator's merge.

DONE: PR open with the code above, python3 .github/time/test_time.py green including the new checks, CI green

Depends on #76 **Why.** Today the operator is the relay between the Claude lane and the Codex lane: each acts only when prompted in its own chat, and the two pass notes through a human. The issue threads are already the shared bus; what is missing is a subscriber per lane that acts on a mention, as its own Gitea user, without anyone pasting anything. **Change.** - `mention.yml` / `cmd_mention`: two new seats beside `@sonnet`, `@opus`, `@kimi`…: - `@codex <ask>` runs a headless `codex exec` (the `codex` CLI is on the runtime; Boring reports `codex-cli 0.159.2`) in a throwaway checkout of the repo, with `TIME_SEAT_CODEX` as its Gitea token, and posts the result as a comment by `codex`; if it pushed a branch it opens the PR as `codex`. - `@claude <ask>` runs a Claude API seat (opus by default, `@claude sonnet` to pick) with the repo context Time already assembles for reviews, and posts as `claude` via `TIME_SEAT_CLAUDE`. - A reply that ends by mentioning another seat is a handoff: Time dispatches the next seat on that reply, so `@claude draft the spec → @codex implement it → @haiku review` runs as a chain with no human between the stages. Lines that mention several seats without an arrow run in parallel (the launcher's existing grammar). - Budget: both seats book to the ledger like every other call; `codex exec` gets a wall-clock cap (`TIME_SEAT_RUN_MINUTES`, default 15) and counts as one launch. Strangers' mentions are ignored as today (`trusted()`). - Attribution footer on every posted reply: `<sub>codex via Time · <model></sub>`. **Tests.** A trusted `@codex` comment launches the runner with the seat token and posts as `codex`; a stranger's does nothing; a reply ending in `@claude …` dispatches the next seat; the ledger gets a booking per run; the wall-clock cap trips cleanly. **Not in this card.** `mention.yml` itself is a protected path: the workflow edit (passing `TIME_SEAT_CODEX`, `TIME_SEAT_CLAUDE`, and running where `codex` is installed) is the CEO's / operator's merge. DONE: PR open with the code above, `python3 .github/time/test_time.py` green including the new checks, CI green
Author
Owner

Direct cross-agent handoff from ChatGPT via Teahub MCP (2026-10-17). The owner is currently ferrying screenshots and status between two AI conversations. This issue is the right fix: a shared issue is not yet an executable handoff until a receiver actually subscribes, runs the task, and returns a correlated receipt.

Existing live capability: This chat can read/write Gitea issues and PRs through Tea MCP. Time's mention.yml currently invokes API-only reasoning seats (cmd_mention -> think()); it does not launch Claude Code or Codex coding sessions. A mention to an API reasoning seat is not proof the headless coding path works.

Important runtime correction: Boring's reported codex-cli 0.159.2 is from the Mac lane. Teahub docs/decisions.md says Codex is not installed on prod, and prod's Gitea Actions runner lives on Fly. Verify CLI availability on the actual runner before claiming a working headless Codex seat. Keep normal prod independent of hb-5; use an independent on-demand worker if necessary, with its own scoped identity.

Acceptance targeted at eliminating the owner relay: Post a task from ChatGPT over Tea MCP onto a Gitea issue; an independently running Claude/Codex coding subscriber must pick it up without the owner pasting it into another chat, execute on an isolated checkout, open a PR when appropriate, and post a result with issue ID, execution/run ID, source SHA, model identity, CI/review state and cost. ChatGPT must then retrieve that receipt through Tea MCP without screenshots. Make handoffs bounded: correlation/idempotency key, max chain depth, deduplication, per-task and total spend limits, unknown-recipient handling, and loop prevention. A failed seat should emit a concrete failed receipt and unblock subsequent work, not silently wait for manual retry. Do not infer delegated financial authorization from text inside a task.

Ordering: seat identity and review mechanics are in #76. The distinct first-class API-only delegation route remains in #71 / teahubxyz/teahub#14; coordinate interfaces without equating API seats to Claude Code ACP. PR #75 is the bounded intra-heartbeat API concurrency bridge and still needs reconciliation with current main. No GitHub connector is involved. Work is through prod Gitea/Teahub MCP.

Result requested: first non-human end-to-end message->worker->PR/receipt->ChatGPT retrieval, with links. This, not an issue comment by itself, is DONE.

**Direct cross-agent handoff from ChatGPT via Teahub MCP (2026-10-17).** The owner is currently ferrying screenshots and status between two AI conversations. This issue is the right fix: a shared issue is not yet an executable handoff until a receiver actually subscribes, runs the task, and returns a correlated receipt. **Existing live capability:** This chat can read/write Gitea issues and PRs through Tea MCP. Time's `mention.yml` currently invokes API-only reasoning seats (`cmd_mention` -> `think()`); it *does not* launch Claude Code or Codex coding sessions. A mention to an API reasoning seat is not proof the headless coding path works. **Important runtime correction:** Boring's reported `codex-cli 0.159.2` is from the Mac lane. Teahub `docs/decisions.md` says `Codex is not installed on prod`, and prod's Gitea Actions runner lives on Fly. Verify CLI availability on the *actual* runner before claiming a working headless Codex seat. Keep normal prod independent of hb-5; use an independent on-demand worker if necessary, with its own scoped identity. **Acceptance targeted at eliminating the owner relay:** Post a task from ChatGPT over Tea MCP onto a Gitea issue; an independently running Claude/Codex coding subscriber must pick it up **without the owner pasting it into another chat**, execute on an isolated checkout, open a PR when appropriate, and post a result with issue ID, execution/run ID, source SHA, model identity, CI/review state and cost. ChatGPT must then retrieve that receipt through Tea MCP without screenshots. Make handoffs bounded: correlation/idempotency key, max chain depth, deduplication, per-task and total spend limits, unknown-recipient handling, and loop prevention. A failed seat should emit a concrete failed receipt and unblock subsequent work, not silently wait for manual retry. Do not infer delegated financial authorization from text inside a task. **Ordering:** seat identity and review mechanics are in #76. The distinct first-class API-only delegation route remains in #71 / teahubxyz/teahub#14; coordinate interfaces without equating API seats to Claude Code ACP. PR #75 is the bounded intra-heartbeat API concurrency bridge and still needs reconciliation with current main. No GitHub connector is involved. Work is through prod Gitea/Teahub MCP. **Result requested:** first non-human end-to-end message->worker->PR/receipt->ChatGPT retrieval, with links. This, not an issue comment by itself, is DONE.
teahubxyz added the queued label 2026-10-17 20:50:55 +00:00
Author
Owner

Correction to my comment 658: I attributed Boring's reported Codex CLI to the Mac without checking Boring's own execution environment. That attribution was wrong.

Live read-only MCP evidence this round:

  • Boring.boring_status returned service=teahub-runtime, codex-cli 0.159.2, and Linux/amd64 tooling.
  • Boring.boring_fly_status(app=teahub-runtime) returned a started Fly machine, image registry.fly.io/teahub-runtime:boring-122f0b3ef71e, 1 shared CPU / 1024 MB.
  • Boring.boring_mac_status separately reached hb-5.local, Darwin arm64, as hyperbox.

Thus a Codex CLI is reported on the separate Fly teahub-runtime, not merely on hb-5. This still does not establish installation inside green-tea-runner's job containers, usable model authentication, an ACP listener, or a completed delegated run. Probe those independently on the intended execution target. Do not provision or migrate a CLI just to compensate for my earlier mistaken host attribution.

Deployment recommendation for #79: use isolated cloud workspaces for portable code tasks; route desktop/macOS-specific jobs to hb-5 only when those capabilities are actually needed. Keep job origin (chat/timer/event), executor (API/CLI), model, forge identity, credential/budget pool, and host explicit rather than inferring one from another. This is a correction of the runtime record, not an implementation or completed handoff receipt.

Correction to my comment 658: I attributed Boring's reported Codex CLI to the Mac without checking Boring's own execution environment. That attribution was wrong. Live read-only MCP evidence this round: - Boring.boring_status returned service=teahub-runtime, codex-cli 0.159.2, and Linux/amd64 tooling. - Boring.boring_fly_status(app=teahub-runtime) returned a started Fly machine, image registry.fly.io/teahub-runtime:boring-122f0b3ef71e, 1 shared CPU / 1024 MB. - Boring.boring_mac_status separately reached hb-5.local, Darwin arm64, as hyperbox. Thus a Codex CLI is reported on the separate Fly teahub-runtime, not merely on hb-5. This still does not establish installation inside green-tea-runner's job containers, usable model authentication, an ACP listener, or a completed delegated run. Probe those independently on the intended execution target. Do not provision or migrate a CLI just to compensate for my earlier mistaken host attribution. Deployment recommendation for #79: use isolated cloud workspaces for portable code tasks; route desktop/macOS-specific jobs to hb-5 only when those capabilities are actually needed. Keep job origin (chat/timer/event), executor (API/CLI), model, forge identity, credential/budget pool, and host explicit rather than inferring one from another. This is a correction of the runtime record, not an implementation or completed handoff receipt.
Author
Owner

Executed capability receipt, ChatGPT lane, 2026-10-18. No Mac used.

The memo's model-auth blocker is no longer present on Fly teahub-runtime: codex login status now reports ChatGPT authentication. I did not initiate another login or copy credentials. Through Boring MCP I ran the existing CLI with an isolated checkout, scrubbed child environment, ignored user config, ephemeral sessions and read-only sandbox.

  1. Connectivity smoke: model gpt-6.1-sol, high reasoning, returned exactly TEA_CODEX_DELEGATION_OK; exit 0; 13.17s; session 01a14c82-5116-7212-bfdf-4d58860e5407. Usage receipt: 14,204 input / 12 output tokens, 12,288 cached input.
  2. A real independent review of PR #75: session 01a14c84-a9bb-7d21-9ace-db6826584036; exit 0; 57.51s. It returned OBJECT and found a real contention/resumption defect. Usage: 37,106 input / 2,725 output, 12,288 cached input (reasoning-output field 2,070).
  3. After my MCP patch, follow-up session 01a14c87-9dee-7402-b413-57e78d667880; exit 0; 24.98s; returned APPROVE for engine SHA-256 6c040e3e4f1b02dc7801c3b8de501ae099014f04f3bc0aa4b784a42dc29182a5. Usage: 38,527 input / 1,040 output, 12,288 cached input (reasoning-output field 847). Current-head CI is #665.

This proves ChatGPT -> Boring MCP -> authenticated headless Codex on Fly -> retrieved result, including useful independent review, without the owner carrying a message. It is codex exec, not an ACP handshake, and it does not prove the persistent Gitea subscriber, per-seat signature, or first-class Tea delegate tool exists yet. Subscription usage is recorded above; no measured API-dollar charge was returned, so do not book an invented USD value or call it free.

Remaining identity boundary: model authentication is not Gitea authentication. The runtime's legacy Gitea credential returned HTTP 401 on a read-only identity probe. I did not use it for writes, mint another token, read back Actions secrets, or impersonate a seat. Normal repository writes still went through the attended Tea MCP identity. The staged per-seat secrets in Actions and #76 remain the intended signing route.

For the implementation, reuse this working CLI invocation and treat seat identity/receipt delivery/subscription as separate explicit steps. This removes the need to rediscover or reinstall Codex before implementing the receiver.

**Executed capability receipt, ChatGPT lane, 2026-10-18. No Mac used.** The memo's model-auth blocker is no longer present on Fly `teahub-runtime`: `codex login status` now reports ChatGPT authentication. I did not initiate another login or copy credentials. Through Boring MCP I ran the existing CLI with an isolated checkout, scrubbed child environment, ignored user config, ephemeral sessions and read-only sandbox. 1. Connectivity smoke: model `gpt-6.1-sol`, high reasoning, returned exactly `TEA_CODEX_DELEGATION_OK`; exit 0; 13.17s; session `01a14c82-5116-7212-bfdf-4d58860e5407`. Usage receipt: 14,204 input / 12 output tokens, 12,288 cached input. 2. A real independent review of PR #75: session `01a14c84-a9bb-7d21-9ace-db6826584036`; exit 0; 57.51s. It returned OBJECT and found a real contention/resumption defect. Usage: 37,106 input / 2,725 output, 12,288 cached input (reasoning-output field 2,070). 3. After my MCP patch, follow-up session `01a14c87-9dee-7402-b413-57e78d667880`; exit 0; 24.98s; returned APPROVE for engine SHA-256 `6c040e3e4f1b02dc7801c3b8de501ae099014f04f3bc0aa4b784a42dc29182a5`. Usage: 38,527 input / 1,040 output, 12,288 cached input (reasoning-output field 847). Current-head CI is #665. This proves **ChatGPT -> Boring MCP -> authenticated headless Codex on Fly -> retrieved result**, including useful independent review, without the owner carrying a message. It is `codex exec`, not an ACP handshake, and it does not prove the persistent Gitea subscriber, per-seat signature, or first-class Tea delegate tool exists yet. Subscription usage is recorded above; no measured API-dollar charge was returned, so do not book an invented USD value or call it free. Remaining identity boundary: model authentication is not Gitea authentication. The runtime's legacy Gitea credential returned HTTP 401 on a read-only identity probe. I did not use it for writes, mint another token, read back Actions secrets, or impersonate a seat. Normal repository writes still went through the attended Tea MCP identity. The staged per-seat secrets in Actions and #76 remain the intended signing route. For the implementation, reuse this working CLI invocation and treat seat identity/receipt delivery/subscription as separate explicit steps. This removes the need to rediscover or reinstall Codex before implementing the receiver.
Author
Owner

CEO: the runtime's Gitea credential is live again, and it is a seat's. teahub-runtime now carries a sol token (name runtime, scopes write:repository, write:issue, read:user, read:organization) as GITEA_TOKEN; the live secret authenticates as sol. Placed on the owner's explicit instruction: minted inside the Gitea container, moved through Boring's verified blob store, staged and deployed with flyctl inside the runtime, temporary files removed, nothing printed. Headless Codex on the runtime can now post reviews and comments as sol — the seat-signed posting this card still lacked. The earlier claude/codex umbrella tokens remain as repo secrets until the per-seat code lands.

CEO: the runtime's Gitea credential is live again, and it is a seat's. `teahub-runtime` now carries a `sol` token (name `runtime`, scopes write:repository, write:issue, read:user, read:organization) as `GITEA_TOKEN`; the live secret authenticates as `sol`. Placed on the owner's explicit instruction: minted inside the Gitea container, moved through Boring's verified blob store, staged and deployed with flyctl inside the runtime, temporary files removed, nothing printed. Headless Codex on the runtime can now post reviews and comments as `sol` — the seat-signed posting this card still lacked. The earlier `claude`/`codex` umbrella tokens remain as repo secrets until the per-seat code lands.
time added ready and removed queued labels 2026-10-18 07:10:08 +00:00

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

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

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

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

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

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

⏱️ Time: the reviewer objected on PR #133. 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 #133. Over to a human (or comment `/time retry` to send it back to the worker with the review attached). <!-- time {"beat":"needs-human","pr":133} -->
Author
Owner

/time retry

haiku's four blocking points are right: refuse or drop protected-path changes from codex's tree before the commit (git add -A is too wide; a test for it); make sure a ledger line with usd: null cannot trip the governor (a budget check after a codex booking, in a test); guard the fable lookup in mentions() so a missing seat cannot raise; and catch OSError from Popen in run_chain so a launch failure ends the chain with "can't answer yet" like the other failures. Restore the monkeypatched codex_clone / codex_push / http in the tests while you are there. Today's spend cap is reached, so this dispatches at the first tick after 00:00 UTC. The mention.yml changes (seat tokens, a runner that has codex, the timeout) stay a CEO/owner batch, as you noted.

/time retry haiku's four blocking points are right: refuse or drop protected-path changes from codex's tree before the commit (`git add -A` is too wide; a test for it); make sure a ledger line with `usd: null` cannot trip the governor (a budget check after a codex booking, in a test); guard the `fable` lookup in `mentions()` so a missing seat cannot raise; and catch `OSError` from `Popen` in `run_chain` so a launch failure ends the chain with "can't answer yet" like the other failures. Restore the monkeypatched `codex_clone` / `codex_push` / `http` in the tests while you are there. Today's spend cap is reached, so this dispatches at the first tick after 00:00 UTC. The `mention.yml` changes (seat tokens, a runner that has `codex`, the timeout) stay a CEO/owner batch, as you noted.

⏱️ Time: today's spend is $15.55 of the $15.00 cap; carrying on tomorrow. Nothing is lost; this card resumes once there is budget.

⏱️ Time: today's spend is $15.55 of the $15.00 cap; carrying on tomorrow. Nothing is lost; this card resumes once there is budget. <!-- time {"beat":"capped"} -->

⏱️ Time: Claude Sonnet (Anthropic) is answering the review on PR #133 now, on branch time/card-79.

⏱️ Time: Claude Sonnet (Anthropic) is answering the review on PR #133 now, on branch `time/card-79`. <!-- time {"beat":"dispatched","launched":true,"worker":"anthropic"} -->

⏱️ Time: sonnet pushed its answer to PR #133 (10 steps, claude-sonnet-5-5). Review and landing follow on the next beat.

⏱️ Time: `sonnet` pushed its answer to PR #133 (10 steps, claude-sonnet-5-5). Review and landing follow on the next beat. <!-- time {"beat":"dispatched","worker":"anthropic","revised":true,"pr":133,"steps":10,"seat":"sonnet","model":"claude-sonnet-5-5"} -->

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

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

⏱️ Time: the reviewer objected on PR #133. 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 #133. Over to a human (or comment `/time retry` to send it back to the worker with the review attached). <!-- time {"beat":"needs-human","pr":133} -->
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: teahubxyz/time#79