Perslis Symbols 工作原理。
规格而非代码。一道闸门。一个人。一个只做验证的运行时。
下文的一切都依据 1.1.2 版本及其记录的运行来陈述。凡是带日期的数字,就是它被测量的日期;凡是尚未完成的事,都会写明。
摘要。软件向模型询问的关于数据的问题,大多是对软件已持有的行做算术。用模型回答它们,按次付费,还返回无法核对的数字。Perslis Symbols 把这一安排反转过来:模型只被要求一次,去编写一条规格——由封闭词汇表中命名步骤组成的流水线,绝非代码——准入闸门在真实数据上证明这条规格(十项检查、三次证明),一个人读出它计算的内容并签名批准,此后用户机器上的标准库运行时通过 MCP 执行它,model_calls: 0。算术是精确的十进制;无法支持精确答案的数据按名称拒绝;工具经 Ed25519 签名,运行时只持有公钥。我们给出词汇表、闸门、信任模型、成本模型、标注日期的测量和边界,并把本站的演示钉在真实运行时与闸门的记录转录上。
问题
我们按地区各付了多少?前三大供应商是谁?多少张发票没有采购单号?这些都不是推理问题。它们是对软件已持有数据的计数、筛选和求和。送给模型,每一次提问都按 token 付费,拿回来的数字却无从核对——置信度分数无法检视,而一个错误的总额看起来和正确的一模一样。
问题不在于模型不擅长算术。问题在于这种安排把模型放在回答时的环路里——在那里它昂贵、缓慢、无法验证——而不是放在编写时,在那里把问题读一次、组合出一条过程,正是它擅长的事。
定义
符号(symbol) — 一个数据集上的问题类别(demo:paid_by_region)绑定到唯一一条已准入的流水线,由准入它的 floor 和批准它的人签名,并作为一个 MCP 工具(derive_demo_paid_by_region)提供。
流水线(pipeline) — 来自第 4 节封闭词汇表的步骤列表,语法为 rows (filter | join)* [group_by] reducer [top]。在任何执行之前验证;要么整体执行,要么完全不执行。
规格(spec) — 模型返回的内容:{question_class, pipeline, do_not}。是数据,不是代码。
准入(admission) — 闸门在真实数据上的裁决:十项检查和三次证明之后为 ADMITTED,否则为 REFUSED 并指名第一项失败的检查。
证明(attestation) — 审核人的签名记录——工具用文字计算什么、它在被审核数据上的答案、规格/表/数据的摘要——绑定到审核人的密钥和已登记的姓名。没有有效证明的工具,运行时拒绝。
拒绝(refusal) — 不是数字的答案:REFUSED(数据无法支持经核对的答案)、NO_EVIDENCE(工具读取的表不存在)、NO_VALUE(没有行匹配)。每一种都说明原因。
缺口(gap) — 没有工具能精确回答的问题,由 report_gap 记录在用户机器上,以便为它构建一个符号。
循环
第 2 步是模型有用的地方,也是它唯一出现的地方。提示词携带用户的列名和封闭词汇表;模型只能从这个词汇表中组合。第 3 步是机械且对抗性的(第 6 节)。第 4 步之所以存在,是因为第 3 步有一条它无法跨越的界限(第 7 节)。第 5 步是一个约 1,600 行的标准库程序,任何人都可以在运行前读一遍(第 9 节)。
词汇表
rows (filter | join)* [group_by] reducer [top]。执行前验证;top 需要 group_by;group_by 必须紧贴 reducer 之前。| 步骤 | 字段 | 作用 |
|---|---|---|
rows | — | 工具基表的每一行。 |
filter | column + 恰好一个:equals、not_equals、in(1–1000 个值)、contains(不区分大小写)、nonempty、empty、gt、lt、gte、lte(数字)、after、before、on_or_after、on_or_before(ISO 日期) | 保留行。文本按规范形式比较;数字和日期边界先解析每个值,若有一个不是数字或不是 ISO 日期,则拒绝本次运行。 |
join | table、on、to、how ∈ {strict(默认)、left、inner} | 多对一查找。查找侧的重复键被拒绝(会使行数倍增)。无匹配的行被拒绝,除非签名的规格声明了 left(保留)或 inner(丢弃)。 |
group_by | column,可选 bucket ∈ {day、week、month、year} | 按规范文本分组,或按 ISO 日期桶(ISO 周)分组。无值的行构成 (missing) 组;真实值恰好写作 (missing) 时拒绝。 |
| reducer | count(无列)、sum、mean、max、min、distinct、count_missing、earliest、latest | 每条流水线一个。求和、最大、最小精确;mean 以银行家舍入保留 10 位小数,并在复述中写明。 |
top | n(1–1000)、order ∈ {desc、asc} | 对分组排名并保留并列:截止线上并列的每个组都返回,因此“哪个供应商最多?”从不悄悄二选一。 |
属于策略而非偶然的限制:数字最多 38 位有效数字、指数在 ±60 以内(SQL DECIMAL(38));超过 1,000 组的未排名答案需要 top;平均值是唯一的舍入。每个工具的复述——“Take every row of invoices; keep rows where status is 'paid'; look up each row's vendor_id in vendors.id (every row must match, or the tool refuses); group by vendors.region; add up amount in each group (exactly).”——由流水线本身生成,因此不可能与实际执行的内容脱节。
精确性与拒绝
数字被解析为十进制,从不使用二进制浮点:9,007,199,254,740,992 + 1 保持为 9,007,199,254,740,993。美式货币写法($1,200.50、-$5)可以读取;任何可能被读成两种意思的写法(1.200,50)不算数字。数字位置上的非数字值不会变成零,也不会被跳过——本次运行被拒绝,且拒绝理由指出列名和值。
以下每一种都被拒绝而非作答:
- 数字位置上不是数字的值(
N/A、1.200,50),或超出精确数字限制; - 日期位置上不是 ISO 日期的值(
03/04/2026); - 查找侧不唯一的连接键;无匹配的行,除非签名规格选择了
left或inner; - 量级差距过大以致无法精确保存的求和;没有
top却超过 1,000 组;因超过 1,000 个并列而含糊的top; - 不是行的表项(损坏的 JSON)、重复的 JSON 键(
{"amount": 10, "amount": 999}曾被读作 999)、在引号字段内被截断的 CSV、单元格数量错误的行、读取过程中发生变化的文件。
原则是:看起来正确的错误答案才是代价高昂的失败。一次拒绝的代价是重读一遍数据;一个悄悄偏低的总额,代价是据此做出的一切决定。
准入闸门
规格在真实数据上准入,而非在其样本上,且只有下面每一项检查都通过才准入。演示规格通过闸门的转录在第 10 节回放;这里说明每项检查的用途。
| # | 检查 | 它防止的失败 |
|---|---|---|
| 1 | structure | 语法之外的流水线——在任何执行之前拒绝。 |
| 2 | not_already_registered | 在同一问题类别下悄悄替换已有的符号。 |
| 3 | columns_exist | 幻觉出来的字段。流水线指名了证据中没有的表或列(“no column 'amt'”)。 |
| 4 | data_supports_answer | 无法支持精确答案的数据:数字列里的 N/A、非 ISO 日期、无匹配或重复的查找键。 |
| 5 | determinism | 运行两次,答案不同。 |
| 6 | produces_a_value | 在证据上什么都推导不出来的过程。 |
| 7 | verifier_accepts_truth | 拒绝真实答案的验证器。 |
| 8 | verifier_rejects_wrong | 什么都接受的验证器——它会通过其他所有检查。 |
| 9 | abstains_without_evidence | 表不存在时编造一个数字;工具必须说 NO_EVIDENCE。 |
| 10 | grounded_in_evidence | 从样本里烘焙进去的常量:扰动真正到达 reducer 的行,答案必须随之改变。 |
验证器从不由模型编写。模型写出的错误验证器会晋升一个错误的工具,那个工具随后自信地、离线地、永远地作答,再没有模型来捕捉它。因此验证器是推导出来的:verify == run(pipeline) == proposed。十项检查之后,过程被证明三次并晋升;连接步骤还会留下一条信息性的覆盖说明。然后交给人。
构建闸门时的两项发现应记入档案。最初的扎根检查只扰动前 25 行,在 10,000 行真实数据上拒绝了每一条正确的规格,因为匹配的行在更后面;现在它扰动到达 reducer 的行,并对每一类问题在该规模上测试。而一个准入“按其书写正确”的闸门,准入了一条标着“已付发票总额”却筛选 status = open 的规格——这正是下一节的内容。
为什么需要人
闸门能证明过程按其书写是正确的,却无法证明过程的含义就是问题的含义。第 10 节记录的转录 gate_wrong 展示了这一点:规格在“所有已付发票的总额”这一标签下保留 status = open,而每项机械检查都通过——因为每项机械检查关心的是过程和数据,而不是那句话。
因此在有人读过复述和真实数据上的答案并说“是”之前,什么都不会发布;批准以审核人自己的密钥签名进工具:记录覆盖规格、表、复述和答案,审核人的密钥 ID 映射到固定注册表中唯一一个已登记的姓名。事后编辑工具、它的表或审核记录都会使之作废;用一个人的密钥以另一个人的名义签署的批准会被拒绝;同一姓名下登记了两个 ID 的注册表会因含糊而被拒绝。
签名与信任
- 工具由准入它们的 floor 以 Ed25519 签名。运行时只能验证:它持有公钥,并且有意不提供添加密钥的命令行选项。未签名或被编辑的规格按名称拒绝,其他工具照常提供(第 10 节的 tamper 转录)。
- 审核经过认证(第 7 节)。公开发布版只信任演示审核人;试点工具包携带自己的审核人。
- 发布:
SHA256SUMS由单独的发布密钥签名,可用ssh-keygen -Y verify验证,其公钥固定在两处——下载页和 GitHub README——因此同时替换 zip 及其校验和是不够的。构建逐字节可复现。安装脚本是签名校验和覆盖的发布资产;没有curl … | bash这一行,因为那会在任何验证之前运行可变内容。 - 为何闸门不在下载包中。如果闸门随包发布,一条错误的规格可以被手写、加载,然后自信地、离线地、永远地作答。所以闸门留在能被维护和审计的地方,而在你机器上运行的是一个执行器——执行上完备,并诚实地承认自己只是执行器。运行时不含网络代码;你可以通过阅读它来确认。
运行时
标准库 Python 3.9+,无依赖,无网络代码,约 1,600 行。它读取 CSV、TSV、JSON(数字作为精确十进制)、JSONL、SQLite(只读)或包含它们的文件夹,在磁盘数据变化时重新读取,并在此之前缓存答案;变化后的文件若无法读取,调用会被拒绝直到可读为止。它通过 stdio 使用 MCP(协议版本 2025-06-18、2025-03-26 和 2024-11-05):
→ {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}
← 已准入的各工具,各带其问题、复述和 "Takes no arguments",外加 report_gap
→ {"jsonrpc": "2.0", "id": 10, "method": "tools/call", "params": {"name": "derive_demo_paid_by_region", "arguments": {}}}
← {"jsonrpc": "2.0", "id": 10, "result": {"isError": false, "content": [{"type": "text",
"text": "{\"status\": \"DERIVED\", \"tool\": \"derive_demo_paid_by_region\", \"table\": \"invoices\",
\"question_class\": \"demo:paid_by_region\", \"value\": {\"Central\": 11215.45, \"East\": 21576.59,
\"South\": 4800.25, \"West\": 32996.02}, \"model_calls\": 0,
\"derivation\": \"rows -> filter(status equals 'paid') -> join(vendors on vendor_id=id) -> group_by(vendors.region) -> sum(amount)\"}"}]}}
工具不接受参数:问题在准入时就已固定,这正是答案可核对的原因。工具描述用文字告诉助手该工具究竟计算什么,若这不是用户所问,则应改为调用 report_gap。调用记录在本地(calls.jsonl、gaps.jsonl);未回答问题的文本留在私有文件夹中,除非操作者选择分享。--check 把每个工具在数据上各运行一次,若有任何一个不是 DERIVED,则以非零退出。
演示——已记录,并在此运行
下方每份转录都是真实闸门(symbolic_floor.floor_ask add)或真实 1.1.2 运行时(通过 MCP stdio 的 floor-serve.py)在虚构演示数据上的记录;唯一的编辑是把临时路径替换为 ./data 和 ./floor。“自己组合”面板执行的是运行时执行器的 JavaScript 移植版,本站的测试把它钉在与 Python 逐例相等。
闸门,逐项检查
运行时如何拒绝
自己组合一条流水线(仅执行器——这里什么都不会被准入)
成本模型
设一次模型回答成本为 c,编写一个符号(编写规格的模型调用,加上人的审核)成本为 A,一次本地执行成本为 ε。问过 N 个问题之后:
t 为每次调用的 token 数,p 为每百万 token 价格;k 次编写尝试、每次 tauthor 个 token、按平均 token 价格计;h 小时审核、费率 r;L 为每次调用的延迟。模型账单随问题数线性增长;符号账单在 A 之后是平的。按下方示例数字——每次 1,500 输入 / 150 输出 token,每百万 $3 / $15,三次 4,000 token 的编写尝试,15 分钟审核、每小时 $120——c ≈ $0.00675,A ≈ $30.11,N* ≈ 4,460 次调用:以每天 2,000 个问题计约两天,此后一年的成本约 $30 而非约 $4,900。这些是供修改的占位数字,不是任何服务商的报价。
有两项成本在公式之外。答错的代价——模型的数字无法为其设界,而符号的拒绝正是为避免它而设计。以及符号尚不能回答的问题的代价——它们被报告为缺口,仍然需要模型;对它们的诚实计数是测试版所测量的一部分。
测量
| 项目 | 测量值 | 时间 · 方式 |
|---|---|---|
| 加载、内存、首次查询 | 500,000 行(18 MB CSV):加载约 1.3 秒,约 400 MB,首次查询约 2.3 秒;重复答案缓存;默认上限 1,000,000 行 | 2026 年 9 月 26 日 · 试点构建,macOS |
| 准入 | 同一 500,000 行上每条规格约 27 秒(十项检查、三次证明) | 2026 年 9 月 26 日 · 试点构建 |
| 演示上的闸门 | 正确规格:10/10 项检查,证明 3/3,已晋升。幽灵列:在第 3 项拒绝。amount 里的 N/A:在第 4 项拒绝。问题错、过程对:10/10——只能靠审核发现。 | 2026 年 9 月 27 日 · 记录的转录(第 10 节) |
| 测试 | 超过 300 项自动化测试在 Python 3.9 和 3.13 上通过;四轮对抗性审查,每项发现都由回归测试钉住 | 2026 年 9 月 26 日 |
| 真实客户端 | 一个 Claude Code 会话通过 MCP 调用了构建好的工具包,报告了精确答案,并把一个无法回答的问题记录为缺口 | 2026 年 9 月 26 日 |
| 本站的移植版 | 在 6 个工具、2 种拒绝、描述与推导上等于 Python 运行时的记录答案 | 2026 年 9 月 27 日 · scripts/check_symbols.js,每次构建运行 |
边界,以及不宣称的事
- 尚未支持:相对日期(“最近 30 天”)、带参数的工具、直接连接数据库、自助闸门。针对你数据的工具通过试点获得。
- 词汇表之外的问题被报告为无法映射;它仍然需要模型。词汇表有意保持小规模,一次只增加一个经证明的原语。
- 闸门证明过程,而非含义;人不是可选项(第 7 节)。
- 签名能阻止篡改和对未签名工具的随意复用;它不能防御控制了运行时所在机器的攻击者。
- 测量来自一台机器上的一次构建,已标注日期;闸门转录每种情况各一次。本站的 JavaScript 移植版是移植——下载包才是运行时。
- 状态:试点。这里没有任何内容是与其他产品的基准比较。
相关工作
各个部分单独来看都不是新的,本页也不作此宣称。把决策编译成无需推理器即可运行的规则,属于专家系统的传统;从受限语言中组合程序并对照示例检查,属于DSL 下的程序综合;可执行、可检视的推导,正是符号流在整个 Perslis 中的命名。这里特有的是这种安排:模型只能提议规格,验证器是推导而非编写的,准入在用户的真实数据上证明,人的批准签名进制品,而发布出去的东西能验证却不能准入。同样的失效关闭姿态——不通过一项可以拒绝的检查,任何东西都不进入可信状态——就是本站其余部分所依据的失效安全模型,只是从答案应用到了工具。
参考与来源
- Perslis Floor 1.1.2——下载、安装脚本、签名校验和与构建状态:perslis.com/perslis-floor;源码与发布:github.com/AgewellEPM/perslis-floor(CHANGELOG 1.1.0–1.1.2)。
- 符号 floor、研究说明与早期运行时:perslis.com/research/floor。
- 本页背后的记录转录:assets/symbols/recordings.json(运行时 1.1.2,记录于 2026-09-27);闸门转录原文在
assets/symbols/gate/。 - Model Context Protocol 规范(tools/list、tools/call、stdio 传输):modelcontextprotocol.io/specification。
- 运行时使用的十进制算术:Python 的 decimal 模块(IBM 通用十进制算术规范);
week桶采用 ISO 8601 周日期;Ed25519 依据 RFC 8032。 - 本页所依据的 Perslis 指南:符号 AI、符号流、失效安全模型。
问答
- 模型能写代码吗?
- 不能。它只返回一个 JSON 对象:一个问题类别和一条由封闭词汇表中命名步骤组成的流水线。执行器里没有
eval、没有exec、没有导入;词汇表之外的操作会被拒绝,而不是被解释执行。 - 我的数据变了会怎样?
- 运行时在数据在磁盘上变化时重新读取,并在此之前缓存答案。如果变化后的文件无法读取,调用会被拒绝直到能读取为止;过期数据从不被悄悄提供。
- 我能给运行时添加自己的受信任密钥吗?
- 不能——这是有意为之。信任只来自编译进运行时的公钥和审核人注册表;没有添加密钥的命令行选项。由未知密钥签名或签名后被编辑的工具会按名称拒绝,其他工具照常提供。
- 什么是缺口?
- 没有任何工具能精确回答的问题。助手调用
report_gap;问题只记录在你的机器上,以便为它构建一个符号。什么都不会离开这台机器。