承認 — a gated tool suspends the turn and the client answers it #26

Closed
opened 2026-07-29 22:56:20 +00:00 by aiko · 1 comment
Owner

What to build

A gated tool suspends her turn and the client answers it. The design has drawn this twice — Turn 4 承認 and all of Turn 9 (承認 on a parked tab) — and nothing tracked it, because the fidelity audit wrongly listed it as having no data path. It has one, and has since before this design existed.

Everything needed is already on the /v1/runs surface the go-between talks to. Verified against hermes-agent 0.19.0:

Piece Where
approval.request event on the run-events stream, command pre-redacted via _redact_approval_command gateway/platforms/api_server.py:6250-6275
choices array on that event — ["once","deny"] when smart-denied, else ["once","session","always","deny"] or ["once","session","deny"] api_server.py:71-74 (_approval_event_choices)
Run status waiting_for_approval api_server.py:1348, 6271
POST /v1/runs/{run_id}/approval, body {choice, resolve_all}; accepts once / session / always / deny plus the aliases approve/approved/allowonce api_server.py:1847, 6548
Capability flags approval_events, run_approval_response api_server.py:2873-2875

The gap is entirely go-between-side. HermesAgent has no approval handling — grep -n approval src/open_llm_vtuber/agent/agents/hermes_agent.py returns nothing, and _stream_run_events discards approval.request with every other unrecognised event. Today a gated tool therefore reads as a hang and then a failure, which is the symptom, not the cause.

Scope

Go-between:

  • Forward approval.request down the client socket as a new message type, carrying the redacted command, the specifics the card shows, and the choices array verbatim.
  • Stop treating a suspended run as a timeout — waiting_for_approval is alive. The poll/stream loop must distinguish it from a stall.
  • Accept the client's answer and relay it to POST /v1/runs/{run_id}/approval. The client must not hold the Hermes bearer key — the go-between makes the call.
  • Persist the outcome so the log can show which grant was given after the fact.

Client:

  • The 承認待ち card per Turn 4: 許可 (44px, #eaff3c) / やめて / 差分, the ask in plain language, the specifics line, and the 保留 · 12秒 hold timer.
  • Render only the choices the event offers. choices is per-request — a smart-denied tool offers ["once","deny"], so drawing four buttons unconditionally would invent two. This is the one place the design's "four values, not two" needs a conditional.
  • 4b's 承認待ち relay state in 受信記録 (許可が必要, name in #eaff3c, hold timer in the duration slot).
  • The log records which grant was given, not merely that it was approved — 許可 · 今回だけ / 許可 · 交信中 / 許可 · ずっと / 拒否. Per Turn 4, a standing grant is the thing you most want to find again later, and where one is in force the 承認後 line says so (fs.write はもうきかない).
  • Turn 9: answerable from a parked 34×52 pal without full expansion, and from the notification shade (9b). One outstanding ask at a time; a second gated tool queues behind the first and the tab counts them.
  • やめて → she finishes the turn without that tool and the log records 拒否.

Acceptance criteria

  • A gated tool suspends the turn and the client shows the ask, not a spinner and then a timeout
  • Only the offered choices are rendered; a smart-denied tool shows two buttons, not four
  • Answering resolves the run and it continues in the same turn
  • resolve_all is reachable where the design implies a blanket answer
  • The log shows which grant was given, and standing grants are discoverable afterwards
  • Answerable from a parked pal and from the notification shade
  • The client never holds the Hermes bearer key
  • A suspended run is never reported to the user as a failure

Not blocked by anything

Independent of ollvt-hermes-bridge#16 (the Platform adapter) and of the go-between inbound receiver. The adapter's send_exec_approval is a second path to the same capability, not a prerequisite.

Design reference: docs/design/denpa-receiver/README.md — Turn 4 承認, Turn 9, and the Buildable today section of the fidelity table.

## What to build A gated tool suspends her turn and the client answers it. The design has drawn this twice — Turn 4 承認 and all of Turn 9 (承認 on a parked tab) — and nothing tracked it, because the fidelity audit wrongly listed it as having no data path. It has one, and has since before this design existed. **Everything needed is already on the `/v1/runs` surface the go-between talks to.** Verified against `hermes-agent 0.19.0`: | Piece | Where | |---|---| | `approval.request` event on the run-events stream, command pre-redacted via `_redact_approval_command` | `gateway/platforms/api_server.py:6250-6275` | | `choices` array on that event — `["once","deny"]` when smart-denied, else `["once","session","always","deny"]` or `["once","session","deny"]` | `api_server.py:71-74` (`_approval_event_choices`) | | Run status `waiting_for_approval` | `api_server.py:1348`, `6271` | | `POST /v1/runs/{run_id}/approval`, body `{choice, resolve_all}`; accepts `once` / `session` / `always` / `deny` plus the aliases `approve`/`approved`/`allow` → `once` | `api_server.py:1847`, `6548` | | Capability flags `approval_events`, `run_approval_response` | `api_server.py:2873-2875` | **The gap is entirely go-between-side.** `HermesAgent` has no approval handling — `grep -n approval src/open_llm_vtuber/agent/agents/hermes_agent.py` returns nothing, and `_stream_run_events` discards `approval.request` with every other unrecognised event. Today a gated tool therefore reads as a hang and then a failure, which is the symptom, not the cause. ### Scope **Go-between:** - Forward `approval.request` down the client socket as a new message type, carrying the redacted command, the specifics the card shows, and the `choices` array verbatim. - Stop treating a suspended run as a timeout — `waiting_for_approval` is alive. The poll/stream loop must distinguish it from a stall. - Accept the client's answer and relay it to `POST /v1/runs/{run_id}/approval`. **The client must not hold the Hermes bearer key** — the go-between makes the call. - Persist the outcome so the log can show which grant was given after the fact. **Client:** - The 承認待ち card per Turn 4: `許可` (44px, `#eaff3c`) / `やめて` / `差分`, the ask in plain language, the specifics line, and the `保留 · 12秒` hold timer. - **Render only the choices the event offers.** `choices` is per-request — a smart-denied tool offers `["once","deny"]`, so drawing four buttons unconditionally would invent two. This is the one place the design's "four values, not two" needs a conditional. - 4b's 承認待ち relay state in 受信記録 (`許可が必要`, name in `#eaff3c`, hold timer in the duration slot). - **The log records which grant was given**, not merely that it was approved — `許可 · 今回だけ` / `許可 · 交信中` / `許可 · ずっと` / `拒否`. Per Turn 4, a standing grant is the thing you most want to find again later, and where one is in force the 承認後 line says so (`fs.write はもうきかない`). - Turn 9: answerable from a parked 34×52 pal without full expansion, and from the notification shade (9b). One outstanding ask at a time; a second gated tool queues behind the first and the tab counts them. - `やめて` → she finishes the turn **without** that tool and the log records `拒否`. ### Acceptance criteria - [ ] A gated tool suspends the turn and the client shows the ask, not a spinner and then a timeout - [ ] Only the offered `choices` are rendered; a smart-denied tool shows two buttons, not four - [ ] Answering resolves the run and it continues in the same turn - [ ] `resolve_all` is reachable where the design implies a blanket answer - [ ] The log shows which grant was given, and standing grants are discoverable afterwards - [ ] Answerable from a parked pal and from the notification shade - [ ] The client never holds the Hermes bearer key - [ ] A suspended run is never reported to the user as a failure ### Not blocked by anything Independent of `ollvt-hermes-bridge#16` (the Platform adapter) and of the go-between inbound receiver. The adapter's `send_exec_approval` is a second path to the same capability, not a prerequisite. Design reference: `docs/design/denpa-receiver/README.md` — Turn 4 承認, Turn 9, and the *Buildable today* section of the fidelity table.
Author
Owner

Closing as a duplicate of #19, which covers this and predates it — I filed this after wrongly concluding nothing tracked 承認. #19 is reopened with the corrected wire facts.

#19 also holds a decision this issue got wrong: the grant vocabulary does not ship as four keys. Per #19 and Turn 9 of the design doc, the two-key 許可 / やめて layout stays and session / always live behind a press-and-hold on 許可, because the acid 許可 must remain the one obvious thing to press and a parked tab's 168px cuff cannot hold four 44px targets. This issue's "the four grant values ship as drawn" was wrong and would have re-litigated a settled decision.

The one piece worth keeping — gate the press-and-hold reveal on the event's per-request choices, so a smart-denied tool suppresses the notch tell instead of revealing dead options — is carried over to #19.

Closing as a duplicate of #19, which covers this and predates it — I filed this after wrongly concluding nothing tracked 承認. #19 is reopened with the corrected wire facts. #19 also holds a decision this issue got wrong: **the grant vocabulary does not ship as four keys.** Per #19 and Turn 9 of the design doc, the two-key 許可 / やめて layout stays and `session` / `always` live behind a press-and-hold on 許可, because the acid 許可 must remain the one obvious thing to press and a parked tab's 168px cuff cannot hold four 44px targets. This issue's "the four grant values ship as drawn" was wrong and would have re-litigated a settled decision. The one piece worth keeping — gate the press-and-hold reveal on the event's per-request `choices`, so a smart-denied tool suppresses the notch tell instead of revealing dead options — is carried over to #19.
aiko 2026-07-29 23:02:46 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiko/denpa#26
No description provided.