Skip to content

Non-consuming visibility or startup hold for delegated-completion deliveries before server restart #32202

Closed

Description

@ViperJuice

Context

Version: t3 0.1.8-nightly.20261106.4275, binary sha256 fa4e92dc049aed9ec6fb09c49a3df982fc67798ef6fc7cb8052bfa0ebf7a9c8b.

We run planned host maintenance in two phases:

  1. Stop the T3 server.
  2. Later, start T3 again.

After the restart, no agent turn may begin in a defined set of threads until an operator sends the first message.

Two startup paths can begin turns without a user message:

  • Restart continuations. They are already controllable. The project override projectSettingsOverrides[projectId].continueThreadsAfterServerUpdate=false gates both the shutdown/reconcile prepare and the continuation sender at execution time. t3_thread_wait with untilDelegatedWorkSettled:true already exposes running, prepared-continuation and pending-continuation-effect state without consuming anything.

  • Delegated-completion delivery. This is the remaining gap. recoverDelegatedTasks → offerDelegatedCompletionDelivery offers a parent turn for every run where all of these hold:

    • delegatedCompletion.disposition === "open";
    • delivery is non-null with taskIds;
    • the thread is not archived or deleted;
    • the delivery message is absent or is an undelivered mailbox steer.

    No supported read exposes this state. No setting suppresses it.

The existing reads do not cover it:

  • task_status and t3_thread_read mark results delivered.
  • t3_thread_wait covers child-side completionDelivery pending/claimed only.
  • t3 thread show always performs a message-page read.

Request: one of the following

Option A: read-only query (preferred)

Add a non-consuming deliveryRecovery object for a thread to two surfaces: t3_thread_read (any view) and a CLI form usable from an independent console while the server runs, t3 thread show <id> --json --state-only. --state-only must not read messages.

The object:

deliveryRecovery: {
  evaluatedAt, eventSequence, serverVersion,
  openDelegatedCompletionOffers: [            // exactly the offerDelegatedCompletionDelivery precondition
    { parentRunId, generation, messageId, taskIds, messageState: "absent"|"undelivered_steer"|"delivered" }
  ],
  childCompletionDeliveries: [                // every subagent/delegated task of this thread
    { taskId, childThreadId, remoteEnvironmentId|null, state: "pending"|"claimed"|"delivered"|"acknowledged"|"disposed"|null }
  ],
  restartContinuation: {                      // same functions startup uses
    enabled, source: "global"|"project"|"policy",
    candidateRunId|null,                      // restartContinuationRun() after restartContinuationBlocked()
    pendingEffects: [{ effectId, runId, status }]
  },
  wouldStartTurnOnRestart: boolean,           // OR of the above under current settings
  complete: boolean, errors: []               // never silently omit inaccessible/remote/native records
}

Add the CLI listing t3 thread list --all-projects --include-subagents --json. Each row must include worktreePath and parentThreadId, and the output must report pagination completeness. An operator can then build the scoped set without an agent session.

Reads must never change completionDelivery, a cohort disposition or message delivery. This must be documented. The same non-consumption guarantee should be documented for t3_thread_wait.

Option B: startup hold

Add a persisted hold that an operator can set before stop and that survives the restart, for example t3 service hold-recovered-work on. Optional scope: --project <id>. While the hold is on, startup:

  • reconciles as today;
  • does not claim provider-runtime.continue effects (the existing claimNext({ excludeRestartContinuations }) path);
  • does not call offerDelegatedCompletionDelivery;
  • keeps queued runs queueHeld.

Also add:

  • t3 service hold-recovered-work status --json, which lists the held offers and continuations by thread and run and reads nothing else;
  • ... release [--thread|--project|--all];
  • ... discard, which disposes held items explicitly and records it.

Acceptance scenarios

Each scenario is seeded and then followed by a controlled restart, unless it says otherwise.

  1. Completed parent, child result not yet delivered. Before stop, the query reports a non-empty openDelegatedCompletionOffers and wouldStartTurnOnRestart=true. Under Option B with hold on, a restart starts zero parent turns. Status lists the offer, and release starts exactly one.
  2. Parent that already consumed its completion. The query reports empty offers and wouldStartTurnOnRestart=false. A restart starts zero turns.
  3. Idempotent reads. Calling the query, CLI --state-only or t3_thread_wait(untilDelegatedWorkSettled) 10 times leaves delivery states, disposition and event sequence unchanged (no delivery events emitted). A later normal delivery still happens.
  4. Cross-project. Parent and child are in different projects, and one child is in a linked remote environment. Both appear, or complete=false with an error is returned. No silent omission.
  5. Restart continuation. A running thread with a live turn, plus a completed run with open awaited background work, both report a candidateRunId while continuation is enabled. With the project override false, enabled=false and a restart starts zero continuation turns.
  6. Prepared continuation. After a graceful stop with continuation enabled, the query is evaluated against the persisted state at next start (Option B status). It lists the prepared continuation without running it.
  7. Queue. Queued runs remain queueHeld and are reported. No queued turn starts before release.
  8. Independent console. Every scenario above can be evaluated by the CLI with no agent session running.

Non-goals

  • No requirement that unrelated projects be idle.
  • No direct database access, internal RPC or undocumented flags for operators.
  • This is unrelated to agent-harness publication replay (agent-harness#1849).

Activity

  1. locked and limited conversation to collaborators on Nov 6, 2026
  2. converted this issue into a discussion #32204 on Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions