Skip to content

fix(server): remote delegated tasks surface their questions and approvals on the parent - #26344

Draft
Silthus wants to merge 3 commits into
pingdotgg:mainfrom
Silthus:fix/remote-child-pending-requests
Draft

Silthus wants to merge 3 commits into
pingdotgg:mainfrom
Silthus:fix/remote-child-pending-requests

Conversation

@Silthus

@Silthus Silthus commented Oct 28, 2026 •

Copy link
Copy Markdown

Problem

A task delegated to a linked environment (delegate_task with target.environmentId) can stop on a question or an approval there. The parent never shows it. The sidebar, web, and mobile clients connected to the parent's server have nothing to answer, and delegate_task / task_status report hasPendingQuestion: false and pendingApproval: null, so a mode: "wait" call runs until it times out. The task only moves on if someone opens the other machine and answers there.

Expected: the parent shows the remote task's question or approval like a local one, an answer given on the parent reaches the task, and a wait ends on it.

Warning

🟡 Merge risk: medium · two-way door (merge-risk rubric)

  • An answer given on the parent now acts in another environment. Relaying runs on the link's own session, so writesThreads holds it to the link's access and fence. A link paired below full access cannot "accept always". If the relay breaks, the remote task stays blocked, as it does on main today. The parent then asks again with an "Answer not delivered" error item.
  • Reach is small. Remote delegation only runs with Linked environments (Settings → Labs, off by default) turned on at both ends. A peer without the new tool is treated as an older version, and the relay stays quiet.
  • Revert: git revert the merge commit. The new projection field is optional and is ignored after a revert.

Change

  • Mirror. The parent's follower (RemoteDelegation) watches the remote thread's live requests through a new link-only MCP tool, t3_link_requests (watch), and records them on the parent's projection as remoteTaskRequests with the internal command delegated_task.remote.requests. The shell's pendingRuntimeRequest falls back to the newest unanswered one, which drives the sidebar status and notifications. client-runtime adds the requests to derivePendingThreadRequests, so web and mobile render them with the existing question and approval UI.
  • Answer. runtime-request.respond on the parent for a mirrored request records the answer on the projection. The follower relays it with t3_link_requests (respond). On the remote, that call runs as the link session, so it gets the same access, mode, and fence checks as any other write over the link. If the remote refuses and the request still waits, delegated_task.remote.respond.fail clears the answer and adds an "Answer not delivered" error item, so the parent asks again.
  • Tools. delegate_task and task_status report hasPendingQuestion and pendingApproval for remote tasks. mode: "wait" wakes on them and keeps the completionWake: "always" upgrade that local tasks get.
  • t3_link_requests is hidden from agents and outside clients (EnabledWhen link sessions only), so agents' tool lists don't grow. AgentContextBudget.test.ts now measures what an agent session lists, instead of every registered tool.
  • A fork does not copy the mirrored requests; the source thread owns its tasks.

Scope and approval

There is no triaged issue for this yet. It is a reliability fix for an existing capability: remote delegate_task already promises the parent a task it can follow, and a task blocked on a question it never surfaces breaks that promise. It spans server, contracts, and client-runtime because the request has to cross the link, land on the parent's projection, and render in the clients that already handle local requests. Happy to open an issue first if you'd rather triage it there.

Verification

Live run on this branch: two isolated servers on one machine, a "laptop" dev server linked to a "box" server with approval-required access. A Claude Haiku parent on the laptop delegated to the box's Replay (dev) provider.

  • Question. delegate_task in wait mode returned on the box task's question (hasPendingQuestion: true, waitTimedOut: false). The laptop sidebar showed the parent as Input, and the parent's composer showed the box's question. I answered it there. On the box, the request resolved with {"schema_vs_ui_flexibility":"UI flexibility"}, applied by a mcp:link-respond:client…:fwd… command (the link session). The task finished and woke the parent.

    Parent on the laptop shows the box task's question
    After answering on the laptop, the box task finishes and the parent resumes

  • Approval. delegate_task in wait mode returned with pendingApproval: {kind: "command", …}. The sidebar showed Approval, and the parent's timeline showed the box's "Ran command · Waiting for approval" row with Decline and Approve. Approve resolved it on the box with decision: "accept", and the task finished.

    Parent on the laptop shows the box task's approval

Cross-version. I ran the follower suite (RemoteDelegation.test.ts) against a box without the thread toolkit, as an older peer. 26 of 26 tests pass, with the two new approval tests skipped. A delegated task to an older peer still follows, finishes, and stops as before.

Test seams

  • linkRequests.test.ts: the tool as a link session calls it. Watch returns the details and waits for a new request. Agents and outside clients cannot find it. A read-only link cannot answer. Another link's thread is refused, and so is "accept always" below full access. An answer dispatches with a stable command id.
  • RemoteDelegatedTask.test.ts: the orchestrator's mirror, answer, wrong answer, double answer, re-mirror that keeps an answer, drop, completion clearing, and the "Answer not delivered" path.
  • RemoteDelegation.test.ts: laptop and box end to end over a real forwarded link. An approval shows on the parent and is answered there, a refusal makes the parent ask again, and a wait ends on the approval while a later result still wakes the parent.
  • threadRequests.test.ts: clients list remote questions and approvals as live requests.
  • ThreadForkService.test.ts: a fork leaves the source's remote requests behind.

Checks: vp run check:changed passes format, lint, knip, and typecheck. Its tests fail in two places that are unrelated to this change. GuestPage.browser.test.tsx fails here with or without the client-runtime change. GitVcsDriver.test.ts fails because this machine rewrites https://github.com/ to SSH in its global git config.

Gaps: I did not walk the mobile app. Mobile reads the same derivePendingThreadRequests that web does, and that function is unit-tested.

Decisions
Question Answer Who Where
One PR or a stack? One PR covering mirror, answer, and tool reporting 👤 Human pending
How does an answer reach the remote? A link-only MCP tool through the existing peer forwarding, so the link's access and fence apply; a new RPC was the alternative 🤖 Agent pending
May a relayed answer "accept always"? Only over a full-access link 🤖 Agent pending
What happens with an older peer? The relay stays quiet and the task follows as before 🤖 Agent pending
Does the CLI's thread show count remote requests? No, it counts the thread's own; the shell, clients, and task tools carry them 🤖 Agent pending

Involvement: one scope question answered, a design pass (3 findings fixed), a live run with screenshots.

Model and harness: Claude Opus 5.5 in Claude Code, run from T3 Code.

🤖 Generated with Claude Code

…vals on the parent

A task delegated to a linked environment kept its questions and approvals
there: the parent showed nothing, task_status reported none, and only a client
attached to that environment could answer them. The follower now mirrors what
the task's thread there waits on onto the parent's record through a new
link-only t3_link_requests tool, the parent's shell and composer show it,
task_status and delegate_task waits report it, and an answer given on the
parent is relayed through the link, which holds it to the link's access.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silthus added a commit to Silthus/t3code that referenced this pull request Oct 28, 2026
Silthus added a commit to Silthus/t3code that referenced this pull request Oct 28, 2026
Silthus added a commit to Silthus/t3code that referenced this pull request Oct 28, 2026
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Oct 28, 2026
- A watch also wakes on a request's question or approval details, and the
  link, follower and parent compare request content, so details that land
  after the request reach the parent.
- A local child no longer inherits its parent's mirrored requests.
- A message sent on the parent no longer declines a remote task's approval,
  on web or mobile, and a remote question offers no attachments.
- A link paired below full access is not offered "accept always".
- The relay retries a failing or older peer per pass with a back-off that
  resets, instead of stopping for the rest of the task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Oct 28, 2026
A question mirrored from a task in another environment cannot take
attachments, so the mobile card no longer offers the picker or paste for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant