Documentation
¶
Overview ¶
Package counterfactual defines the standardized data structure for reverse-thinking deliverables produced before non-trivial tool calls.
Why this package exists ¶
CLAUDE.md article 1 (reverse thinking) requires that any non-trivial design choice be challenged from the opposite side before commit. The soft skill at ~/.claude/skills/reverse-think/ runs MiniMax to produce hidden_assumptions / failure_scenarios / verdict / verdict_reason. Until v0.3-dev that produce was free-form text. counterfactual locks the schema so the same shape can be (a) persisted on staging.Record, (b) replayed through evolve.LogReplayer, (c) audited cross-tenant.
The package is opt-in: nothing in core/pkg/engine forces a Deliverable to exist. The hook layer (platform/common/safetychain) decides when to invoke the reverse-think skill, based on tools.Metadata.RequiresReverseThinking. Default off = zero latency / zero LLM call overhead on hot paths.
Boundary with staging / validator / evolve ¶
- staging.Record: a Deliverable is one signal a Validator may consult, stored in Record.Metadata under MetadataKey. counterfactual itself is not a Validator and emits no Verdict on staged diffs.
- validator.Verdict: scoped to "is this staged decision safe to commit", consumed by staging.Engine. A Deliverable is upstream evidence, not a verdict.
- evolve.Reflector: counterfactual.Deliverable.AsReplayEvent (commit 4) adapts a Deliverable into evolve.ReplayEvent so reverse-thinking conclusions can be replayed against real KPI feedback. evolve does not import counterfactual; counterfactual imports evolve to provide the adapter (one-way dependency, mirrors reflector umbrella shape).
反事实包: 反向思维五步的标准化数据结构 ¶
CLAUDE.md 原则 1 (反向思维) 规定任何非平凡设计决策在 commit 前都要从 反面挑战一遍. 当前 ~/.claude/skills/reverse-think/ 软性 skill 跑 MiniMax 产出 hidden_assumptions / failure_scenarios / verdict / verdict_reason, 但产出形态是自由文本. v0.3-dev 起 counterfactual 包锁定 schema, 让同一 结构可以 (a) 落到 staging.Record 元数据 / (b) 经 evolve.LogReplayer 重放 / (c) 跨租户审计.
本包是 opt-in: core/pkg/engine 不强制 Deliverable 存在. hook 层 (platform/common/safetychain) 看 tools.Metadata.RequiresReverseThinking 决定何时触发反向思维 skill. 默认关闭 = 热路径 0 延迟 / 0 LLM 调用开销.
与 staging / validator / evolve 边界 ¶
- staging.Record: Deliverable 是 Validator 可参考的一种信号, 存于 Record.Metadata 下 MetadataKey 这个 key. counterfactual 本身不是 Validator, 不对 staged diff 出 Verdict.
- validator.Verdict: 作用域是 "此 staged 决策是否可 commit", 由 staging.Engine 消费. Deliverable 是上游证据, 不是 Verdict.
- evolve.Reflector: counterfactual.Deliverable.AsReplayEvent (commit 4) 把 Deliverable 适配成 evolve.ReplayEvent, 让反向思维结论可以与真实 KPI 反馈一起重放学习. evolve 不导入 counterfactual; counterfactual 导入 evolve 提供 adapter (单向依赖, 与 reflector umbrella 模式一致).
Reference files ¶
deliverable.go -- Deliverable struct + Verdict / Step constants deliverable_test.go -- JSON roundtrip + Validate edge cases replay_event.go -- evolve.ReplayEvent adapter (commit 4)
Index ¶
Constants ¶
const ( StepRead = "read" // 读早期方案代码 StepCleverUgly = "clever_ugly" // 标精妙 / 操蛋 StepElevated = "elevated" // 升华思维 (CLI/SDK/API/跨行业) StepReverse = "reverse" // 反向思维 StepConfirmation = "confirmation" // 等待确认 )
Step labels which of the five CLAUDE.md article-1 phases produced this Deliverable. Values are open-ended strings (not strict enum) so future or industry-specific phases can extend without breaking schema.
Step 标记本 Deliverable 出自 CLAUDE.md 原则 1 五步中的哪一步. 取值是 开放字符串 (非严格枚举), 未来或行业特定步骤可扩展不破坏 schema.
const ( // MetaKeyStep tags ReplayEvent.Log.Meta with the Step constant value // (StepRead / StepReverse / etc.) so a Reflector can filter events // produced from a specific phase of the five-step cycle. // // MetaKeyStep 标 ReplayEvent.Log.Meta 携带 Step 常量值 (StepRead / // StepReverse 等), Reflector 可按五步中具体哪一步过滤. MetaKeyStep = "flyto.counterfactual.step" // MetaKeyVerdict tags ReplayEvent.Log.Meta with the Verdict string // so a Reflector can fan events by verdict label without unmarshaling // the full Deliverable from Payload. // // MetaKeyVerdict 标 ReplayEvent.Log.Meta 携带 Verdict 字符串, Reflector // 可按 verdict 标签分发, 不必从 Payload unmarshal 完整 Deliverable. MetaKeyVerdict = "flyto.counterfactual.verdict" // MetaKeySource tags ReplayEvent.Meta to mark the upstream source // of the event ("reverse_think" for events produced by this adapter). // Reflectors that consume from multiple LogReplayer sources use this // to route correctly. // // MetaKeySource 标 ReplayEvent.Meta 表事件上游来源 ("reverse_think" // 表本 adapter 产出). 多 LogReplayer 来源的 Reflector 据此路由. MetaKeySource = "flyto.counterfactual.source" // SourceReverseThink is the canonical value for MetaKeySource on // events produced by AsReplayEvent. // // SourceReverseThink 是 AsReplayEvent 产出事件上 MetaKeySource 的规范值. SourceReverseThink = "reverse_think" )
Meta keys carried on the produced evolve.ReplayEvent. Cross-package consumers (Reflector implementations that want to read reverse-thinking context off ReplayEvent.Meta or ReplayEvent.Log.Meta) match these literal strings. Constants centralized so future renames are coordinated.
产出的 evolve.ReplayEvent 上携带的 Meta key. 跨包消费方 (想从 ReplayEvent.Meta 或 ReplayEvent.Log.Meta 读反向思维上下文的 Reflector 实现) 按这些字面量匹配. 集中常量化便于协调改名.
const MetadataKey = "flyto.counterfactual.deliverable"
MetadataKey is the canonical map key under which a Deliverable should be stored when embedded in staging.Record.Metadata or any other free-form map[string]any context bag. Cross-package consumers reading the bag use this constant instead of stringly-typed literals.
MetadataKey 是 Deliverable 嵌入 staging.Record.Metadata (或其他自由格式 map[string]any 上下文袋) 时的规范 map key. 跨包消费方读取时统一用本常量, 避免散落的字面量.
Variables ¶
var ErrOccurredAtZero = errors.New("counterfactual: occurred_at must be set by producer")
ErrOccurredAtZero is returned by Validate when OccurredAt is the zero time. Producers must stamp the response receipt timestamp; missing one signals a producer bug, not a default-able field.
ErrOccurredAtZero 在 OccurredAt 为零值时由 Validate 返回. 生产方必须打 响应到达时间戳, 缺失表示生产方 bug, 本字段不该有默认.
var ErrVerdictReasonRequired = errors.New("counterfactual: verdict reason required")
ErrVerdictReasonRequired is returned by Validate when VerdictReason is empty.
ErrVerdictReasonRequired 在 VerdictReason 为空时由 Validate 返回.
var ErrVerdictRequired = errors.New("counterfactual: verdict required")
ErrVerdictRequired is returned by Validate when Verdict is empty.
ErrVerdictRequired 在 Verdict 为空时由 Validate 返回.
Functions ¶
This section is empty.
Types ¶
type Deliverable ¶
type Deliverable struct {
// HiddenAssumptions lists the implicit assumptions the original
// recommendation depends on. Surfaced by the reverse pass. Empty
// slice = no hidden assumptions found (a clean run).
//
// HiddenAssumptions 列原方案依赖但未明说的隐含前提. 反向思维识别.
// 空 slice = 没找到隐含假设 (反向思维通过).
HiddenAssumptions []string `json:"hidden_assumptions"`
// FailureScenarios lists concrete failure modes if the original
// recommendation is wrong.
//
// FailureScenarios 列原方案如果错了具体怎么挂.
FailureScenarios []string `json:"failure_scenarios"`
// Verdict is the conclusion: original holds, alternative wins,
// or depends on more information.
//
// Verdict 是结论: 原方案成立 / 替代胜出 / 依赖更多信息.
Verdict Verdict `json:"verdict"`
// VerdictReason is a one to two sentence summary justifying Verdict.
//
// VerdictReason 是一两句话总结 Verdict 的依据.
VerdictReason string `json:"verdict_reason"`
// ToolName is the tool whose call site triggered this reverse pass.
// Empty when the Deliverable is not associated with a tool call
// (e.g. a free-standing design review).
//
// ToolName 是触发本次反向思维的工具调用名. 不与工具调用关联时
// (例如独立设计 review) 留空.
ToolName string `json:"tool_name,omitempty"`
// Step labels which of the five article-1 phases produced this.
// See the Step* constants. Empty = unspecified (caller did not tag).
//
// Step 标记五步中哪一步产出. 见 Step* 常量. 空 = 未指定.
Step string `json:"step,omitempty"`
// DecisionID optionally correlates this Deliverable with a
// staging.Record or any decision identifier the consumer assigns.
// Empty when no decision context is in scope.
//
// DecisionID 可选, 把 Deliverable 与 staging.Record 或消费方约定的
// 决策 id 关联. 无决策上下文时留空.
DecisionID string `json:"decision_id,omitempty"`
// OccurredAt is when the reverse pass produced this Deliverable.
// Set by the producer (e.g. reverse_think.Client.Run uses time.Now()
// at response receipt). Never auto-stamped by Validate or any
// downstream consumer -- a missing timestamp signals a programming
// error in the producer rather than a sane default.
//
// OccurredAt 是反向思维产出本 Deliverable 的时刻. 由生产方设置 (例如
// reverse_think.Client.Run 在收到响应时取 time.Now()). Validate 和下游
// 消费方都不自动补全 -- 时间戳缺失表示生产方编码 bug, 不应静默兜底.
OccurredAt time.Time `json:"occurred_at"`
}
Deliverable is the structured product of one reverse-thinking pass. JSON tag names mirror the prompt template in ~/.claude/skills/reverse-think/SKILL.md so a raw MiniMax response can be unmarshaled directly into this struct (after wrapping the metadata fields, see reverse_think.Client.Run in core/pkg/skills/reverse_think).
Deliverable 是一次反向思维的结构化产物. JSON tag 字段名对齐 ~/.claude/skills/reverse-think/SKILL.md 的 prompt 模板, 让 MiniMax 原始 返回可直接 unmarshal 入本结构 (元数据字段由 reverse_think.Client.Run 包装, 见 core/pkg/skills/reverse_think).
func (*Deliverable) AsReplayEvent ¶
func (d *Deliverable) AsReplayEvent() evolve.ReplayEvent
AsReplayEvent adapts the Deliverable into an evolve.ReplayEvent so a reverse-thinking conclusion can be pushed through evolve.LogReplayer for consumption by registered evolve.Reflector implementations. This closes the "evolution sink" loop: reverse-thinking verdicts are replayed against real KPI feedback over time so the system learns the correlation between "what reverse-think said" and "what actually happened".
Why the adapter lives in counterfactual, not in evolve ¶
evolve stays schema-agnostic by design (doc.go: "All interfaces exchange business data as any / string / float64. The engine makes no assumption about any industry schema."). Importing counterfactual into evolve would couple a generic learning loop to one specific upstream signal type. The reverse direction is fine -- counterfactual declares "I know how to feed evolve" -- and mirrors the reflector umbrella's 4 cross-family adapters (Validator/Evaluator/evolve.Reflector are sibling-callable, replaceable backends; counterfactual provides a Deliverable-to-LogEntry bridge in the same shape).
Feedback nil at production time ¶
At reverse-think time the decision has not yet produced a real-world outcome -- there is no KPI to score against. Feedback returns nil here, matching evolve.ReplayEvent's documented "decision just landed, KPI has not arrived yet" semantics. The platform layer fills Feedback later, when the actual KPI signal arrives (e.g. on-time rate from a settlement match), via evolve.FeedbackChannel.Report. Reflectors that pair Log + Feedback to learn the verdict / outcome correlation read both from a paired-up event downstream.
Payload safety ¶
Payload is a Clone of d, not d itself, so a Reflector that mutates the Payload (some implementations rewrite Meta during ingestion) does not retroactively corrupt the staging Record or audit log that still holds a reference to d.
nil receiver ¶
Calling on a nil *Deliverable returns the zero ReplayEvent rather than panicking. This is consistent with Clone's nil tolerance and lets defensive callers chain the adapter without explicit nil checks.
AsReplayEvent 把 Deliverable 适配为 evolve.ReplayEvent, 让反向思维结论 可经 evolve.LogReplayer 推给注册的 evolve.Reflector 实现消费. 这关上 "进化沉淀" 循环: 反向思维 verdict 与现实 KPI 反馈随时间重放, 系统学习 "反向思维说的" 与 "实际发生的" 相关性.
为什么 adapter 在 counterfactual 包不在 evolve 包: evolve 保持 schema- agnostic (doc.go 明示). evolve import counterfactual 会把通用学习循环 绑到一种上游信号类型. 反方向 OK -- counterfactual 声明 "我知道怎么喂 evolve" -- 与 reflector umbrella 4 个跨家族 adapter 同形态 (Validator / Evaluator / evolve.Reflector 同族可换后端, counterfactual 提供 Deliverable 到 LogEntry 的桥, 形态一致).
反向思维时 Feedback 为 nil: 决策尚未产生现实后果, 没有 KPI 可打分. 与 evolve.ReplayEvent godoc 的 "决策刚发生, 反馈延迟中" 语义一致. platform 层在真实 KPI 到达时 (如账单匹配的准时率) 经 evolve.FeedbackChannel.Report 填 Feedback. Reflector 配对 Log + Feedback 学习 verdict / 后果相关性时 从下游配对事件读两端.
Payload safety: Payload 是 d 的 Clone 而非 d 本身. Reflector 修改 Payload (有些实现摄入时重写 Meta) 不回溯污染仍持引用的 staging.Record 或审计日志.
nil receiver: 在 nil *Deliverable 上调返回零值 ReplayEvent 而非 panic. 与 Clone 的 nil 兼容一致, 防御性调用方可链式调不必显式 nil 检查.
func (*Deliverable) Clone ¶
func (d *Deliverable) Clone() *Deliverable
Clone returns a deep copy of the Deliverable. Slices are duplicated so consumers can mutate the returned value without affecting the original (relevant for audit trails that snapshot at multiple stages).
Clone 返回 Deliverable 的深拷贝. slice 复制, 消费方可改返回值不影响 原值 (审计链多阶段快照场景需要).
func (*Deliverable) Validate ¶
func (d *Deliverable) Validate() error
Validate checks the minimal contract for downstream consumption: non-empty Verdict, non-empty VerdictReason, non-zero OccurredAt. HiddenAssumptions and FailureScenarios are allowed empty (a clean reverse pass legitimately finds nothing). ToolName / Step / DecisionID are optional context fields.
CLEVER: Validate intentionally does NOT enforce Verdict ∈ {A_holds, B_wins, depends_on_X} so MiniMax (or future LLMs) can introduce new verdict labels without core release. The cost is downstream consumers must handle unknown verdicts; the benefit is forward compatibility without coordinated upgrade.
Validate 检查下游消费的最小契约: Verdict 非空, VerdictReason 非空, OccurredAt 非零值. HiddenAssumptions 与 FailureScenarios 允许空 (干净的反向思维 pass 合法地找不到东西). ToolName / Step / DecisionID 是可选上下文字段.
精妙之处 (CLEVER): Validate 刻意不强制 Verdict ∈ {A_holds, B_wins, depends_on_X}, 让 MiniMax (或未来 LLM) 引入新 verdict 标签时不必等 core 发版. 代价是下游要处理未知 verdict; 收益是无须协调升级的向前兼容.
替代方案 (已否决): Validate 把 Verdict 限定在三个常量 -- 否决: 任何 新 verdict 都得改 core 发版, 与 docs/api-reference.md "schema-agnostic" 原则冲突.
type Verdict ¶
type Verdict string
Verdict is the reverse-thinking conclusion strength. Values mirror the Anthropic-compatible JSON schema returned by ~/.claude/skills/reverse-think/ (see SKILL.md prompt template).
Verdict 是反向思维结论的强度. 取值对齐 ~/.claude/skills/reverse-think/ 返回的 Anthropic 兼容 JSON schema (见 SKILL.md 的 prompt 模板).
const ( // VerdictAHolds means the original recommendation survives the // reverse pass; proceed. // // VerdictAHolds 表示原方案经反向思维仍成立, 可继续. VerdictAHolds Verdict = "A_holds" // VerdictBWins means the alternative wins; switch course. // // VerdictBWins 表示替代方案胜出, 应换路径. VerdictBWins Verdict = "B_wins" // VerdictDepends means the answer hinges on a dimension neither // option fully captures; needs more information before commit. // // VerdictDepends 表示答案取决于双方都未充分捕捉的维度, 需补足信息再决定. VerdictDepends Verdict = "depends_on_X" )