镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Remove the Settled state for tasks and the UI flows that go along with it #1527

Open

Description

@marksftw

Research for removing the Settled task state, so a task has exactly two states: active and archived (deleted stays a separate, terminal operation that removes the row).

Findings below are from origin/master @ a9007473. No code was changed.


1. What "Settled" is today

A task's state is a three-value ladder, stored in one column and passed through two Rust enums:

Layer Anchor
SQLite column + CHECK apps/maple-agent/crates/maple-agent/src/store/schema.rs:105 — state TEXT NOT NULL CHECK (state IN ('active','settled','archived'))
Store enum store/tasks.rs:41 TaskState { Active, Settled, Archived } (as_str, read_task at :1465)
Service enum (serialized to the app) agent/types.rs:839 AgentTaskState — #[serde(rename_all = "camelCase")], so the wire values are `"active"
Deleted not a state. tasks.deleted_ms starts deletion and the collector removes the row (schema.rs comment; store/gc.rs)

Behaviour attached to settled specifically:

  • A run wakes it. Starting any run on a settled task flips it to active first (agent/runs.rs:1445 wake(), called from run admission). So "settled" means "no run since I last put it away".
  • Running tasks refuse to leave Active. set_session_state funnels non-active moves through maintenance_while_idle and reports "Stop the running agent before changing this task's state" (agent/tasks.rs:638).
  • Sidebar has a Settled section and its own page. Active and settled are two independent keyset pages per project: TaskSection::State(Active) and TaskSection::State(Settled) (store/paging/navigation.rs:459-495), each with its own cursor (NavigationProjectGroup.more = {active, settled}).
  • Status grouping (SidebarGroup::Status) exists only to split Active from Settled (app/src/ui/chat/sidebar.rs:1770-1775).
  • Hover "step" ladder: active → Settle → settled → Archive → archived → Delete (sidebar.rs:417-475).

So "removing Settled" is not a one-line enum delete: it collapses a two-page, two-cursor, two-section, two-group model into one, and it changes a persisted SQL constraint and two versioned wire protocols.


2. Impact by layer

Reference count of typed Settled references (enum variants, section/page kinds, wire strings): ~134 across 17 files, in apps/maple-agent. Highest density is the sidebar paging/collection code, not the store.

A. Data model and storage

  • store/tasks.rs: TaskState::Settled, SerializedTaskSection::Settled (:137-162, this is the plain-string wire shape {"section":"settled"}), read_task (:1467), project_groups SQL t.state IN ('active','settled') (:1067 — projects must still group while their only tasks were settled).
  • store/schema.rs: the table CHECK and the four partial indexes that filter on state. Migration chain is append-only (VERSION = 5, MIGRATIONS at :309); validate_version replays migrations 1..=N as the reference schema (:420) and diffs real schema rows, so any migration must be deterministic.
  • Migration decision (the main technical fork). SQLite cannot drop a value from a CHECK in place:
    • (recommended, low risk) migration 6 = UPDATE tasks SET state='active' WHERE state='settled', leave the column CHECK permissive. Storage tolerates a value the code never writes. No table rebuild, no FK churn.
    • (strict) migration 6 rebuilds tasks (new table, copy with identical ids, drop, rename) and recreates 7 indexes. Six child tables reference tasks(id) with ON DELETE CASCADE (schema.rs:151,167,188,214,221,271), so this needs foreign-key-safe ordering and a full verification pass. Only worth it if we want the constraint to be exact.
  • store/integrity_constraints.rs:88 asserts the literal constraint text state IN ('active','settled','archived') — unchanged under the permissive option, must be updated under the strict one.
  • crates/store-bench/src/run.rs:416 seeds a third of its tasks as settled (bench distribution assumes three states).

B. Service / runtime API

  • agent/types.rs: drop the variant, and AgentTaskState::gerund() (:855) loses its "settling" arm.
  • agent/store.rs:540,562: bidirectional store↔service mapping.
  • agent/surface.rs:337: parses the task state string ("settled"), errors on unknown values — a legacy "settled" row would break surface attach unless it maps to Active.
  • agent/runs.rs:1445: wake() becomes dead code and should be deleted, along with its test.
  • agent/tasks.rs:638: doc + guard; with two states the guard is just "archived needs idle".
  • acp/mod.rs:500: ACP delete_session archives — unaffected, but ACP test fixtures reference the enum (acp/tests.rs).

C. Wire formats — this is the release risk

TaskSection and the state string cross two versioned protocols, both with hard version refusal:

  1. app ↔ local host: app/src/host/protocol.rs VERSION = 3; NavigationTaskQuery travels as JSON params (app/src/backend/paging.rs:360,425).
  2. app ↔ remote execution computer: app/src/remote/wire.rs VERSION = 7, minimum 7, older peers get an explicit refusal (docs/remote-access.md:231).

Removing the "settled" variant means an old peer's {"section":"settled"} fails to deserialize ("Invalid host paging parameters") and a new app talking to an old remote (or the reverse) breaks paging. Options: bump both versions in the same release, or keep a transitional "settled" => active alias in deserialization for one release. Paging cursors embed the section in their scope and fail closed (store/paging/navigation.rs:1012); paging state is in-memory only, so a relaunch clears them — not a data migration problem.

NavigationProjectGroup.more = ProjectTaskCursors { active, settled } is also wire shape and must collapse to a single cursor.

D. Desktop UI

  • sidebar.rs: SidebarSection::Settled (:492-527, label + section-toggle-settled id), TaskRung::Settled / TaskMove::Settled (:417-475), menu_settle_id + settle-task-{id} element id (:283,320), menu composition "Settle task" (:2619), TaskAction::SetState mapping (:2704,2736), settled_rows() / settled_expanded, section assembly partition (:1770), session_is_active() (:1823 — becomes always-true and should go), page/LoadMore wiring (:1884-1892), plus two visual tests (:4799,4830).
  • sidebar_paging.rs: PageKind::Settled (40 refs) — page ownership, ready/more/ids sets, apply_live_summary target mapping (:1161), load_page section mapping (:1274), and the page-label table (:812).
  • sidebar_prefix.rs:101: the local prefix keeps two streams (Active, Settled); becomes one.
  • sidebar_ordering.rs:371: TaskGroup::Status(state == Settled && !running) — the status grouping's second bucket.
  • sidebar_computers.rs (~20 refs): per-computer paging mirrors the same split — ComputerPageKind::Tasks { settled: bool }, Project { root, settled }, next_active/next_settled, concurrent dual queries (:232-282), and state == "settled" bucketing (:427).
  • sidebar_catalog.rs:48: comment/logic for "independently read active/settled prefixes are combined".
  • computers.rs:38,138, dialogs.rs:421 (doc), mod.rs:1108-1160 test helpers (sidebar_settled, settle_task, unsettle_task), backend.rs:258/backend/local.rs:2441 (doc comments on set_session_state), backend/paging.rs:132 (string→enum mapping; its _ => Active fallback already gives graceful legacy handling).

Product questions the UI raises (see §4).

E. Tests to rewrite (not just recompile)

app/src/ui/chat/tests.rs — several tests are the settle behaviour: sections split pinned/active/settled, "settling a task moves it out of the inbox", "state records move a task back and forth", "a run shows a settled task active", ladder/menu/step assertions.
sidebar_paging.rs has ~40 in-file tests asserting Settled page membership and cursors.
store/tasks_tests.rs, store/paging/navigation_tests.rs, agent/tests.rs (a_run_wakes_a_settled_task_and_a_started_task_switches_models, also named in docs/scenario-checklist.md C7), agent/store/tests.rs.
Note test fixtures set SidebarGroup::Status by default (sidebar.rs:1498), so many sidebar tests implicitly depend on the Active/Settled split and will need re-baselining.

F. Docs

Only apps/maple-agent/docs/scenario-checklist.md:34 (C7) names the settled behaviour via a test name. docs/local-store.md does not document state values.


3. Explicitly not in scope (name collisions)

Do not touch these; they are unrelated uses of "settle":

  • crates/pi-coding-agent: AgentSettled, AgentSessionEvent::Settled, AgentBeforeSettle — run/turn lifecycle hooks.
  • agent/timeline.rs settle_turn_permission_items, INTERRUPT_SETTLE_TIMEOUT, popup.settle(), maintenance_tests::settle() (test helper that pumps the UI), Promise.allSettled in Maple Research.

4. Decisions needed before implementation

  1. Where do existing settled tasks go on upgrade? Recommended: active (visible in the inbox; the user can archive). Archiving would silently hide work that was merely "put away". Nothing else in the app implies a different answer — a settled task is already listed and openable.
  2. Does the sidebar keep a section split at all? After the change the unarchived list is one bucket. Proposal: keep Pinned + the per-project/connection groups + the Archived fold, and drop the Active/Settled section labels together. This is the largest UX change and the one to confirm first.
  3. What happens to the "Status" sidebar grouping (SidebarGroup::Status)? Its only two buckets were Active and Settled, so it degenerates. Options: remove the option (persisted "status" already falls back to Project via #[serde(other)]), or keep it as "Active vs Archived".
  4. Hover step button on an active task. Today it is "Settle" (check icon). It would become "Archive" (archive icon), with Delete appearing only on archived rows. Confirm that two rungs (active → archived → delete) is the intended ladder.
  5. Protocol strategy: single release with both wire versions bumped (host v3, remote v7), or one release of "settled" => active alias tolerance first.

5. Suggested implementation order

  1. Store: drop TaskState::Settled; one-section queries; project_groups uses state='active'; migration 6 normalizes rows; update integrity_constraints + bench.
  2. Service: drop AgentTaskState::Settled, delete wake(), map legacy "settled" → Active in surface.rs / backend/paging.rs / sidebar.rs:141.
  3. Paging/navigation: single cursor per project group; delete the second stream and the Tasks { settled } / Project { root, settled } page kinds; keep cursors failing closed.
  4. UI: one unarchived section, menu/step ladder, PageKind::Settled removal, session_is_active removal, unread-marker comment.
  5. Wire: bump host::protocol::VERSION and remote::wire::VERSION, update docs/remote-access.md.
  6. Tests + docs: rewrite the settle-specific tests, update scenario checklist C7, run just ci in apps/maple-agent.

Rough size: one focused PR is feasible but large — the store+service+wire half is mechanical once (1) is decided (≈1 day), and the sidebar/paging half is the risky part because it is where settled is load-bearing (≈2-3 days including tests). Choosing the permissive CHECK removes the only genuinely fiddly migration.

6. Notes

  • Validation must be Local lane: backend/store change, schema migration, and app↔host protocol change. Needs: fresh-store open, upgrade-open from a store with settled rows, and a paired-remote paging check.
  • Prior art: PR Agent: sink settled tasks within their project #1456 (closed) — "Agent: sink settled tasks within their project" — reworked settled ordering inside the project view (sidebar.rs, sidebar_paging.rs, tests). That diff is exactly what this change deletes, so it must not be revived as-is.

Activity

  1. marksftw commented on Nov 10, 2026

    @marksftw
    CollaboratorAuthor

    I have an agent taking a stab at this.

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