下载地址
https://www.zhengdianxue.com/wp-content/uploads/skills/vxi--vxi-longrun.zip将提示词发送给你的 AI 安装该 skills
请根据 https://skillhub.cn/install/skillhub.md,安装 @vxi/vxi-longrun。长任务跑很久,后台任务又被强制杀掉,返回码非零又没有任何输出,最难判断它是否还活着、要不要现在重跑。靠断点续跑和心跳判活把这类长耗时任务改成后台作业:进度写在磁盘上,AI 靠进度轮询几个文件就知道做到哪一步,失败也会被明确报出来,不必守着一条命令等到被杀,大文件下载、批量调接口、跑求解器这些脚本都能这样跑,后台运行也不用死等。
阅读全文 (展开查看完整说明)
概述
⚡ 最短上手
不想先读下面的判断树,就先跑这一条,看框架本身能不能跑通:
python scripts/longrun.py selftest --outdir .tmp-longrun-selftest跑完 status=done 就说明环境没问题;接自己业务照抄 scripts/example_job.py,写法见 references/job-contract.md。
🚦 适用与不适用(先判这一条,再往下读)
使用者会这么说(照抄一句就能叫起它,不必自己先判档;⚠️ 最直接的判据是时长、不是业务类型——估出来可能过几分钟,或压根估不准要跑多久,就算命中):
| 使用者原话 | 归哪一路 |
|---|---|
| 「这段脚本我估计要跑好几分钟 / 半小时,怎么跑才不会被掐」「这段脚本要跑很久,前台一跑就超时被杀」「脚本执行到一半就被杀了,返回码非 0 还没输出」 | 先按本节分档,判据读 references/when-to-use.md |
| 「想让它后台跑、能报进度还能断点续跑」「要采集一大批日志,中间断了想接着上次的地方继续」「批量调接口要处理几万条,怎么写成能续跑的」 | 写 Job,读 references/job-contract.md |
| 「怎么判断这个后台任务跑完了没、是不是卡住了」「任务跑完了状态却是 incomplete,这是什么意思」 | 任务已在跑,读 references/observation.md |
「给这个技能的脚本加上进度落盘和断点续跑」;英文照抄 run this long script in the background with progress and resume | 接进另一个技能,读 references/consumer-integration.md |
适用:一段脚本可能跑到超过执行器对单条命令的墙钟时限(下载上传、日志与数据采集、流式读写大文件、跑求解器、批量调接口、大目录扫描、转码都是常见形态,判据仍在时长、不在形态)。这类任务不加防护地前台死等,结局是被杀、返回 exit 非 0 加空或半截输出,什么也诊断不出来。
套了框架之后还有一次分档:保守估计不超过 480 秒、且结果落盘、且期间不必汇报进度的,前台档——直跑一次并给命令显式传够 timeout(被杀也不白跑,检查点还在);其余全部后台档——后台起加 wait 逐窗口等待。分档由你按业务经验判,拿不准归后台档;三条前提与判据见 references/when-to-use.md。
⚠️ 本框架有两组名字相近的档,它们正交、各解一个问题(这张表是唯一出处):
| 这一组 | 分的是什么 | 谁定、什么时候定 | 判据在哪 |
|---|---|---|---|
| 前台档 / 后台档 | 这一趟怎么跑:前台直等,还是后台起加 wait 轮询 | 调用方,每次起跑前判一次 | references/when-to-use.md |
| 极简档 / 完整档 | 你的 Job 要不要支持续跑:只报进度,还是额外存检查点、响应取消 | 业务代码,写 Job 时定一次 | references/job-contract.md §1 |
四种组合都合法(后台档跑一个极简档的 Job 最常见):跑法那一组不改你 Job 的写法,写法那一组也不限制这一趟怎么起。
不适用(命中任一条就别套框架,另找出路):
- 一眼几秒就跑完的脚本:前台直跑,套框架只是负担。
- 完成与否要靠主观判断的任务:框架判停只认「业务交回了摘要 dict」这一个机械信号,判不了「这篇写得够不够好」。
- 要边跑边把内容流式输出给人看的任务:框架把进度落盘、由观测方轮询读取,中间产物不经终端流出。
- 结果会随中间产物不断变化、完成条件本身说不清的任务:没有稳定的终态判据,轮询判不出该停在哪。
- 使用者可能中途改向的任务:后台任务只认「继续」与「中止」,改需求要重跑。
- 会话级中断(AI 平台限流或网络抖动把整个会话连同上下文一起断掉):被杀的不是进程而是会话,套框架无用(但跨会话找回一个还在跑的任务在本框架内,靠状态文件里的
command与outdir)。改落一份进度台账:每完成一步即时写盘,恢复时先读台账、再逐条核实外部实际状态,以外部实际状态为准——台账是下限、不是事实。
边界速查:上面这两段是唯一出处;完整判据分散在四处——该不该套见 references/when-to-use.md,框架与业务的职责分界见 references/job-contract.md §7 那张表,在跑的任务撞上意外现场见 references/observation.md §5、§8,反模式见 references/faq.md。
⚠️ 已知限制(能用,但有这几处短板)
- 静默 worker 失败(本框架给「业务自己开的 worker 线程异常,框架看不见」这件事的统一叫法,
job-contract.md§7 与faq.md反模式表都用它):框架只把run(ctx)主线程抛出的异常转成failed终态;worker 线程挂了而主线程照常返回摘要 dict 时,终态会是done——「部分没跑成」被报成「跑完了」。这是最容易踩、后果也最隐蔽的一条:业务自行开线程就要自己收异常、join后在主线程判断并抛出(或计入metrics失败数并在summary里如实体现),做法与代码示例见references/job-contract.md§7。 - 框架不设任何资源上限:不限制业务占用的内存量,也不限制能开多少个 worker 线程或并发连接,这类容量约束完全由业务自己按目标环境控制,详见同上一节。
📦 本技能只供给一件东西:长任务框架
Job / Context / run()——进度落盘、心跳判活(heartbeat liveness,状态字段 heartbeat_seq / heartbeat_at)、异常转终态、检查点续跑(checkpoint resume,API ctx.checkpoint() 与文件 _checkpoint.json)、协作式取消。引用本技能就意味着那个技能要跑长任务,须按 references/consumer-integration.md 抄一份准入声明。
⚠️ 别为通用小工具引用本技能:把输出强制成 UTF-8 的 force_utf8() 不是对外供给物,为它整包依赖本框架是错的依赖方向;各技能在自己的 scripts/ 里自持一份即可(判据与后果见 references/consumer-integration.md §3)。
⛔ 绝对禁止
- 禁止把
scripts/longrun.py拷进别的技能:框架的唯一出处是本技能,各技能按references/consumer-integration.md的片段引用它。拷副本会让同一份框架出现多个版本,本文档描述的行为在某些技能里根本不存在,使用端照文档判停就判错。 - 禁止把
status为running/interrupted/incomplete/failed的任务汇报成跑完了:判「跑完了没」只认done。 - 禁止猜测未列出的参数或子命令:只用「命令一览」与
references/observation.md§1 列出的入口与参数。 - 禁止直接把
scripts/longrun.py当业务入口跑:它是框架库,直接运行只支持「命令一览」那四个子命令。 - 被准入护栏拦下(退出码 4)时禁止重启、禁止换目录重跑:那说明已有实例在跑、或重试预算已用尽。照 stderr 给的下一步做。
🗺️ 路由表(任务 → 必读 reference)
| 你在做… | 必读 reference |
|---|---|
| 判断某段脚本该不该套框架,以及套了之后走前台档还是后台档(预估耗时、拿不准怎么办、前台档的三条前提) | references/when-to-use.md |
写业务侧的 Job(选档、ctx 各方法怎么调、plan / tick 怎么用、返回什么、run() 的四个旋钮、最小骨架、设计边界),完成度语义该用哪些约定字段,以及框架与业务的职责分界(§7 那张表,含读外部数据的责任) | references/job-contract.md |
任务已经起了,要查子命令的参数、读 _status.json 的某个字段、判它死没死或卡没卡、让使用者自己看进度、跨会话接管、或被拦下(退出码 4)不知怎么办 | references/observation.md |
| 把本技能接进另一个技能(引用片段怎么抄、准入声明抄哪几句、烘焙进 SKILL.md 的运行协议与命令级禁令、接入后怎么自检,§1 是从零接入的端到端演练),以及什么样的同构实现算正当例外 | references/consumer-integration.md(其 §5 是照抄进消费端 SKILL.md 的自足成品,改本篇后要回头改它) |
| 第一次接触本框架,想走一遍全流程,或查框架自有机制的统一名字 | references/walkthrough.md |
| 跑本框架时撞上意外结果,一切顺利时不必读 | references/faq.md |
⚠️ 按上表读完某篇后,先确认读回的内容非空;读回空就重读一次,未确认非空不得往下走——读取失败是静默的,「读了但什么也没读到」与「读到了」在你眼里长得一样。
🛠️ 命令一览(进阶参数一览与语义见 references/observation.md §1)
命令行之外,业务代码以编程式接入(API 导入形态):from longrun import Job, run 后实现 run(ctx),最小代码块与从零接入的端到端演练见 references/consumer-integration.md §1、最小骨架见 references/job-contract.md §5。
scripts/ 下可直接运行的入口有 3 个(框架 longrun.py 带四个子命令,另两个是业务示例)。下面这一列是基础形态、日常照抄即可;每个命令还各带一两个进阶参数,需要时去 references/observation.md §1 查:
| 命令 | 干什么 | 基础形态(照抄即可) |
|---|---|---|
selftest | 跑内置演示 job,确认框架在你的环境能跑通 | python scripts/longrun.py selftest --outdir .tmp-longrun-selftest |
wait | 阻塞等一个窗口,返回进度轨迹加判活判卡结论与 next_action | python scripts/longrun.py wait --outdir .tmp-<名> --max-wait 480 |
status | 只读一次,回答「还在跑吗」;--oneline 只给一行 | python scripts/longrun.py status --outdir .tmp-<名> --oneline |
runs | 列出本机在跑的任务,跨会话接管从这里查 | python scripts/longrun.py runs |
example_job.py | 最简业务示例(下载大文件,游标是字节偏移),也是写自己 Job 时的照抄对象 | python scripts/example_job.py --url <地址> --dest <目标文件> --outdir .tmp-download |
example_batch_api_job.py | 第二份业务示例(批量调外部 API,游标是「第几条记录」),详见 references/job-contract.md §2 | python scripts/example_batch_api_job.py --input <记录清单.json> --outdir .tmp-batch-demo |
⚠️ --dest 指向一个已存在的文件而又没有可续跑的检查点时,示例默认拒绝开跑(覆盖不可逆、也没有备份),要真覆盖才显式加 --overwrite。这一拒绝落的是失败终态、退出码 1(不是入参非法的 2):它在 run(ctx) 里判,那时框架已接管、业务异常统一转 failed;原文件一字未改,stderr 那句就是下一步。照抄这个 Job 写自己的业务时把这条一起抄走:凡会覆盖或删除使用者文件的动作,默认都该是拒绝加显式开关。
五条硬规矩,每次都要守(其余细则读 references/observation.md):
- ⚠️ 用满 480 秒的窗口必须给
wait显式传够timeout:多数执行器默认 120 秒,会提前把wait自己杀掉。 - ⚠️
wait自己超时或被杀不等于任务失败:任务在另一个后台进程里、多半还在跑。先读_status.json判活,禁止据此报失败,禁止再起一个任务。 - ⚠️ 窗口秒数照抄状态里的
suggested_poll_seconds,等待命令照抄wait_command,两样都别自己算、别自己拼。 - ⚠️ 判「跑完了没」只认
status为done;判「下一步做什么」只照next_action。 - ⚠️ 判活的第一判据是
heartbeat_seq有没有涨,不是heartbeat_at的时间戳年龄:机器休眠两小时再唤醒,时间戳的墙钟年龄就是两小时,而进程好好的;wait/status已把这条判完给出结论,你只管读结论,别自己按时间戳的新旧去猜活没活。
退出码:done / interrupted 为 0,failed 为 1,incomplete 为 3,被准入护栏拦下(已有实例在跑、或重试预算用尽)为 4 且 outdir 一字未改;wait / status / runs 自己的退出码只表示观测成功与否(0 / 2),与任务终态无关。逐条语义见 references/observation.md §2。
👀 进度页:一条不经过你的可见通道
框架在 outdir 里额外写一个每 5 秒自刷新的 progress.html,绝对路径就是状态里的 progress_url。起任务的那一轮就把这个路径告诉使用者,一句「要实时看进度就在浏览器里打开这个文件」——这是整套进度可见性里唯一需要你配合的动作,只说一次。它由任务进程自己写,你的会话关掉也照样在更新;跑完页面停止刷新、标签页标题变成终态字样。
⚠️ progress_url 为 null 时说明进度页写不出来(目录只读、磁盘满等),warnings 里会有对应说明。此时如实告诉使用者「这次没有进度页」,别再让人去打开一个不存在的文件。页面内容细节与其他要点见 references/observation.md §6。
⚠️ 别对使用者说「等我执行完成后通知你」这类话:这轮结束后你没有任何机制能主动开口,说了兑现不了,换来的是使用者傻等一个不会来的通知。如实告诉他这轮之后不会有人主动联系,他可以过一会儿再问一句(那时读一下状态文件就能秒答),也可以现在就打开进度页自己盯着。
📁 随包资源
- 上面「命令一览」列的那 3 个可运行入口:
scripts/longrun.py(框架骨架Job与run,零第三方依赖,也是四个子命令的入口)与两份业务示例。⚠️example_job.py的网络那一段只用标准库urllib.request,断网机器上照样能读、能改、能跑通除下载本身以外的全部分支。 assets/consumer_bootstrap.py— 消费端引用本技能的定位片段,照抄进自己技能的入口脚本;用法见references/consumer-integration.md。它也可直接跑一次当定位自检:找得到就打印命中的 scripts 目录并退 0,找不到就打印安装引导并退 2。- 只被 import、禁止直接运行的模块(跑它们不产出任何结论):
scripts/longrun_defs.py(常量、原子写、检查点、纯判定与进程判活)、longrun_context.py(业务接缝Context)、longrun_status.py(状态通道与字段契约)、longrun_progress.py(批次记账、速率、ETA、进度指纹)、longrun_guard.py(单实例锁、重试预算、同源检测、运行台账)、longrun_observe.py(判活判卡与阻塞式wait)、longrun_render.py(结论的措辞与版式)、longrun_page.py(自刷新进度页)、longrun_cli.py(四个子命令的实现)、longrun_messages.py(面向使用者的文案)。消费端照常只from longrun import ...、无感。
🧪 自检
python -m pytest evals -q -p no:cacheprovider开发态依赖自备、钉版本区间不留开放上界(-i 的镜像源国内更快,通畅时去掉走官方源亦可):
pip install "pytest>=7.0,<9.0" -i https://pypi.tuna.tsinghua.edu.cn/simple⚠️ 网络隔离环境下没有镜像源可用:框架骨架零第三方依赖,四个子命令与业务侧 Job 一个包都不装就能跑,唯一要 pytest 的只有上面这条自检本身。断网就跳过它,改用篇首「⚡ 最短上手」那条 selftest 验证框架可用(跑完看 status=done),不影响接入使用。
数据流向
框架自身不外发任何数据:scripts/ 下零第三方依赖、不做遥测、不采集使用统计、不上报错误与崩溃。落盘的都在你本机:进度、状态、检查点与进度页在你指定的 outdir 里;另有一份运行台账(记 outdir 与原命令,供跨会话接管)在 $LONGRUN_HOME/runs、缺省为家目录下 .longrun/runs,跑完不删、下次起跑时清掉超 14 天的。⚠️ 别把凭据当命令行参数传——原命令会随台账在家目录留最长 14 天;改用配置或环境变量。台账字段与清法见 references/observation.md §7。
唯一的网络访问在示例脚本 scripts/example_job.py:按你显式给的 --url 把文件下行取到本机,不上行送任何内容;换成你自己的 Job 后网络行为全由你那段业务代码决定。没有需要关闭的外发开关。
外部输入三类:状态、进度、检查点都是框架自己写的字节;台账指针在 outdir 之外、谁能写那个目录就能放一份进来,故 runs 那一行的每个字段在合并实时状态之后都再过一遍形态校验——超长或含控制字符即整字段丢弃(outdir 本身不合法则整行不显示),不进「逐字照抄」那一行;示例脚本读的第三方字节是响应头的 Content-Length 与状态码(两处都有兜底分支)与响应体(只按块落盘,不进上下文、不被拼进路径命令查询,落点另有 check_dest 与覆盖护栏把着)。⚠️ 换成你自己的 Job 之后这一条要重判,判法见 references/job-contract.md §9。
许可
本 skill 以 MIT 许可开源:允许任何人免费使用、修改、商用与再分发,只需在副本中保留版权与许可声明。Copyright © 上海维音信息技术股份有限公司 (VXI)。完整条款见同目录的 [LICENSE.md](LICENSE.md) 文件。