Skip to content

Thread lifecycle: Settled as a real state, automatic settlement, idle auto-archive removed (B1) #526

Description

@Tryanks

Summary

Make Settled a real lifecycle state with automatic settlement, following T3 Code's current code (upstream commit 83a82a46b), and remove idle auto-archive. Phase B1 of the plan in #535. Source of truth is T3 Code's code; this text is corrected whenever it disagrees.

State

SessionMeta gains settled_override: Option<Settled | Active>, settled_at, unsettled_at, auto_settle_disabled_at. Active is the temporary keep-active written by explicit un-settle; auto_settle_disabled_at is the durable per-thread "never auto-settle" switch (menu: Auto-settle behavior → Disabled / Enabled). archived_at stays as it is.

Migration: an existing settled_at with no override loads as Settled. Threads auto-archived in the past stay archived.

Message origin and activity

Stored messages gain an explicit origin: human, agent, server. It is set at the host's send/steer/callback boundary and preserved through queue, steer, acknowledgement and persistence. Historical events: a message in a top-level thread is human; a message in a thread with a parent is model-dispatched.

Two clocks, derived from the event log on the host (never stored twice, no second log):

  • last user-role message (any origin), and latest run requested / started / completed → last activity;
  • last human-authored message → the anchor for PR-driven settlement (A2).

A rename is not activity. A queued or unacknowledged human send is pending user work. Metadata timestamps are seconds, events milliseconds; seconds values are never used as revision tokens.

Automatic settlement

  • Inactivity: last activity strictly older than auto_settle_after_days (default 3, numeric 1–90, null = never; per-project override). settled_at = the activity time. No activity timestamp at all → never ages; "never ran" alone is not an exemption.
  • PR terminal state: the rule in Source control: linked pull requests, sync, branch discovery, stacks, and linking tools (A2) #532, delivered with Source control: linked pull requests, sync, branch discovery, stacks, and linking tools (A2) #532's follow-up PR after both branches merge (it is unreachable without links, so B1 ships neither the rule nor the 2-minute grace, only the auto_settle_on_merge setting). auto_settle_on_merge default on, per-project override.
  • Blockers: archived; any override; pinned (Thread lifecycle: pinned threads and manual arrangement (B2) #528) or durably disabled; pending approval or input; a preparing / starting / running / waiting run; completion-holding background work (PR watch, subagent, the provider's own background tasks, an unsettled dispatched child; a plain command or dev server does not hold); a scheduled wake (usage-limit resume) still pending; a user-role message of any origin younger than 2 minutes with no run after it (upstream ThreadSettlementService.ts:86–104 uses latestUserMessageAt; only the PR anchor is human-authored); an open or unsynced visible PR.
  • Sweep: host-owned, runs with no client: on startup, one minute after each sweep drains, and immediately on relevant settings changes, PR sync, provider detach, terminal run states and confirmed merges. A decision commits only if the thread's decision revision is unchanged (not the seconds-resolution updated_at). Changing settings never reopens settled threads.
  • Dispatched execution children are exempt from automatic settlement: only the lead or the user settles them (see Thread lifecycle: root-only sidebar, Agents view, and orchestrate settle replaces archive (B1) #637).

Settle (manual or automatic)

Mutates the target thread only. The current descendant cascade and ancestor reactivation are removed.

  • Clears every PR watch, pin, active order and unsettled_at.
  • Detaches this thread's provider through the existing shutdown-to-idle path bound to the start generation, so a stale cleanup never stops a freshly restarted provider. Independent orchestrate children are separate sessions and are not stopped.
  • Closes only terminals proven idle by fresh subprocess inspection, keeps their output, leaves a terminal open when inspection fails or the thread was re-engaged. Busy terminals (dev servers) stay open.
  • Manual settle refuses while this thread has a queued, preparing, starting, running or waiting run, a pending approval or callback input, or a queued human message; queued automatic wakes and delegated-completion notices are cancelled instead.

Automatic un-settle

Only on accepted message admission at the host's send/steer/callback boundary (not the provider's later acceptance): clears Settled or Active, stamps unsettled_at when reopening from Settled. A valid child completion reopens its parent; a stale, archived or deleted delivery is rejected before any reactivation (today the parent is reactivated first, crates/runtime/src/app/orchestrate.rs:1239). Provider start and approval arrival do not reset the override.

Sidebar

Partition precedence: Settled override → pinned → active. Order: pinned, active, settled. Settled sorts by one resolved timestamp (settled_at, else latest activity, else updated) descending, id tie-break; collapsed by default, 10 rows then +25, the viewed row always visible. Active: unarranged rows by max(created, unsettled) descending, then arranged rows by key; activity never reorders. Pure classification, comparators and order-key generation live in core; expansion, pagination, drag and undo live in ui. The old family-based grouped and recent comparators are removed. Roster: see #637 (root threads and forks only).

Undo

Five-second toast with Undo (mod+z when no text field or terminal has focus) for settle, unpin and archive. Undo sends the reverse command: un-settle plus re-pin with the former key, re-pin, unarchive. It never restores watches, provider sessions or exact timestamps.

Remove idle auto-archive

Delete auto_archive_disabled, auto_archive_max_idle_days, auto_archive_keep_count, auto_archive_notice_shown, auto_archive_candidates, Command::AutoArchiveSweep, ArchivedCount, the sidebar-triggered sweeps, the notice rows and dialog, settings inputs, both locales' strings, and tests that exist only for them. Explicit archive, its UI and descendant collection stay. Auto-archive never deleted logs or worktrees, so no retention policy replaces it. Settled rows stay in the index snapshot where archived rows did not: measure one representative large profile's sweep time and index size before claiming equivalence. Resolves #515.

Protocol

Commands: settle, unsettle, set auto-settle enabled; settings patch fields with project overrides. Wire notes under PROTOCOL_VERSION, no bump in the PR.

Known staged gap

Until #532 lands, a thread with an externally open but unlinked PR can inactivity-settle (upstream's no-link path behaves the same). Settled threads are not reopened by later discovery.

Evidence required

At the runtime command boundary: old settled data stays settled after restart; a non-resident thread ages from activity, not rename; queued human work blocks while queued automatic notices are cancelled; a valid child completion reopens only its parent, a stale or archived one does not; settle then a new send cannot lose the new provider or terminal input; a real idle shell is closed and a busy one kept; one large profile measured. Full CONTRIBUTING checks, both themes, phone geometry.

Reference

T3 Code 83a82a46b: apps/server/src/orchestration-v2/ThreadSettlementService.ts, Orchestrator.ts (settle/unsettle/pin, message dispatch), ProjectionStore.ts (candidates, activity), packages/contracts/src/orchestrationV2.ts, packages/client-runtime/src/state/threadSort.ts, docs/user/thread-sidebar.md.

Activity

  1. changed the title [-]Thread lifecycle: make Settled a real state and remove auto-archive[/-] [+]Thread lifecycle: Settled as a real state, automatic settlement, idle auto-archive removed (B1)[/+] on Oct 8, 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

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions