跳到正文
Hacker News· luvmakin·· 10 天前AI 评分60

Singularity 发布面向编码智能体的程序性记忆工具

Show HN: Singularity – memory that makes coding agents cheaper on repeat work

AI 导读

Singularity 是一个面向编码智能体的程序性记忆工具,从过去的 Claude Code 会话中学习,让重复和相似任务花更少时间和 token。它通过一条命令安装,为 Claude Code、Codex、Gemini CLI、Droid 等智能体加入 hooks 和 skill,在任务开始时下发工作流路线与警告。

正文 · AI 翻译

编码代理的程序性记忆:从过去的 Claude Code 会话中学习,让重复和相似的任务花费更少的时间和更少的 token。设计、决策和结果见 HANDOFF.md,代码如何编写见 CLAUDE.md。

安装

一条命令。它会检查你的机器,把 singularity 放到 ~/.singularity,并引导你在它找到的编码代理中设置记忆。

macOS、Linux、WSL 或 Git Bash:

curl -fsSL --connect-timeout 10 https://raw.githubusercontent.com/pandacover/singularity/main/install.sh | sh

Windows PowerShell:

irm https://raw.githubusercontent.com/pandacover/singularity/main/install.ps1 | iex

你需要 git。如果 PATH 中没有 Node.js 24 或更高版本,安装程序会自行获取一份副本放到 ~/.singularity/node(对照 nodejs.org 的校验和进行验证),除此之外不做任何改动。记忆从 Claude Code 会话中学习,并借助 Claude Code 的模型调用,所以也要有 Claude Code。WSL 保留自己的代理:对于你在 Windows 上运行的代理,请在 Windows 上运行 PowerShell 命令。(--connect-timeout 让 curl 从你的网络无法访问的 GitHub 地址快速转移,而不是等待数分钟。)

代理 记忆在那里做什么 设置添加什么
Claude Code 在任务开始时交接,在任务期间发出警告,从会话中学习 ~/.claude/settings.json 中的 hooks,skill
Codex 在任务开始时交接,在任务期间发出警告(用 /hooks 信任一次 hooks) ~/.codex/hooks.json 中的 hooks,skill
Gemini CLI 在任务开始时交接,在任务期间发出警告 ~/.gemini/settings.json 中的 hooks,skill
Droid 在任务开始时交接,在任务期间发出警告 ~/.factory/hooks.json 中的 hooks,skill
Cursor、OpenCode、其他读取 ~/.agents/skills 的代理 当你要求时 skill

设置还会把 singularity 命令放到你的 PATH 中,并询问记忆是否可以自行学习(在你的 Claude 账户上每轮约 $0.25,每天最多 $1)。它从不触碰代理的其余设置,并保留它更改的每个文件的副本(<file>.before-singularity)。

记忆如何了解一个仓库:一个以更改已提交且测试通过而结束的 Claude Code 会话会成为一条记录。在一个仓库中有几条记录后,记忆会从中学习工作流(每种更改放在哪里、如何检查、途中犯的错误)。从那时起,需要它们的任务会在其第一个提示中获取它们,在当天代码的现状中找到。在已有早期 Claude Code 会话的仓库中,设置会提议从那些会话开始。

singularity status              # which agents have memory, and what it knows per repo
singularity recall "<task>"     # what a task here would be handed (no model call)
singularity learn [--past]      # learn now; --past starts from this repo's earlier sessions
singularity setup               # again, to update or change your answers
singularity uninstall [--purge] # take memory out of every agent (memory stays unless --purge)

从克隆开始:npm install,然后 node apps/cli/src/cli.ts setup。

开发

一个使用 npm workspaces 的 Turborepo monorepo:

工作区 它是什么
apps/cli 命令行、hooks、记忆和评估工具:TypeScript 7 和 Effect 4,由 Node 24 直接运行(无构建步骤)
apps/landing 落地页:Vite 和 React
npm install
npm test               # every workspace's tests (vitest), through turbo
npm run typecheck      # every workspace's tsc
npm run build          # the landing page, into apps/landing/dist
npm run dev            # the landing page's dev server
npm run cli -- <args>  # the command line, same as node apps/cli/src/cli.ts <args>

本 README 中的命令从仓库根目录运行,评估运行也在那里进行(runs/)。

总结一次会话

node apps/cli/src/cli.ts traces <session id or path to .jsonl> [--json]

读取 Claude Code 的转录(包括子代理)并打印 token、工具调用、失败的调用和重复的文件读取。

本地记忆

记忆存放在 ~/.singularity(或 $SINGULARITY_HOME)中。它属于一个租户,在其 config.json 中命名,并且是关于主题的:仓库,通过其根提交和远程来识别,因此一个仓库的每个克隆都是同一主题。

工作流记录,每次完成的运行一条,是证据:

node apps/cli/src/cli.ts record import runs/excalidraw/*/ [--task ID ...]   # eval runs (of some tasks only)
node apps/cli/src/cli.ts record session SESSION_ID              # a session that committed its change with passing tests
node apps/cli/src/cli.ts record annotate --all --per-task 4     # a model's reading, a few cents a record
node apps/cli/src/cli.ts record list | show ID

一条记录包含日志和 diff 在没有模型的情况下所显示的内容:更改的文件、运行在其中添加行的位置(紧邻上方和下方的现有行,绝不是新行)、每条 shell 命令及其是否成功,以及绕路(一次失败的调用、后来修复它的调用,以及其间的 token)。模型的解读添加任务的类型、其步骤(每个步骤是被要求、需要,还是代理自己的选择)、代码中的地标,以及每次绕路教了什么,并带有精确的触发条件。每项声明都会对照运行提交时的代码和日志进行检查,如果不成立则丢弃。

记忆图由记录构建:任务类型及其路线 (必需步骤和可选步骤,每个可选步骤都带有条件), 步骤及其在每个仓库中发生的位置,以及警告及其触发条件。

node apps/cli/src/cli.ts memory build --conditions   # merge the records; a replay check commits or rejects
node apps/cli/src/cli.ts memory show [VERSION]
node apps/cli/src/cli.ts memory replay | triggers | candidates

每次构建都是一个候选。回放检查会针对每一个过往任务, 询问该图是否会恰好提供它所需的内容而不多给,以及 其触发条件是否会在那些成功执行的命令上触发;如果一个构建给任务提供了一个它从未要求的步骤,就会被拒绝。memory triggers 通过警告的触发条件回放已记录的运行, 以查看哪些警告本会在其错误发生之前出现。--conditions 让模型编写可选步骤的条件, 并判断哪些几乎相同的步骤名称其实是同一个步骤(花费几美分)。

搜索,精确搜索和按词搜索(按含义搜索仅限托管版本):

node apps/cli/src/cli.ts search help dialog shortcut
node apps/cli/src/cli.ts search --exact handleKeyboardGlobally [--type record|kind|step|warning]

交接,通过 Claude Code 钩子实现:

node apps/cli/src/cli.ts handover --cwd REPO "Change the zen mode shortcut to Alt+M"   # what a task would get
node apps/cli/src/cli.ts hooks install [--scope user|project|local]                   # or `hooks print`, for claude --settings
node apps/cli/src/cli.ts hooks status | uninstall

在会话的第一次提示时,钩子会交接该任务的路线 及其步骤上的警告:词搜索最多提出三种任务类型,然后 模型确认其中一种并挑选其步骤(约 $0.03,耗时几秒)。随路线一起交接的还有其位置处的代码:钩子会在工作 树中查找该类型的早前运行在编辑每个步骤时所参照的行, 并交接这些行现在周围的内容,这样智能体无需先阅读这些位置就能进行编辑。记忆保存的是要查找哪些行,而不是代码本身。 所有这些都保持在 10,000 个字符以内,这是 Claude Code 截断钩子文本的阈值。在 每条 shell 命令或编辑之后,如果某个警告的精确触发条件出现,就会交接该警告,每个会话一次。当会话以一次已提交的更改和通过的 测试结束时,它就成为一条记录。SINGULARITY_HOOKS=off 会关闭这些钩子; 评估框架会设置它,因此已安装的钩子不会进入测量运行。

该记忆是 v0(标签 memory-v0);hooks install 安装其钩子。 记忆 v1 是 singularity setup 为日常使用而安装的版本(如上所述), 并位于同一个主目录下,在 tenants/<tenant>/workflows/ 中。安装程序如果发现 v0 的钩子,会将其替换。在日常使用中,v1 会逐个仓库地学习 (apps/cli/src/workflows/Learn.ts):一个仓库的记忆单独学习后再合并回来, 因此一个仓库永远不会替换另一个仓库的记忆,两个仓库共享的 id 会追加第二个 仓库的 id。下面的命令就是它由之构成的各个部分,正如 评估套件使用它们的方式:

node apps/cli/src/cli.ts workflows build [--task ID ...] [--repo DIR] [--fresh]       # induce from the records (one model call or two)
node apps/cli/src/cli.ts workflows show [AT] [--json]                                 # print it
node apps/cli/src/cli.ts workflows cues [--repo DIR]                                   # write cues, so tasks are picked without a model (one model call or two)
node apps/cli/src/cli.ts workflows finish [--repo DIR]                                 # learn what each workflow's checks rewrite (no model)
node apps/cli/src/cli.ts workflows handover TASK... --cwd REPO [--at COMMIT] [--pick cues|words|model] [--parts 2] [--draft] # preview a task's hand-over
node apps/cli/src/cli.ts workflows evolve [--task ID ...] [--setup S ...] [--records-from HOME] [--repo DIR] [--dry-run] # learn from new runs
node apps/cli/src/cli.ts workflows common --task ID --task ID ... [--kind WORDS] [--repo DIR] [--dry-run] # learn what runs of different tasks of a kind did alike
node apps/cli/src/cli.ts workflows candidates                                         # proposals and what became of them

自第十次会话以来:同一类型的任务共享什么(workflows common, apps/cli/src/workflows/Common.ts)。逐个任务地学习,两个错误修复各自变成了一个 工作流,而其他任何错误都无法使用。这一遍会读取同一类型的 多个任务(错误修复)的运行记录,并只保留至少两个 不同任务的运行都做过的事情:这里如何在测试中复现一个错误,如何 检查修复(没有修复时测试失败,使用 git stash),以及过程中 犯过的错误。其位置包括运行读取过但未更改的代码块 (apps/cli/src/workflows/Reads.ts),例如测试辅助类的 Keyboard 类, 交接会将其显示为大纲:该代码块的第一行及其成员, 来自任务开始时的代码。之后运行 workflows cues。

自第八次会话以来:交接以一条命令结束,该命令运行 其工作流所需的所有检查,并重新生成早前运行所生成的快照文件 (apps/cli/src/workflows/Finish.ts);evolve 还会读取每次运行在首次编辑之前 仍然查找了什么(apps/cli/src/workflows/Lookups.ts; --dry-run 会打印它),再次写入提示线索并用它们回放;以及 workflows-split 安装程序会将一个钩子无法承载的交接(Claude Code 会将每个钩子截断在 10,000 个字符)分成两部分,来自两个任务开始钩子。

v1 保留用空白({field}、{key})编写的小型工作流,这些工作流从运行中学习而来,并保留一个由它们构成的图,其边表示一个工作流何时导向另一个。步骤的位置被保留为包围该编辑的代码块(class App › getContextMenuItems › if (this.state.viewModeEnabled) › return [),而绝不是代码;在任务开始时,它的钩子(apps/cli/src/workflows/hook.ts)会挑选任务所需的工作流,按代码当前的样子(如果移动了,就在它移动到的文件中)找到每个位置,并将它们连同当前行号一并交出。evolve 根据新的运行做了什么以及它向它们展示了什么来修订记忆,并且只有当在运行上重放时,它显示出更多它们编辑过的位置、更少它们未改动的位置,才保留该修订。

本地优先(标签 memory-local-first):挑选不进行模型调用。每个工作流都带有线索,即表明任务需要它以及任务在哪里陈述其空白值的短语,由 workflows cues 编写一次(apps/cli/src/workflows/CueWriter.ts);在任务开始时,它们会与任务进行精确匹配,其否定子句(“不要添加捷径”)被排除在外,任务陈述的空白值会被填入(apps/cli/src/workflows/Cues.ts)。这是钩子的默认行为,也是 workflows handover 的默认行为;没有线索的记忆则按词语挑选。SINGULARITY_SELECTOR=model(或 --pick model)则让模型调用来挑选,正如 v1 最初被测量时那样。构建会写出没有线索的工作流:之后运行 workflows cues(一次带线索的记忆学习轮次会自行写出它们)。列出多个更改的任务(编号或项目符号列表)会逐个更改地挑选和填充:每个项目,连同任务对所有这些项目的陈述,陈述其自己的值;多个更改所需的工作流只展示一次,并附上每个更改的值以及它所针对的更改。

已测试并放弃(用户的决定:记忆帮助智能体完成任务,它并不完成任务):使用 --draft(以及在 workflows-draft 设置中),任务开始时的第二次模型调用以唯一不留任何开放的方式填充空白:它根据那些工作流及其位置处的代码,为这个任务写出更改本身(约 5-10 美分,半分钟)。每个编辑在交出之前都会对照代码进行检查(其旧行在其文件中恰好出现一次),智能体得到的是完整更改而不是步骤(apps/cli/src/workflows/Draft.ts)。记忆仍然不保留任何代码。

运行评估

node apps/cli/src/cli.ts eval run apps/cli/examples/excalidraw/suite.toml --reps 3 [--task ID ...] [--dry-run]
node apps/cli/src/cli.ts eval report runs/excalidraw/<run dir> [more dirs...] [--compare]

一个套件(TOML,见 apps/cli/examples/excalidraw/suite.toml 和 apps/cli/src/eval/Suite.ts)指定一个源仓库、智能体设置和任务。每个任务都有一个基础提交、一个提示以及决定成功与否的检查命令。check_files 可以在智能体完成后添加隐藏测试,这样智能体就无法通过编辑它们来通过。apps/cli/examples/excalidraw/ 是真正的评估套件(针对本地 excalidraw 克隆的两个任务族);apps/cli/examples/toy/ 是一个针对此仓库历史的小型、廉价的套件。

每次运行都从任务基础提交处仓库的干净克隆开始,位于 <workspaces>/<suite> 中。工作区在 Windows 上默认为 C:\singularity-workspaces(位于主文件夹之外,因此那里的 CLAUDE.md 不会被加载到运行中),在其他地方默认为 ~/.singularity/workspaces;设置 SINGULARITY_WORKSPACES 或传递 --workspaces 来更改它。克隆没有远程,因此源仓库永远不会被触及,也没有分支、标签或 reflog,因此后续提交(可能包含隐藏测试)不会进入智能体的视野。

Claude Code 以无头模式运行,其自动记忆关闭且没有 MCP 服务器。测试框架以低于正常优先级运行所有内容(当没有其他东西需要机器时使用 --priority normal),并且套件的 env 表可以限制测试工作进程数,从而保持机器可用。每次运行都会将成本、令牌、墙钟时间、工具调用和检查结果记录到 results.jsonl,并同时保存转录和差异。使用 --dry-run 查看确切的 claude 命令而不运行任何内容。

记忆设置

--setup 挑选给智能体的内容:

  • no-memory:无(基线)。
  • saved-scripts:最接近的过去成功运行(其提示、文件、源差异和可用命令),按提示相似度匹配。
  • saved-scripts-top2:最多取两个最接近的过往运行,每个都足够相似。
  • graph:过程图。描述与提示词有共同词语的步骤标记了任务进入图的位置;Sonnet 随后阅读任务以及附近步骤及其条件,并决定哪些适用(约 $0.01,计入该次运行的成本)。被选中的步骤会作为一份清单一次性移交:每个步骤何时适用、如何操作、要避免什么,以及涉及的文件和命令。学习环节要求 Sonnet 将每次运行转化为图的编辑(见 apps/cli/src/eval/GraphLearner.ts)。两次调用都通过 claude -p --json-schema 使用你的登录进行。--graph-version N 使用的是较早版本。
  • saved-scripts-warnings:最接近的过往运行,如 saved-scripts 所给出的那样,再加上图中的警告:记录在其步骤上的错误和死胡同,不含清单。--memory 是已保存脚本的存储,--warnings 是图的存储。
  • hooks:本地记忆,由其自身的钩子像日常使用那样移交。--memory 是一个记忆主目录(一个 SINGULARITY_HOME),被复制到每次运行的目录中,因此它永不改变。Claude Code 通过 --settings 获得这些钩子:在第一个提示词时给出路线,在其触发条件出现时给出警告。路线选择的模型调用运行与智能体相同的 Claude Code,并计入该次运行。用 handover --home HOME --cwd WORKSPACE "the prompt" 预览任务会得到什么。
  • workflows:记忆 v1,以相同方式由其自身的钩子移交:--memory 是一个带有 v1 记忆的记忆主目录,被复制到每次运行的目录中;工作流在第一个提示词时到达,某个陷阱的警告在其触发条件出现时到达,而选择的模型调用计入该次运行。每次运行都保留其移交日志(展示了什么,在第几行),workflows evolve 从中学习。用 workflows handover 预览。此设置保持 v1 如其所测:由一次模型调用来挑选工作流。
  • workflows-cues:本地优先的记忆:相同的钩子,在任务开始时没有模型调用;记忆的线索挑选工作流并填补任务陈述中的空白(workflows handover --pick cues)。
  • workflows-split:workflows-cues,其移交内容长于单个钩子所能承载,因此分两部分从两个任务开始钩子发送(workflows handover --parts 2)。
  • workflows-draft(已测试并放弃):相同,但智能体获得的是变更本身,在任务开始时根据工作流和代码起草(workflows handover --draft);起草调用也计入该次运行。

除 no-memory 之外的设置需要 --memory DIR;为测量运行添加 --frozen,这样记忆在被测量时不会改变。

--claude PATH 运行另一个 Claude Code,例如某个较早版本的固定副本(~/.local/share/claude/versions/ 保留几个),以便将一次运行与来自同一版本的基线进行比较。运行从不更新 Claude Code。

从已记录的运行构建记忆,无需再次运行智能体:

node apps/cli/src/cli.ts eval learn apps/cli/examples/excalidraw/suite.toml runs/excalidraw/baseline-1 \
  --setup saved-scripts --memory runs/excalidraw/memory/saved-scripts --task altkey-zen-m

查看某个设置会为每个任务移交给智能体什么,并检查一张图:

node apps/cli/src/cli.ts eval inject apps/cli/examples/excalidraw/suite.toml --setup graph --memory runs/excalidraw/memory/graph
node apps/cli/src/cli.ts graph show runs/excalidraw/memory/graph

report --compare --baseline saved-scripts 将各设置与已保存脚本进行比较,而不是与无记忆进行比较。

新设置在 apps/cli/src/eval/Setups.ts 中实现 MemorySetup。

来源:Hacker News · github.com