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
- Cut
blocked? (lean: yes — top of restore list, but the signal may come fast)
- Accept losing subagent task-binding for now?
- Is
failed truly redundant once subagent outcomes stop auto-writing?
completionEvidence: cut entirely, or keep as typed references?
- Is 20+3 the right cap? Derive from the 8000-char budget with real renders before merging.
- 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 换紧凑随机)、subject、status 3 态。砍:key(与 id 合一)、createdAt/updatedAt/endedAt、parentId(~150 行逻辑买一个本应是扁平的设计)、owner(AgentRun 都没有这字段)、三个证据字段(模型写的文本是声明不是证据)、resumeTrust(F1)。
机制:砍事件日志+投影表(换一张规范化当前态表)、key 分配/backfill、7 天归档、分页/revision、claim/settle/claimAvailable、迁移白名单+证据规则、task_list/task_get、legacy 别名;200 上限收紧到 20+3。保留:tail 注入(特性本身)、脱敏(安全)、host 单一权威(权威的真正内容)、Desktop 面板(字段简化)。
工具 4→2:task_create、task_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;保守方的意见变成了加回清单。
开放问题
- 砍
blocked?(倾向砍,但它是加回清单榜首)
- 暂时接受失去子代理任务绑定?
- 子代理不再自动写回后
failed 还必要吗?
completionEvidence 彻底砍还是留强类型引用?
- 20+3 是正确上限吗(用真实渲染从 8000 字符反推验证)?
- 上限调整与砍
task_list 必须原子落地?
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)resumeTrustis dead. Only production caller classifies with empty refs: 5 levels, 3 reachable;untrustednever occurs, sofilterModelVisibleTaskLedgerTasksis the identity function. A corrupt ledger throws on read rather than being classified.listActionableTaskKeysfilters onpending/in_progressonly. The belief that trust had a consumer is why it survived.claimAvailableis dead code through 4 layers, zero callers.omittedCountmisreports: 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.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)
idkeysubjectstatuscreatedAt/updatedAtendedAtparentIdownerblockedReason/failureReason/completionEvidenceresumeTrustMechanisms
revision_changed/ revisionclaim/settleAgentOutcomeclaimAvailableexplicitReopen+ evidence rulestask_list/task_gettask.ledger.queryTools: 4 → 2 (
task_create,task_update).5. Target shape
6. Restore list (what we expect to add back, and the signal)
blockedcompletionEvidenceclaim/settletaskIdfield7. Migration
No DROP. Import existing tasks (id/subject/status) into the new table. Update
operational-state-backup.tsmanifest. Panel + lifecycle doc update in the same PRs.8. Rollout
task_get, legacy aliases, archive,endedAt. ~20% of surface, independently reviewable.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
blocked? (lean: yes — top of restore list, but the signal may come fast)failedtruly redundant once subagent outcomes stop auto-writing?completionEvidence: cut entirely, or keep as typed references?task_listremoval 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 上验证过)
resumeTrust是死的:唯一调用点传空 refs,5 档只有 3 档可达,untrusted永不产生,过滤函数是恒等函数;账本坏了是读不出来而不是被分类。claimAvailable死代码:穿透 4 层,零调用。omittedCount恒定误报:把策略性不选的终态也算省略,每轮怂恿模型调 task_list。判据
代码可逆(git)、数据可逆(不 DROP)、模型契约可逆。唯一不可逆的是删用户数据——所以全砍,旧表原地保留只读。
裁决
字段 13→3:保留
id(uuid 换紧凑随机)、subject、status3 态。砍:key(与 id 合一)、createdAt/updatedAt/endedAt、parentId(~150 行逻辑买一个本应是扁平的设计)、owner(AgentRun 都没有这字段)、三个证据字段(模型写的文本是声明不是证据)、resumeTrust(F1)。机制:砍事件日志+投影表(换一张规范化当前态表)、key 分配/backfill、7 天归档、分页/revision、
claim/settle/claimAvailable、迁移白名单+证据规则、task_list/task_get、legacy 别名;200 上限收紧到 20+3。保留:tail 注入(特性本身)、脱敏(安全)、host 单一权威(权威的真正内容)、Desktop 面板(字段简化)。工具 4→2:
task_create、task_update。目标形态
加回清单(按信号)
blockedcompletionEvidenceclaim/settle落地
P0 删死代码零行为变化(F1/F4/F5、分页、task_get、别名、归档、endedAt)→ P1 字段收敛 → P2 存储简化(单表、copy 改快照)→ P3 规模/工具(20+3、砍 task_list、无分页查询)。每阶段独立 PR。
做过两次独立外部架构评审(一激进一保守),都确认过度设计并独立验证了 F1–F6;保守方的意见变成了加回清单。
开放问题
blocked?(倾向砍,但它是加回清单榜首)failed还必要吗?completionEvidence彻底砍还是留强类型引用?task_list必须原子落地?