Files
Jyotisha/PROGRESS-report-writer-failure-20260902.md
T
Jesse_ChenandCursor 5c0bec0c94
Independent Staging Quality Gate / validate (push) Successful in 7m56s
Independent Staging Quality Gate / publish (push) Successful in 1m49s
fix(report): keep personal-report heartbeats ref'd during chapter writes
Staging lost the job lease mid-chapter because the unref'd interval never
fired outside an HTTP request. Refresh the lease on section progress and
leave the heartbeat timer ref'd so a long model await cannot starve it.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 02:13:13 +08:00

10 KiB
Raw Blame History

PROGRESS · 个人报告写作阶段 report_schema_invalid2026-09-02

任务书:TASK-report-section-writer-failure-20260902.md838d9e76 分支:codex/report-writer-failure-20260902 基线:origin/staging @ 838d9e76

开工前置:已读 docs/research/pre_work_error_ledger.mdfrontend/AGENTS.mddocs/BUG_HISTORY.md 检索 BUG-352 / 451 / 486489 / 495。按任务书从 origin/staging 建独立 worktree,不碰原仓脏树。

隐私:下文只有错误码、token 计数、finishReason、时间戳与 request UUID(任务书已公开)。无 prompt、bundle、模型原文、姓名、出生资料或账号标识。


任务 0 · Staging 实证(门控)

取证主机:staging 118.26.111.127,路径 /opt/jyotisha-staging。Postgres 容器 jyotisha-staging-postgres-1

日志窗口丢失

取证时公开 staging health 仍指向 35688015838d9e76 的父提交)。Web 容器在 2026-09-02T14:00:21Z22:00 +08)被 recreate,与任务书提交 838d9e7614:00:43Z)几乎同时。事故窗口(13:19–13:21 UTC)的 docker logs 已不在。[personal-report-agent][personal-report] generation_failed 无法回收。usage_ledger 对该 requestId 为 0 行。

因此 不能 用本事故的 finishReason / outputTokens 分布来确认「主嫌 · 截断」。截断判定只能写 blocked

事故行(requestId 77b79067-bd70-465e-ac23-adc4500d4738

结果
personal_reports failed / report_schema_invalid / created 13:19:23 UTC / updated 13:21:38 UTC
personal_report_jobs failed / last_error_code=report_schema_invalid / phase failed / progress 43% / attempt 1 of 3 / finished 13:21:38 UTC
theme-career blockedattempts 2/2last_error_code=section_evidence_insufficientupdated 13:21:38.398 UTC
theme-marriage pending0 attempts
theme-timing pending0 attempts
theme-wealth pending0 attempts

43% = 30 + floor((1/4)*55)career 被 block 后第一次 onProgress。Job 在 career block 后约 39ms 结束,其余三章从未 start

历史 report_schema_invalid(08-30 起,与任务书 5 个小时一致):跨这些小时的 section 行里,本事故是唯一一条 blocked + section_evidence_insufficient。其余为 pending 或 ready。

定案

  1. 主嫌(CJK /2 截断)未证实。 落库码不是 section_output_truncated。截断在 writer 路径通常会变成 PersonalReportAgentOutputError("report_schema_invalid")section_output_invalid。本事故没有 telemetry,不得写成 finishReason=length
  2. 次嫌(evidenceRefs 回显)是 career 章的最佳解释。classifySectionErrorCode 把任何含 "evidence" 的消息打成 section_evidence_insufficientreport_writer_evidence_refs_mismatch 含子串 evidence,会被误分类。career 2/2 次、约 29s,符合 assert 失败 + 一次 repair,而不是四章长输出截断。
  3. 第三层:首章 blocked 后没有继续写其余章。 产品测试已要求其他章仍应交付。Job 以不可重试的 report_schema_invalid 失败(attempt 1/3),说明 worker 收到了 {status:"failed"}invalid_request/storage_invalid,而不是 retryable 抛错。无日志无法闭合「是 filter / onProgress / 其它未捕获抛错」。本轮仍把每章失败隔离开,避免一章 throw 中止整份。

all_sections_blocked 原先裸 return、不走 failSchema,因此即使日志还在,也可能看不到 generation_failed。本轮改为走 failSchema

任务 1 仍按任务书授权落地中文口径公式,校准用的是 08-30 任务 0 的整份调用观测(outputTokens=3069finishReason=stop),不是 本事故。research / deep 的 3072 cap 保持不变;若以后实测需要更高,另开任务。


任务 1 · CJK 输出预算

禁止 targetCharacters.max / 2

口径:1 中文字符 ≈ 1.3 tokenCJK_OUTPUT_TOKENS_PER_CHAR)。

章节结构余量 568narrative 之外的 title / actions / caveats / refs / JSON 外壳):

部分 估算
title ≈ 80 字 ceil(80 × 1.3) = 104
3 条 actions ≈ 120 字 156
caveats ≈ 80 字 104
evidenceRefs JSON 80
JSON 键与括号 124
合计 568

摘要结构余量 580 已经包含 headline / priorities / JSON,不再把 headline 算第二次:

ceil(2000 × 1.3) + 580 = 2600 + 580 = 3180,再与章节同一 cap 3072 取 min08-30 整份 outputTokens=3069 / stop)。落地 3072

章节:min(3072, max(1024, ceil(maxChars × 1.3) + 568))

深度 max 字 未封顶 落地预算
concise 500 1218 1218
standard 1200 2128 2128
deep 2000 3168 3072cap,缺口 96 token
research 2800 4208 3072cap;约合 narrative 2504 字,低于 2800 上限)
summary 2000 字段 3180 3072(与章节同一 cap

08-30 整份 writer 一次调用 outputTokens=3069stop,说明 3072 能装下一份大 JSON。本事故未证实截断,因此不把 cap 自行抬高。length 修复重试:同一 outputBudget(不得低于首次),提示词要求压到 targetCharacters.min 并闭合 JSON。


任务 2 · evidenceRefs 回显

  • sectionPrompt 增加:evidenceRefs 必须逐字复制 plan.evidenceRefs 数组,不得增删
  • assertWriterOutput 仍是 id/theme 全等 + refs 集合相等,语义未放宽。
  • Repair 只拼类别词:identity mismatch / refs mismatch / schema invalid / output truncated。refs 类再次重申逐字复制。不把 ref id、正文或 bundle 拼进 repair。
  • classifySectionErrorCode 先匹配 identity / refs,再匹配泛化 "evidence",避免再把 refs mismatch 写成 section_evidence_insufficient

任务 3 · 失败可观测

不改表。应用层聚合已有 personal_report_sections 行。

  • 详情 APIfailureSummaryinnerReasonall_sections_blockedsection_generation_incomplete)、sectionErrorCodes
  • 列表 API:失败行多查 request_id, status, last_error_code(无正文),写入 failureSummary
  • 报告中心:failureSummary ?? failureCode。详情页摘要为主文案,等宽错误码为辅。

本事故若用新摘要,应接近:「4 个主题中 1 个写作失败:证据引用未对齐,3 个未开始」。旧码仍会显示「证据不足」,因为历史行的 last_error_code 不会回写。


任务 4 · 真实验证

GET https://staging.jyotisha.chat/api/health.deployment.gitCommit = eda37c1523341898850e6d71d63c8dd40563973b。Web 容器 2026-09-02T17:31:48Z recreate。未提升 main。

Web docker logs 在 Ready 之后几乎不再收录 console.info。本轮拿不到 [personal-report-agent]finishReason / 每章 token。usage_ledger 对未 ready 的请求无 complete 行。2 倍线的真实 inputTokens 仍缺测;墙钟如下,不自行裁决。

Run A · requestId 54be49f1-a657-43fa-88ce-7d7e8d575831

standard personal_full,主题 career / marriage / wealth / timing。started 17:44:07 UTC,终态 17:54:30 UTC(约 10.4 分钟)。

status attempts narrative 字数 last_error_code
theme-career ready 1 1557 (空)
theme-marriage ready 2 1395 (空)
theme-timing ready 2 842 (空)
theme-wealth ready 1 470 (空)

事故那一章(career)这次 1 次成功,没有 section_output_truncated / section_refs_mismatch。四章都有正文。

Job 在写作过程中 lease_expired 两次(默认租约 120s,单章可超过 2 分钟;heartbeat 未能保住租约),第三次 attempt 写完 wealth 后约 39ms 整份 report_schema_invalid。进度停在 71%timing 完成后的值),wealth 的 85% onProgress 未落库。四个 section 行仍是 ready。摘要/装配没有产出 report_document。reservation released

默认 leaseSeconds=120 不够串行四章写作。心跳 setIntervalunref 后,Next.js instrumentation 在没有 HTTP 请求时会跳过定时器,单章 4 分钟写作期间租约无法续上。补丁:600s 租约、20s 心跳、心跳定时器保持 ref、章节 onProgressheartbeatLease 再写进度。摘要预算与章节同一 cap 3072(原公式 3180)。

回溯抽查(只记是否对得上 ref,不摘正文)

# 叙事层(布尔) 对回的 evidenceRef
1 wealth 含 yoga 词 ev-tech-yoga
2 四章皆含 含 大运/主运/副运 ev-tech-vimshottari
3 marriage 含 yoga 词 + 大运词 ev-tech-d9 + ev-tech-dk
4 career 含 yoga 词 + 大运词 ev-tech-amk + ev-tech-vimshottari
5 wealth 含 yoga 词 + 大运词 ev-tech-d2 + ev-tech-d11

未在四章叙事中扫到强弱/shadbala 用词,不记为回溯命中。

Run B · requestId 8e75299b-58ac-43c2-8bf4-59f9bd394734

为区分租约与摘要失败另开一次。四次 POST /api/consultation_workflow 均为 200,随后 worker 在 buildReportEvidenceBundleV2 处非重试失败,calculation_unavailable,无 section 行。未再消耗第三次生成。

相对 2 倍线

无每章 inputTokens/outputTokens。Run A 墙钟约 10.4 分钟(含两次租约回收与 marriage/timing 的 section repair)。数据摆出,不裁决。

任务 4 结论

Writer 修正在章节层成立:四章有正文、career 一次通过、无截断码。整份 ready 仍被 120s 租约和最后一步 report_schema_invalid 挡住。租约、心跳续租与摘要 cap 已推 staging;公开 health 在跟进部署前仍指向 eda37c15。整份 ready 要等新 SHA 部署后再验一次。


质量门

结果
frontend/node_modules/.bin/tsc --noEmit 通过(exit 0,无输出)
eslint(改动的 TS/TSX 文件) exit 0,无 error 输出
聚焦 tsx --testplan / writer-failure / generation-v2 / api / view / agent-telemetry 104 pass / 0 fail
复跑 api + writer-failure 53 pass / 0 fail
personal-report-generation.test.ts + personal-report-worker.test.ts 51 pass / 0 fail

未跑全量 tests/*.test.ts,不声称全套绿。未改 .gitea/workflows/**,未提升 main。

任务 4 补丁(租约 600s、摘要 cap 3072、心跳保持 ref + 进度续租):tsc --noEmit 通过;plan / generation-v2 / worker 聚焦测试通过。