Skip to content

fix(server): recover remote task launches after source restart - #20937

Closed
Lenoridev wants to merge 272 commits into
pingdotgg:t3code/peer/remote-delegatefrom
Lenoridev:fix/remote-delegation-launch-recovery-pr
Closed

Lenoridev wants to merge 272 commits into
pingdotgg:t3code/peer/remote-delegatefrom
Lenoridev:fix/remote-delegation-launch-recovery-pr

Conversation

@Lenoridev

@Lenoridev Lenoridev commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A remote delegate_task child can start before the source environment records its task. If the source stops or loses the accepted launch response, that child can keep running without a source task to recover, follow or stop. Retrying manually with the original key narrows this gap but does not recover work after an unattended source restart.

Change

Summary

delegate_task
  persist source task and launch inputs
  mark the launch under the parent dispatch lock
  launch with the original identity
  attach the destination receipt
startup recovery
  replay pending launches with their stored identity

Persist the source task and resolved launch inputs before sending the launch. Mark the start under the parent dispatch lock, then attach the destination receipt to that existing task. Startup recovery replays the original namespace/key without requiring the parent's launching run to remain active.

The stored launch identity also identifies the original OAuth grant generation, without storing credentials. A replacement link cannot turn recovery into a second child. Forwarding rechecks current authority; refusal records launch uncertainty explicitly. Pending cancellation avoids launching known-unsent work and reconciles already-accepted work using the captured Stop generation.

The shared contracts represent a remote task before its thread ID is known. Web and mobile keep that task identifiable as remote, show Launching, and disable opening it until attachment. These client changes are required by the pending state; workspace selection, Home, remote questions and approval notices remain separate.

Scope and approval

Fixes #17051, the launch/record defect and recovery approach documented by @juliusmarminge. This is a focused reliability follow-up to #16719. The PR targets t3code/peer/remote-delegate, refreshed onto b7b14bfdd858c359c8f4f541d5227f3d7a8d6051 on October 27. The normal two-parent merge retains both histories; only the 16 recovery files differ from that target. This latest refresh preserves the target restack without changing any of those recovery files.

This publishes the existing tested offer, refreshed onto the current feature branch. The issue's October 22 maintainer triage leaves the MCP result/restart contract for manual review. This branch-targeted PR remains proposed for review and can be applied/restacked by the stack owner. It does not imply approval to merge or a change to the rest of the stack.

Verification

Evidence

On the October 27 target 8aefc971a6, retaining the regression and restoring only the target's RemoteDelegation.ts reproduces the defect: after destination acceptance, the source has zero tasks instead of one. Restoring the recovery implementation passes the same regression, including rebuilding the source runtime against the same SQLite database after response loss and an ended parent run, then recovering exactly one task and destination thread with the original identity. The latest target retains this same RemoteDelegation.ts source byte for byte; this refresh reuses that red proof and runs the recovery regression again as part of the server suite.

At refresh c812fd58e9, 116 tests pass in six focused server files on Node 26.12.1 with the target's pinned Vite+ 1.4.0 and TypeScript drop-in 0.8.0. These cover RemoteDelegation, RemoteDelegatedTask, ThreadLaunchService, PeerLinks, PeerForwarding and ThreadManagementService. The target changed provider start/control behavior but left the recovery files unchanged, so this pass focuses on server compatibility.

cd apps/server
vp test run src/peer/RemoteDelegation.test.ts src/orchestration-v2/RemoteDelegatedTask.test.ts src/orchestration-v2/ThreadLaunchService.test.ts src/peer/PeerLinks.test.ts src/peer/PeerForwarding.test.ts src/orchestration-v2/ThreadManagementService.test.ts --maxWorkers=1

Scoped server, web and mobile types pass, as does the contracts package typecheck. The server scope also includes the forwarding test that failed in earlier target CI. Changed-file formatting and lint pass without findings.

The required vp run check:changed b7b14bfdd8 attempt passed formatting, lint and generated GraphQL validation. With one CPU and bounded memory, its broader scans did not finish within three minutes, so the owned process group was stopped. The aggregate command and its remaining knip/type/test stages are incomplete, not passed. The focused tests and types above ran separately. Current-head upstream CI remains pending.

The tests exercise accepted-response loss and runtime rebuild, never-sent intents, pending cancellation/Stop generations, reduced/revoked/replaced authority, duplicate and mismatched attachments, destination receipt replay and existing forwarding. They use source SQLite/orchestration and loopback HTTP/MCP/OAuth with modeled destination/provider services, plus the destination ThreadLaunchService receipt test. They do not establish provider or physical-host acceptance.

UI evidence

Before: pending task says Running After: pending task says Launching
Before: pending remote task labeled Running After: pending remote task labeled Launching

The images are the previously captured isolated web-client comparison from October 11: the same synthetic pending task rendered by original UI d79fdd537c and the original recovery contribution. They demonstrate Running versus Launching and pending navigation handling; they are historical captures, not a fresh client pass on this PR head. The original backend did not produce the synthetic pending record. Mobile card regressions passed at the earlier 8d8833d3b4 refresh; this refresh checks mobile types. No native mobile, real-provider, OS-process restart, two-physical-host or T3 Connect acceptance is claimed.

Merge danger

Door: one-way while source tasks are awaiting destination attachment. Complete or cancel pending launches before downgrading to a version that requires every remote task to already have a thread ID.

Blast radius: orchestration. The change affects remote launch recovery, link authority checks, cancellation and the display of pending remote tasks. The tests do not establish acceptance on physical hosts or every provider.

Written with GPT-6.1-Sol through the Codex harness in T3 Code, on behalf of Lenoridev.

Copilot AI balanced review requested due to automatic review settings October 17, 2026 15:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@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 17, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — This change adds durable remote-launch recovery and cancellation state across orchestration, shared contracts, peer authorization, and web/mobile clients, altering production execution and OAuth-link behavior. The cross-component state-machine and authentication impact are substantial enough to require human review.

You can add or adjust custom eligibility rules. Learn more.

juliusmarminge and others added 5 commits October 17, 2026 23:12
When the task's run on the linked environment ended, the follower completed
the task with that run's reply, even while the thread there still waited on
its own subagents, background tasks or a completion delivery that would wake
it for the real answer. A local task waits for that work and takes its
result from the run that answered after it.

t3_thread_wait takes an optional untilDelegatedWorkSettled. With it, the wait
also blocks until delegatedTaskProgress reports result_available for the
thread, waking on task, provider-thread and run updates, and reports that
result run as resultRun. The follower passes it and reads the result from
resultRun. Both fields are optional: an older peer ignores the input and
answers as before, and the follower then falls back to the launched run.

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

Stop on a parent skipped a remote task that already had its result, so
follow-up work on its thread in the linked environment (a message sent
there with t3_thread_send, or a run that started after the result) kept
running. A local child is stopped on every Stop, result or not, and
cancelling a finished remote task already stops its thread there.

Stop now records the owed stop with an internal
delegated_task.remote.stop command: it sets remoteChild.stoppedThere to
false and counts the stop, leaving the status, result and completion
delivery alone. The follower, restarted from the live event tail or by
the startup scan, sends that stop under a key per count and records it
as "stopped"; a "ran-out" report never clears it. One follower runs per
task, and a request while it runs makes it look once more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es keep the cause

delegate_task with environmentId launched in the linked project's checkout even when
isolation "worktree" was asked for. A worktree there would branch from a commit this
checkout has and the other may not, so the call is refused before launching, pointing
at t3_thread_launch with a worktree workspaceStrategy.

Recording the task or its cancellation no longer builds the caller's message from the
dispatch error; the cause goes to the log and the caller gets a fixed message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
delegate_task to a linked environment launches an ordinary thread there,
marked delegatedFrom, so a client connected to both machines listed it in
the sidebar as well as in the parent's lineage panel. Its approvals and
questions also never reached the parent here.

Lists now treat such a thread as a subagent when the client also lists
the thread that delegated it: the web sidebar and its project ordering,
mobile's thread lists, and notifications leave it out. A parent here is
flagged, and shows the "subagent needs input" banner, when the task or
one of its own subagents there asks. A task whose parent the client
cannot list (that machine switched off, or the parent gone) stays listed
on its own, so its work is never out of reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the t3code/peer/remote-delegate branch from 0d787b3 to 3dfeca1 Compare October 18, 2026 06:16
Lenoridev and others added 2 commits October 18, 2026 08:23
delegate_task to a linked environment launches an ordinary thread there,
marked delegatedFrom, so a client connected to both machines listed it in
the sidebar as well as in the parent's lineage panel. Its approvals and
questions also never reached the parent here.

Lists now treat such a thread as a subagent when the client also lists
the thread that delegated it: the web sidebar and its project ordering,
mobile's thread lists, and notifications leave it out. A parent here is
flagged, and shows the "subagent needs input" banner, when the task or
one of its own subagents there asks. A task whose parent the client
cannot list (that machine switched off, or the parent gone) stays listed
on its own, so its work is never out of reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the t3code/peer/remote-delegate branch from 3dfeca1 to 17e26cb Compare October 18, 2026 06:25
juliusmarminge and others added 12 commits October 17, 2026 23:26
A link's T3-Mode-Limit caps the modes of what it starts, but within those
modes it could still steer the user's own threads or change this
environment's projects and settings. Work a link starts is now fenced to
that link.

The linking side registers its OAuth client with the t3code-peer-link
software id; the receiving side signs that into the session's claims. Every
thread such a session launches carries an immutable linkOrigin, and so do
the subagents, forks and create_threads it derives. A client's own
thread.create cannot set one.

In the shared access declarations, a linked caller may change only threads
of the same link, and may not use writesEnvironment tools (projects,
settings), schedule tasks, or change scheduled tasks. Reads are not fenced.
Setup scripts are skipped for stamped threads in ProjectSetupScriptRunner,
which every path that runs them goes through.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Settings → Connections gains a Linked environments section, shown when the
server supports peer links and the session may manage access: each link with
whether it answers, its access, and a warning in its last five days, plus a
Link dialog (address, pairing code, access) and Forget. Mobile lists a
connected environment's links under Connections.

A thread a linked environment started shows "From <label>" next to its title
on web, and in the thread subtitle on mobile.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An environment can now sign in to another one's /mcp as an outside agent,
with a pairing code from that environment, without a browser. It drives the
peer's existing MCP OAuth flow server-side, keeps the token in the server
secret store and the rest in peer_environment_links (migration 059). The
peer lists the link as "T3 Code · <label>" in Connections, where it can be
revoked.

The token is only ever sent to an address whose descriptor answers as the
linked environment. PeerMcpClient calls the peer's tools and carries the
caller's limits in T3-Mode-Limit on every request. Linking refuses this
environment itself and peers that cannot honour that header.

`t3 environment link|list|unlink` and the peerLinks.* RPCs manage links.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
delegate_task takes target.environmentId (and optionally target.projectId).
The child then runs as an ordinary thread in the linked environment,
launched through the link with a key derived from the caller's
clientRequestId, in the project there with this thread's repository unless
one is named.

The parent here records the task with delegated_task.remote.request: its
node, subagent row and turn item, with remoteChild instead of a local child
thread. RemoteDelegation follows the thread there and completes the task
with delegated_task.remote.complete, which reuses the parent half of a local
finalize, so the parent wakes as it would for a local child. Open remote
tasks are followed again at startup. A revoked or expired link fails the
task with that reason; any other error backs off, up to five minutes.

task_status and mode=wait answer from the row, task_cancel interrupts the
thread there and completes the task as cancelled, and restart recovery no
longer treats a remote task as abandoned provider work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An agent can now work on a linked environment's threads through the same T3
MCP tools. t3_project_list, orchestrator_capabilities, t3_thread_launch,
t3_thread_list, t3_thread_read, t3_thread_send, t3_thread_wait and
t3_thread_interrupt take an optional environmentId; t3_environment_links
lists the links.

Each tool's own access declaration still runs here first, so a read-only or
ended caller is refused before anything leaves. The forwarded call carries
the caller's modes in T3-Mode-Limit, and the linked environment holds it to
those and the link's access. Retry keys are hashed with the caller's
namespace, since every caller here shares the link's one session there. A
forwarded wait goes out in pieces of at most 50 s so no route drops it idle.
A remote launch cannot carry attachments yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Read failed-run activity and prefer that run's error over an earlier assistant
reply. Keep successful replies unchanged and retrieve long errors in full
without taking diagnostics from a later run.

Add failed-run, pagination, run-isolation and successful-recovery regressions
for the existing review on pingdotgg#16719.

Implemented with GPT-6.1-Sol through Codex in T3 Code on behalf of Lenoridev.
When the task's run on the linked environment ended, the follower completed
the task with that run's reply, even while the thread there still waited on
its own subagents, background tasks or a completion delivery that would wake
it for the real answer. A local task waits for that work and takes its
result from the run that answered after it.

t3_thread_wait takes an optional untilDelegatedWorkSettled. With it, the wait
also blocks until delegatedTaskProgress reports result_available for the
thread, waking on task, provider-thread and run updates, and reports that
result run as resultRun. The follower passes it and reads the result from
resultRun. Both fields are optional: an older peer ignores the input and
answers as before, and the follower then falls back to the launched run.

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

Stop on a parent skipped a remote task that already had its result, so
follow-up work on its thread in the linked environment (a message sent
there with t3_thread_send, or a run that started after the result) kept
running. A local child is stopped on every Stop, result or not, and
cancelling a finished remote task already stops its thread there.

Stop now records the owed stop with an internal
delegated_task.remote.stop command: it sets remoteChild.stoppedThere to
false and counts the stop, leaving the status, result and completion
delivery alone. The follower, restarted from the live event tail or by
the startup scan, sends that stop under a key per count and records it
as "stopped"; a "ran-out" report never clears it. One follower runs per
task, and a request while it runs makes it look once more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es keep the cause

delegate_task with environmentId launched in the linked project's checkout even when
isolation "worktree" was asked for. A worktree there would branch from a commit this
checkout has and the other may not, so the call is refused before launching, pointing
at t3_thread_launch with a worktree workspaceStrategy.

Recording the task or its cancellation no longer builds the caller's message from the
dispatch error; the cause goes to the log and the caller gets a fixed message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
delegate_task to a linked environment launches an ordinary thread there,
marked delegatedFrom, so a client connected to both machines listed it in
the sidebar as well as in the parent's lineage panel. Its approvals and
questions also never reached the parent here.

Lists now treat such a thread as a subagent when the client also lists
the thread that delegated it: the web sidebar and its project ordering,
mobile's thread lists, and notifications leave it out. A parent here is
flagged, and shows the "subagent needs input" banner, when the task or
one of its own subagents there asks. A task whose parent the client
cannot list (that machine switched off, or the parent gone) stays listed
on its own, so its work is never out of reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the t3code/peer/remote-delegate branch from 17e26cb to 198c585 Compare October 18, 2026 06:28
juliusmarminge and others added 22 commits October 27, 2026 12:04
An OAuth client on /mcp is capped by the access it was approved with.
When another environment calls on behalf of one of its agents, that agent
may run under narrower modes, and the target has no way to know.

/mcp now reads `T3-Mode-Limit: <runtimeMode>/<interactionMode>` from OAuth
clients and caps the request at the narrower of the two. The header can
only narrow: a broader one changes nothing, and a malformed one is
refused with 400. The client's interaction mode can now be capped too;
before, it was always default.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A launch had no retry key, so an agent (or another environment calling on
its behalf) that lost the response could only guess whether the thread
was created, and a retry started a second one.

t3_thread_launch now takes an optional clientRequestId. The command,
thread and message ids derive from the caller and the key, and the launch
service already replays a command it accepted, so a retry returns the
first thread. Without a key every launch is new, as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An environment can now sign in to another one's /mcp as an outside agent,
with a pairing code from that environment, without a browser. It drives the
peer's existing MCP OAuth flow server-side, keeps the token in the server
secret store and the rest in peer_environment_links (migration 059). The
peer lists the link as "T3 Code · <label>" in Connections, where it can be
revoked.

The token is only ever sent to an address whose descriptor answers as the
linked environment. PeerMcpClient calls the peer's tools and carries the
caller's limits in T3-Mode-Limit on every request. Linking refuses this
environment itself and peers that cannot honour that header.

`t3 environment link|list|unlink` and the peerLinks.* RPCs manage links.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An agent can now work on a linked environment's threads through the same T3
MCP tools. t3_project_list, orchestrator_capabilities, t3_thread_launch,
t3_thread_list, t3_thread_read, t3_thread_send, t3_thread_wait and
t3_thread_interrupt take an optional environmentId; t3_environment_links
lists the links.

Each tool's own access declaration still runs here first, so a read-only or
ended caller is refused before anything leaves. The forwarded call carries
the caller's modes in T3-Mode-Limit, and the linked environment holds it to
those and the link's access. Retry keys are hashed with the caller's
namespace, since every caller here shares the link's one session there. A
forwarded wait goes out in pieces of at most 50 s so no route drops it idle.
A remote launch cannot carry attachments yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
t3_project_create and t3_project_clone take environmentId, so an agent can
bring a repository to a machine before launching or delegating work there.
Both still need full access here, and the linked environment holds the call
to the link's access.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s a linked environment

Forwarding was wired tool by tool, so only the first eleven tools took
environmentId. It now lives in McpToolAccess.toLayer: a tool whose
parameters take OrchestratorMcpEnvironmentTarget runs in the linked
environment it names once its declaration has checked the caller here.
The local handler never sees environmentId. Tools that act as the calling
thread itself cannot take one, and the layer refuses to build if they do.

Queues, pending requests, thread settings and organizing, forks and
merge-backs, scheduled tasks, pull requests, worktree refs, project
read/update/delete and environment preferences now reach a link too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A link's T3-Mode-Limit caps the modes of what it starts, but within those
modes it could still steer the user's own threads or change this
environment's projects and settings. Work a link starts is now fenced to
that link.

The linking side registers its OAuth client with the t3code-peer-link
software id; the receiving side signs that into the session's claims. Every
thread such a session launches carries an immutable linkOrigin, and so do
the subagents, forks and create_threads it derives. A client's own
thread.create cannot set one.

In the shared access declarations, a linked caller may change only threads
of the same link, and may not use writesEnvironment tools (projects,
settings), schedule tasks, or change scheduled tasks. Reads are not fenced.
Setup scripts are skipped for stamped threads in ProjectSetupScriptRunner,
which every path that runs them goes through.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A fleet host provisions machines by having an agent clone a repository there
and register it, which the fence refused as a change to the environment.
Adding a project leaves everything already there as it was, so linked work
may create and clone projects with full access; it still cannot change or
delete existing ones, settings, or scheduled tasks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A link's agents run as the receiving environment's user, so at full access
the fence only ever stopped T3's own tools, never what the agent could do.
It also kept a host from driving machines it fully trusts: answering a
pending question or steering a thread the user started there, updating a
project, or running the project's setup script in a worktree it launched.

The stamp now records the access the link was paired with, and only a link
below full access is fenced. Its work still carries linkOrigin, so clients
keep showing where it came from. Stamps from before access was recorded stay
fenced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings → Connections gains a Linked environments section, shown when the
server supports peer links and the session may manage access: each link with
whether it answers, its access, and a warning in its last five days, plus a
Link dialog (address, pairing code, access) and Forget. Mobile lists a
connected environment's links under Connections.

A thread a linked environment started shows "From <label>" next to its title
on web, and in the thread subtitle on mobile.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
delegate_task takes target.environmentId (and optionally target.projectId).
The child then runs as an ordinary thread in the linked environment,
launched through the link with a key derived from the caller's
clientRequestId, in the project there with this thread's repository unless
one is named.

The parent here records the task with delegated_task.remote.request: its
node, subagent row and turn item, with remoteChild instead of a local child
thread. RemoteDelegation follows the thread there and completes the task
with delegated_task.remote.complete, which reuses the parent half of a local
finalize, so the parent wakes as it would for a local child. Open remote
tasks are followed again at startup. A revoked or expired link fails the
task with that reason; any other error backs off, up to five minutes.

task_status and mode=wait answer from the row, task_cancel interrupts the
thread there and completes the task as cancelled, and restart recovery no
longer treats a remote task as abandoned provider work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Read failed-run activity and prefer that run's error over an earlier assistant
reply. Keep successful replies unchanged and retrieve long errors in full
without taking diagnostics from a later run.

Add failed-run, pagination, run-isolation and successful-recovery regressions
for the existing review on pingdotgg#16719.

Implemented with GPT-6.1-Sol through Codex in T3 Code on behalf of Lenoridev.
When the task's run on the linked environment ended, the follower completed
the task with that run's reply, even while the thread there still waited on
its own subagents, background tasks or a completion delivery that would wake
it for the real answer. A local task waits for that work and takes its
result from the run that answered after it.

t3_thread_wait takes an optional untilDelegatedWorkSettled. With it, the wait
also blocks until delegatedTaskProgress reports result_available for the
thread, waking on task, provider-thread and run updates, and reports that
result run as resultRun. The follower passes it and reads the result from
resultRun. Both fields are optional: an older peer ignores the input and
answers as before, and the follower then falls back to the launched run.

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

Stop on a parent skipped a remote task that already had its result, so
follow-up work on its thread in the linked environment (a message sent
there with t3_thread_send, or a run that started after the result) kept
running. A local child is stopped on every Stop, result or not, and
cancelling a finished remote task already stops its thread there.

Stop now records the owed stop with an internal
delegated_task.remote.stop command: it sets remoteChild.stoppedThere to
false and counts the stop, leaving the status, result and completion
delivery alone. The follower, restarted from the live event tail or by
the startup scan, sends that stop under a key per count and records it
as "stopped"; a "ran-out" report never clears it. One follower runs per
task, and a request while it runs makes it look once more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es keep the cause

delegate_task with environmentId launched in the linked project's checkout even when
isolation "worktree" was asked for. A worktree there would branch from a commit this
checkout has and the other may not, so the call is refused before launching, pointing
at t3_thread_launch with a worktree workspaceStrategy.

Recording the task or its cancellation no longer builds the caller's message from the
dispatch error; the cause goes to the log and the caller gets a fixed message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
delegate_task to a linked environment launches an ordinary thread there,
marked delegatedFrom, so a client connected to both machines listed it in
the sidebar as well as in the parent's lineage panel. Its approvals and
questions also never reached the parent here.

Lists now treat such a thread as a subagent when the client also lists
the thread that delegated it: the web sidebar and its project ordering,
mobile's thread lists, and notifications leave it out. A parent here is
flagged, and shows the "subagent needs input" banner, when the task or
one of its own subagents there asks. A task whose parent the client
cannot list (that machine switched off, or the parent gone) stays listed
on its own, so its work is never out of reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RemoteDelegation.start resumes every open remote task at once, and each
follower read its parent's whole subagent list to find one task, then held
it across a wait of up to ten minutes. A parent with 1,441 tasks (35 MB of
payload) and 122 open ones made every restart decode ~170k records and hold
8 GB, so the server ran out of heap seconds after starting and crash-looped.

getThreadRecords now takes a subagentIds filter. Followers, and task_status
waits that re-read on every parent subagent.updated, read just their task.
Against a copy of the affected database the startup fan-out drops from 8.2 GB
retained over 21.6 s to 8 MB over 61 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the t3code/peer/remote-delegate branch from ac028c0 to 8aefc97 Compare October 27, 2026 19:53
@juliusmarminge
juliusmarminge force-pushed the t3code/peer/remote-delegate branch from 8aefc97 to b7b14bf Compare October 27, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 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.

3 participants