Skip to content

RFC: Radically slim the Session Task Ledger #2290

Description

@Astro-Han

RFC: Radically slim the Session Task Ledger

Status: Draft. Scope: task-ledger in core / storage / runtime-host / runtime + desktop panel + docs/session-task-ledger-lifecycle.md.

TL;DR

The ledger exists for one reason: the model's work plan lives in a context that is volatile, private, bounded, and expensive — so it must live outside the conversation, in a persistent, authority-owned, session-scoped state re-injected each turn at bounded cost. That goal needs only three things: a persisted task list, task_create/task_update, and turn-tail injection. In one month the feature grew from 5 fields / 2 tools / one JSON file to 13 fields / 4 tools / event-sourced dual tables + a paginated query protocol. Proposal: cut every element whose removal loses nothing irreversible (code is in git, data stays in place), and keep an explicit restore-on-signal list for what we expect to miss.

1. First principles

Volatile/private/bounded context → plan must be external → session-scoped with stable identity → model needs create/update → transitions validated by a system authority → injection has a hard budget, scale derived from it → writes atomic.

Not derivable from this goal: event sourcing, task trees, subagent ownership, audit, time-based archiving, pagination, revisions, a 5-level trust taxonomy, free-text evidence. Each must justify itself independently.

2. Diagnosis (all verified on current main)

  • F1 — resumeTrust is dead. Only production caller classifies with empty refs: 5 levels, 3 reachable; untrusted never occurs, so filterModelVisibleTaskLedgerTasks is the identity function. A corrupt ledger throws on read rather than being classified.
  • F2 — the "Goal trust gate" is a status gate. listActionableTaskKeys filters on pending/in_progress only. The belief that trust had a consumer is why it survived.
  • F3 — the projections table is written, never read. Every read replays all events; the write path commits events and projection in two transactions and signals before the projection is written.
  • F4 — claimAvailable is dead code through 4 layers, zero callers.
  • F5 — omittedCount misreports: counts policy-excluded terminal tasks as "omitted", so every turn the model is told "N omitted — use task_list" for tasks that weren't budget-omitted at all.
  • F6 — one rule, six authorities. "Completion requires evidence" is enforced in zod, normalize, validate, replay, read-validation, and wire projection. This duplication, more than any field, explains 1092 lines.

3. The criterion

Code is reversible (git). Data is reversible (no DROP). Model contract is reversible (models re-adapt). The only irreversible act is deleting user data. Therefore: cut everything, keep old tables in place read-only.

4. Verdict

Fields (13 → 3)

Field Verdict Why
id keep (uuid → compact random id) foundation
key cut (merge into id) dual identity + dual allocation paths; key is just id's display form
subject keep the payload
status 6 → 3 (pending/in_progress/completed) blocked/failed/cancelled are execution details; top restore candidate
createdAt / updatedAt cut insertion order suffices
endedAt cut with archiving
parentId cut ~150 lines of core for a hierarchy the original design said was flat; the one real invariant (parent-before-child) can return standalone
owner cut subagent binding belongs to AgentRun, which has no such field — the product never asked for it
blockedReason / failureReason / completionEvidence cut model-authored text is a claim, not evidence (real evidence lives in tool results/files/git/tests)
resumeTrust cut F1

Mechanisms

Mechanism Verdict Why
Event log + projection table cut → one normalized current-state table F3; copy becomes a snapshot; audit belongs to RuntimeEvent
Key allocation / backfill cut identity merged
7-day archive cut cap never freed terminal slots; renderer selector already governs budget
Pagination / cursor / revision_changed / revision cut ≤2 pages under 200 cap, server does full list + slice anyway, Desktop never uses it
claim / settleAgentOutcome cut real regression (production callers exist) but reversible; second goal deserves its own system
claimAvailable cut F4
Transition whitelist + explicitReopen + evidence rules cut validate enum membership only; system doesn't endorse self-reports either way
task_list / task_get cut cap drops to 20 active + 3 terminal, full ledger always fits the tail — "tail makes list redundant" becomes true again
Legacy aliases cut one-month-old interface
200-task cap tighten to 20+3 derived from the 8000-char budget (open question 5)
Paginated task.ledger.query simplify to one full-read query shared by Desktop + TUI
turn-tail injection keep the feature itself
Redaction / tag stripping keep security boundary
Host single-writer + admission gate keep this is what authority means, not complexity
Desktop panel keep, simplify fields user-visible

Tools: 4 → 2 (task_create, task_update).

5. Target shape

interface Task { id: string; subject: string; status: 'pending'|'in_progress'|'completed'; }
// session_tasks(session_id, task_id, subject, status, seq)
// tail each turn: 20 active + 3 recent terminal, ≤8000 chars, stable tail truncation
// old tables kept in place, no longer written

6. Restore list (what we expect to add back, and the signal)

Cut element Signal Cost
blocked models keep writing "waiting for user" into subjects 1 enum + 1 render line — most likely
completionEvidence users: "says done, no trace why" restore as typed refs (runId/toolCallId), not text
claim/settle the day AgentRun grows a taskId field design as its own system
Event log conversation copy needs precise turn history zero (tables kept)
Revision TUI stale-response bugs one integer

7. Migration

No DROP. Import existing tasks (id/subject/status) into the new table. Update operational-state-backup.ts manifest. Panel + lifecycle doc update in the same PRs.

8. Rollout

  • P0 — delete dead code, zero behavior change: F1/F4/F5 fixes, pagination, task_get, legacy aliases, archive, endedAt. ~20% of surface, independently reviewable.
  • P1 — field convergence: 3 statuses, id/key merge, cut parentId + evidence + owner.
  • P2 — storage simplification: single table, copy → snapshot, backup manifest.
  • P3 — scale/tools: cap 20+3, cut task_list, unpaginated query.

Each phase = one PR, own verification. Two independent external architecture reviews (one radical, one conservative) were run on this exact question; both confirmed over-design and verified F1–F6. Their conservative positions became §6.

9. Open questions

  1. Cut blocked? (lean: yes — top of restore list, but the signal may come fast)
  2. Accept losing subagent task-binding for now?
  3. Is failed truly redundant once subagent outcomes stop auto-writing?
  4. completionEvidence: cut entirely, or keep as typed references?
  5. Is 20+3 the right cap? Derive from the 8000-char budget with real renders before merging.
  6. Cap change and task_list removal must land atomically (one decision)?
简体中文要点版

RFC:Session Task Ledger 激进瘦身

状态:Draft。范围:core/storage/runtime-host/runtime 的 task-ledger + desktop 面板 + 生命周期文档。

TL;DR

这个特性存在的唯一理由:模型的工作计划在易失、私有、昂贵的上下文里,必须外置成持久、权威、每轮受限预算注入的 session 级状态。 这个目标只需要三样东西:持久化任务列表、task_create/task_update、turn-tail 注入。一个月里它从 5 字段/2 工具/单文件 JSON 长成 13 字段/4 工具/事件溯源双表+分页协议。提议:凡是砍掉后没有不可逆损失的(代码在 git 里、数据原地保留)全部砍掉,并维护"按信号加回"清单。

实测诊断(都在 main 上验证过)

  • F1 resumeTrust 是死的:唯一调用点传空 refs,5 档只有 3 档可达,untrusted 永不产生,过滤函数是恒等函数;账本坏了是读不出来而不是被分类。
  • F2 "Goal 信任门"实际是状态门:只按 pending/in_progress 过滤,trust 不参与。
  • F3 投影表只写不读:每次读都全量重放事件;写入还是两个事务、signal 先于投影。
  • F4 claimAvailable 死代码:穿透 4 层,零调用。
  • F5 omittedCount 恒定误报:把策略性不选的终态也算省略,每轮怂恿模型调 task_list。
  • F6 一条规则六个权威:"完成必须带证据"写在 6 处——这比任何字段都更能解释 1092 行。

判据

代码可逆(git)、数据可逆(不 DROP)、模型契约可逆。唯一不可逆的是删用户数据——所以全砍,旧表原地保留只读。

裁决

字段 13→3:保留 id(uuid 换紧凑随机)、subjectstatus 3 态。砍:key(与 id 合一)、createdAt/updatedAt/endedAtparentId(~150 行逻辑买一个本应是扁平的设计)、owner(AgentRun 都没有这字段)、三个证据字段(模型写的文本是声明不是证据)、resumeTrust(F1)。

机制:砍事件日志+投影表(换一张规范化当前态表)、key 分配/backfill、7 天归档、分页/revision、claim/settle/claimAvailable、迁移白名单+证据规则、task_list/task_get、legacy 别名;200 上限收紧到 20+3。保留:tail 注入(特性本身)、脱敏(安全)、host 单一权威(权威的真正内容)、Desktop 面板(字段简化)。

工具 4→2task_createtask_update

目标形态

interface Task { id: string; subject: string; status: 'pending'|'in_progress'|'completed'; }
// session_tasks(session_id, task_id, subject, status, seq)
// 每轮 tail:20 非终态 + 3 终态,≤8000 字符;旧表保留不再写

加回清单(按信号)

元素 信号 成本
blocked 模型老把"等用户"写进 subject 1 枚举+1 行渲染,最可能
completionEvidence 用户抱怨"说做完了没痕迹" 以强类型引用回归,非文本
claim/settle AgentRun 长出 taskId 那天 作为独立系统
事件日志 会话分叉要精确历史 零(表没删)
revision TUI 迟到响应 bug 一个整数

落地

P0 删死代码零行为变化(F1/F4/F5、分页、task_get、别名、归档、endedAt)→ P1 字段收敛 → P2 存储简化(单表、copy 改快照)→ P3 规模/工具(20+3、砍 task_list、无分页查询)。每阶段独立 PR。

做过两次独立外部架构评审(一激进一保守),都确认过度设计并独立验证了 F1–F6;保守方的意见变成了加回清单。

开放问题

  1. blocked?(倾向砍,但它是加回清单榜首)
  2. 暂时接受失去子代理任务绑定?
  3. 子代理不再自动写回后 failed 还必要吗?
  4. completionEvidence 彻底砍还是留强类型引用?
  5. 20+3 是正确上限吗(用真实渲染从 8000 字符反推验证)?
  6. 上限调整与砍 task_list 必须原子落地?

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions