kairos-chain 3.35.0 → 3.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +129 -0
- data/bin/kairos-chain +80 -0
- data/lib/kairos_mcp/version.rb +1 -1
- data/templates/knowledge/design_to_implementation_workflow/design_to_implementation_workflow.md +5 -0
- data/templates/knowledge/multi_llm_review_workflow/multi_llm_review_workflow.md +181 -2
- data/templates/skillsets/synoptis/config/synoptis.yml +10 -0
- data/templates/skillsets/synoptis/lib/synoptis/constitutive/attestation_chain.rb +53 -3
- data/templates/skillsets/synoptis/lib/synoptis/constitutive/content_attestation_entry.rb +17 -4
- data/templates/skillsets/synoptis/lib/synoptis/constitutive/proposal_criterion.rb +11 -3
- data/templates/skillsets/synoptis/lib/synoptis/constitutive/revocation_withdrawal_entry.rb +60 -0
- data/templates/skillsets/synoptis/lib/synoptis/constitutive/subject_ref.rb +43 -6
- data/templates/skillsets/synoptis/lib/synoptis/tool_helpers.rb +14 -1
- data/templates/skillsets/synoptis/lib/synoptis.rb +1 -0
- data/templates/skillsets/synoptis/skillset.json +3 -1
- data/templates/skillsets/synoptis/test/test_l2_constitutive_slice1.rb +4 -1
- data/templates/skillsets/synoptis/test/test_l2_constitutive_slice2.rb +121 -0
- data/templates/skillsets/synoptis/test/test_l2_constitutive_slice3.rb +117 -0
- data/templates/skillsets/synoptis/tools/l2_attestation_commit.rb +68 -20
- data/templates/skillsets/synoptis/tools/l2_attestation_revoke.rb +104 -0
- data/templates/skillsets/synoptis/tools/l2_attestation_scan.rb +8 -4
- data/templates/skillsets/synoptis/tools/l2_attestation_view.rb +64 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0da61a60c3ecd5e32e85052668638f65522376949dc5577a54ab5db754e7118a
|
|
4
|
+
data.tar.gz: fe8f12657d7032c9047368372dd6e49bc733e83804a55c83717608980d0017bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e466478fd4e87509d7b4efa1218813fba0f395ea2296852a69b4f2190070ddddf59b2a8961f4b1a34d71ad4d1666716df105ad609f21e6ac9f91e228a58dbf8
|
|
7
|
+
data.tar.gz: 4cf41c84ff411cb3e18de05683343eb3f84a3281c7f93461ec90c6ea4806919ebf58ae70694e0e629248189411b6b815d764cb3e10f6956d502f5222fc60cc46
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,135 @@ All notable changes to the `kairos-chain` gem will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project follows [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [3.39.0] - 2026-07-05
|
|
8
|
+
|
|
9
|
+
### Added — L1 `multi_llm_review_workflow` v3.5 → v3.6: Step 0.25 Unknowns Pass
|
|
10
|
+
|
|
11
|
+
Adds a pre-draft unknown-discovery discipline to the multi-LLM review workflow,
|
|
12
|
+
adapted from Thariq Shihipar's "A Field Guide to Fable: Finding Your Unknowns"
|
|
13
|
+
(2026-07-03), techniques ① blindspot pass and ③ one-question-at-a-time interview.
|
|
14
|
+
Bundled L1 knowledge only — no code paths changed.
|
|
15
|
+
|
|
16
|
+
- **Step 0.25 (pre-draft, qualifying reviews only)**: before drafting a design or
|
|
17
|
+
knowledge/documentation artifact routed to full multi-LLM review, the orchestrator
|
|
18
|
+
enumerates its blind-spot questions (without self-answering) and interviews the human
|
|
19
|
+
one question at a time, then triages every surfaced unknown to Resolved / Declared /
|
|
20
|
+
Draft-deferred. Moves unknown discovery from the expensive channel (review rounds) to
|
|
21
|
+
the cheap channel (pre-draft dialogue).
|
|
22
|
+
- **Five invariants INV-U1–U5**: front-load gate (no dispatch before the pass);
|
|
23
|
+
human gate (no orchestrator self-answering); constitutive recording (unconditional L2
|
|
24
|
+
record, incl. zero-result); bounded demotion of declared unknowns (provenance bound +
|
|
25
|
+
inverted default, closing a laundering channel); human-only classification authority.
|
|
26
|
+
- **Unattended (autonomous loop) clause**: fail-closed — every surfaced unknown is
|
|
27
|
+
judgment-shaped by default and stops the run; only a human-authored mandate may
|
|
28
|
+
pre-classify categories as non-judgment-shaped, and unattended declarations gain no
|
|
29
|
+
reviewer-demotion power until human-ratified. Mandate expressiveness, ratification
|
|
30
|
+
protocol, and the pending-question queue are deferred to the Autonomous Growth Loop
|
|
31
|
+
guard track.
|
|
32
|
+
- Three-location L1 sync (instance `.kairos/knowledge/`, gem `knowledge/`, gem
|
|
33
|
+
`templates/knowledge/`); change recorded to blockchain (hash a74a11b7…). One-line
|
|
34
|
+
cross-reference added to `design_to_implementation_workflow`.
|
|
35
|
+
- Designed via its own 3-round self-referential multi-LLM review (R1 REVISE 2/6 →
|
|
36
|
+
R2 REVISE 2/6 → R3 4/6 APPROVE; design record
|
|
37
|
+
`docs/drafts/multi_llm_review_unknowns_pass_v0.3.1_FROZEN.md`).
|
|
38
|
+
|
|
39
|
+
## [3.38.0] - 2026-07-05
|
|
40
|
+
|
|
41
|
+
### Added — Synoptis L2 attestation: ACT-5 trigger sources + scan CLI (Slice 3b/3c)
|
|
42
|
+
|
|
43
|
+
Makes the ACT-5 trigger point distinguishable and invocable from a session-end
|
|
44
|
+
Stop hook, so firing + trigger-record logging can be guaranteed rather than relying
|
|
45
|
+
on the orchestrator remembering to run the tool.
|
|
46
|
+
|
|
47
|
+
- **Trigger source (3b)**: `l2_attestation_scan` gains a `trigger_source` argument
|
|
48
|
+
(`manual` / `orchestrator_session_end` / `session_end_hook`), recorded in the trigger
|
|
49
|
+
record (`AttestationChain#append_trigger(source:)`). ACT-5's "at least one defined
|
|
50
|
+
trigger point" is now distinguishable in the operational log, and the tool documents
|
|
51
|
+
its two auto-firing paths (orchestrator-at-session-end for delivery+approval; Stop hook
|
|
52
|
+
for a mechanical liveness guarantee).
|
|
53
|
+
- **Scan CLI (3c)**: new `kairos-chain attestation scan [--session SID] [--source SRC]
|
|
54
|
+
[--data-dir DIR]`. It loads the constitutive criterion, proposes a session's
|
|
55
|
+
judgment-bearing contexts, and appends a trigger record — propose-only, never attests
|
|
56
|
+
(ACT-1 keeps attestation human-approved through the MCP tool). This is the shell
|
|
57
|
+
entry point a session-end Stop hook calls. Verified end-to-end.
|
|
58
|
+
|
|
59
|
+
The two firing mechanisms are complementary: the orchestrator convention delivers
|
|
60
|
+
proposals to a present human for approval; the Stop hook guarantees the trigger fires
|
|
61
|
+
and is logged even when the orchestrator does not. The settings.json Stop-hook wiring is
|
|
62
|
+
a per-project harness step (not shipped in the gem). 17 Slice-3 unit tests green (incl.
|
|
63
|
+
trigger-source); Slices 1 (35) and 2 (25) unchanged.
|
|
64
|
+
|
|
65
|
+
## [3.37.0] - 2026-07-05
|
|
66
|
+
|
|
67
|
+
### Added — Synoptis L2 attestation: semantic criterion + snapshot (Slice 3a)
|
|
68
|
+
|
|
69
|
+
Matures the ACT-2 selection criterion and adds the optional content snapshot
|
|
70
|
+
(§Kinds), without embedding an LLM call inside the tool (selective-survival: the
|
|
71
|
+
in-Ruby LLM scoring is deferred until the frontmatter criterion is observed to be
|
|
72
|
+
insufficient).
|
|
73
|
+
|
|
74
|
+
- **Semantic layer (ACT-2)**: `l2_attestation_scan` now attaches a bounded content
|
|
75
|
+
`preview` to each proposal and surfaces a `rubric` (config `semantic_rubric`). The
|
|
76
|
+
orchestrator applies the rubric to the previews to decide which frontmatter-filtered
|
|
77
|
+
candidates to propose to the human — the LLM-semantic judgment lives in the
|
|
78
|
+
orchestrator, not in the tool. The criterion stays inspectable and revisable by
|
|
79
|
+
editing config (`judgment_types`, `semantic_rubric`, `preview_chars`).
|
|
80
|
+
- **Snapshot (§Kinds optional)**: `l2_attestation_commit(embed_snapshot: true)` embeds
|
|
81
|
+
the subject's current content in the entry, so audits needing the original wording
|
|
82
|
+
have it (LED-2a preserves it). The snapshot's SHA256 equals the entry digest, so it is
|
|
83
|
+
self-verifying. Bounded by `max_snapshot_bytes` (default 256KB); oversize content is
|
|
84
|
+
refused (`snapshot_too_large`) rather than truncated.
|
|
85
|
+
- `SubjectRef` gains `content_text` / `content_preview`.
|
|
86
|
+
|
|
87
|
+
Deferred (until observed need): in-Ruby `llm_client` scoring of candidates; session-end
|
|
88
|
+
auto-firing of the trigger (ACT-5 liveness is currently satisfied by explicit/orchestrator
|
|
89
|
+
scan). 12 Slice-3 unit tests + 4 tool integration tests green; Slices 1 (35) and 2 (25)
|
|
90
|
+
unchanged.
|
|
91
|
+
|
|
92
|
+
## [3.36.0] - 2026-07-05
|
|
93
|
+
|
|
94
|
+
### Added — Synoptis L2 attestation: supersession + revocation (Slice 2)
|
|
95
|
+
|
|
96
|
+
Records the rewrite/withdrawal lifecycle of a constitutive attestation by appending,
|
|
97
|
+
never editing (the bank-ledger correction model). Completes §Kinds' two attestation-
|
|
98
|
+
ledger entry kinds and satisfies LED-2b (lineage) and ACT-3 (approval binding).
|
|
99
|
+
|
|
100
|
+
- **Supersession**: a re-attestation of an already-attested subject is now a
|
|
101
|
+
supersession — `l2_attestation_commit` auto-derives the current head and commits its
|
|
102
|
+
entry_id as `target_ref` (ContentAttestationEntry gains `target_ref`). The first
|
|
103
|
+
attestation about a subject carries none.
|
|
104
|
+
- **Revocation-withdrawal**: new `RevocationWithdrawalEntry` kind (commits subject +
|
|
105
|
+
target_ref + moment, no digest/content) and a new `l2_attestation_revoke` tool. It
|
|
106
|
+
appends a withdrawal of the subject's current head; like any ledger entry it requires
|
|
107
|
+
human approval (ACT-1) and is itself append-only, so "claimed then withdrawn" survives.
|
|
108
|
+
- **ACT-3 binding**: the commit/revoke proposal returns the digest and the target it
|
|
109
|
+
would act on; the approving call echoes them as `expected_digest` /
|
|
110
|
+
`expected_target_ref`. If the live content changed (digest_mismatch) or the head moved
|
|
111
|
+
(target_moved) between proposal and approval, the append is refused, not silent.
|
|
112
|
+
- **Fold**: `AttestationChain#current_head` / `current_state` fold a subject's entries
|
|
113
|
+
(first → supersessions → withdrawals) into a status (none/attested/withdrawn); a new
|
|
114
|
+
read-only `l2_attestation_view` tool surfaces it. Fold ordering rule fixed per §11: a
|
|
115
|
+
revocation clears the head only when it targets the current head.
|
|
116
|
+
|
|
117
|
+
Synoptis's trust engine (duplicate hard-reject, TTL, revocation store) is untouched —
|
|
118
|
+
the constitutive ledger is a separate chain, so re-attest-as-supersession is native and
|
|
119
|
+
no reconciliation was needed. 25 Slice-2 unit tests + 14 tool integration tests green;
|
|
120
|
+
Slice 1 (35) unchanged.
|
|
121
|
+
|
|
122
|
+
## [3.35.1] - 2026-07-05
|
|
123
|
+
|
|
124
|
+
### Fixed — Synoptis L2 attestation: frontmatter parsing dropped all proposals
|
|
125
|
+
|
|
126
|
+
`l2_attestation_scan` surfaced zero proposals against real L2 contexts because
|
|
127
|
+
`SubjectRef.extract_frontmatter` used `YAML.safe_load` without permitting `Date`:
|
|
128
|
+
a `date:` field (present in every real context's frontmatter) raised
|
|
129
|
+
`Psych::DisallowedClass`, which was rescued to `nil`, so the `type` was never read
|
|
130
|
+
and no context matched the criterion. Caught by live end-to-end verification, not
|
|
131
|
+
by unit tests (whose fixtures lacked a `date:` field). Fix permits `[Date, Time]`
|
|
132
|
+
and adds a regex fallback for the single `type` scalar so the criterion is robust
|
|
133
|
+
against any frontmatter YAML that `safe_load` rejects. Unit fixtures now carry a
|
|
134
|
+
`date:` field to guard the regression.
|
|
135
|
+
|
|
7
136
|
## [3.35.0] - 2026-07-05
|
|
8
137
|
|
|
9
138
|
### Added — Synoptis: L2 constitutive attestation (Slice 1)
|
data/bin/kairos-chain
CHANGED
|
@@ -343,6 +343,86 @@ when 'upgrade'
|
|
|
343
343
|
end
|
|
344
344
|
exit
|
|
345
345
|
|
|
346
|
+
when 'attestation'
|
|
347
|
+
# Constitutive L2 attestation CLI (Slice 3c). Currently one action: `scan`, the
|
|
348
|
+
# ACT-5 trigger point invokable from a session-end Stop hook so that firing +
|
|
349
|
+
# trigger-record logging is guaranteed even when the orchestrator does not run the
|
|
350
|
+
# MCP tool. It proposes only and logs a trigger record; it never attests (ACT-1
|
|
351
|
+
# keeps attestation human-approved through the MCP tool).
|
|
352
|
+
ARGV.shift # Remove 'attestation'
|
|
353
|
+
|
|
354
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
355
|
+
require 'kairos_mcp'
|
|
356
|
+
|
|
357
|
+
if (idx = ARGV.index('--data-dir'))
|
|
358
|
+
KairosMcp.data_dir = File.expand_path(ARGV[idx + 1])
|
|
359
|
+
ARGV.delete_at(idx + 1)
|
|
360
|
+
ARGV.delete_at(idx)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
action = ARGV.shift
|
|
364
|
+
case action
|
|
365
|
+
when 'scan'
|
|
366
|
+
require 'json'
|
|
367
|
+
require 'yaml'
|
|
368
|
+
require 'fileutils'
|
|
369
|
+
|
|
370
|
+
session = (i = ARGV.index('--session')) ? ARGV[i + 1] : nil
|
|
371
|
+
source = (i = ARGV.index('--source')) ? ARGV[i + 1] : 'session_end_hook'
|
|
372
|
+
|
|
373
|
+
syn_lib = File.join(KairosMcp.skillsets_dir, 'synoptis', 'lib')
|
|
374
|
+
unless File.directory?(syn_lib)
|
|
375
|
+
puts JSON.generate(status: 'error', message: 'synoptis skillset not installed')
|
|
376
|
+
exit 0
|
|
377
|
+
end
|
|
378
|
+
$LOAD_PATH.unshift(syn_lib)
|
|
379
|
+
require 'synoptis/registry/file_registry'
|
|
380
|
+
require 'synoptis/constitutive/subject_ref'
|
|
381
|
+
require 'synoptis/constitutive/proposal_criterion'
|
|
382
|
+
require 'synoptis/constitutive/attestation_chain'
|
|
383
|
+
|
|
384
|
+
context_dir = KairosMcp.context_dir
|
|
385
|
+
session ||= if Dir.exist?(context_dir)
|
|
386
|
+
Dir.children(context_dir)
|
|
387
|
+
.select { |c| File.directory?(File.join(context_dir, c)) }
|
|
388
|
+
.max_by { |c| File.mtime(File.join(context_dir, c)) }
|
|
389
|
+
end
|
|
390
|
+
unless session
|
|
391
|
+
puts JSON.generate(status: 'error', message: 'no session found to scan')
|
|
392
|
+
exit 0
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
cfg_path = File.join(KairosMcp.skillsets_dir, 'synoptis', 'config', 'synoptis.yml')
|
|
396
|
+
const_cfg = (File.exist?(cfg_path) ? (YAML.safe_load(File.read(cfg_path)) || {}) : {})['constitutive'] || {}
|
|
397
|
+
|
|
398
|
+
data_dir = File.join(KairosMcp.data_dir, 'synoptis_data')
|
|
399
|
+
FileUtils.mkdir_p(data_dir)
|
|
400
|
+
registry = Synoptis::Registry::FileRegistry.new(data_dir: data_dir)
|
|
401
|
+
chain = Synoptis::Constitutive::AttestationChain.new(registry: registry)
|
|
402
|
+
criterion = Synoptis::Constitutive::ProposalCriterion.new(
|
|
403
|
+
context_dir: context_dir,
|
|
404
|
+
judgment_types: const_cfg['judgment_types'] ||
|
|
405
|
+
Synoptis::Constitutive::ProposalCriterion::DEFAULT_JUDGMENT_TYPES,
|
|
406
|
+
preview_chars: const_cfg['preview_chars'] ||
|
|
407
|
+
Synoptis::Constitutive::ProposalCriterion::DEFAULT_PREVIEW_CHARS
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
proposals = criterion.propose(session_id: session)
|
|
411
|
+
chain.append_trigger(surfaced_count: proposals.length, source: source)
|
|
412
|
+
puts JSON.generate(
|
|
413
|
+
status: 'proposed',
|
|
414
|
+
session_id: session,
|
|
415
|
+
trigger_source: source,
|
|
416
|
+
surfaced_count: proposals.length,
|
|
417
|
+
proposals: proposals
|
|
418
|
+
)
|
|
419
|
+
exit 0
|
|
420
|
+
else
|
|
421
|
+
warn "Unknown attestation action: #{action.inspect}"
|
|
422
|
+
warn 'Usage: kairos-chain attestation scan [--session SID] [--source SRC] [--data-dir DIR]'
|
|
423
|
+
exit 1
|
|
424
|
+
end
|
|
425
|
+
|
|
346
426
|
when 'mode'
|
|
347
427
|
ARGV.shift # Remove 'mode' from ARGV
|
|
348
428
|
|
data/lib/kairos_mcp/version.rb
CHANGED
data/templates/knowledge/design_to_implementation_workflow/design_to_implementation_workflow.md
CHANGED
|
@@ -65,6 +65,11 @@ different bugs.
|
|
|
65
65
|
|
|
66
66
|
### Design Phase
|
|
67
67
|
|
|
68
|
+
#### Pre-draft: Unknowns Pass
|
|
69
|
+
- For artifacts routed to full multi-LLM review, run the Unknowns Pass
|
|
70
|
+
(blindspot enumeration + human interview) **before** drafting v0.1 —
|
|
71
|
+
see `multi_llm_review_workflow` Step 0.25 (v3.6+).
|
|
72
|
+
|
|
68
73
|
#### Solo Design (v0.1)
|
|
69
74
|
- Single LLM (Opus-class) produces initial design
|
|
70
75
|
- Include: architecture, component design, schema, error handling, phase boundaries
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: multi_llm_review_workflow
|
|
3
3
|
description: "Multi-LLM review methodology and execution — workflow pattern, CLI tooling, consensus analysis, Persona Assembly. Applicable to design, implementation, documentation, or any artifact."
|
|
4
|
-
version: "3.
|
|
4
|
+
version: "3.6"
|
|
5
5
|
tags:
|
|
6
6
|
- workflow
|
|
7
7
|
- review
|
|
@@ -43,6 +43,154 @@ Codex (c)-class value-divergent REJECTs as blocking, which causes review loops t
|
|
|
43
43
|
fail to converge. The cross-reference exists in `related:` frontmatter; this step
|
|
44
44
|
makes it an explicit pre-condition rather than an implicit hint.
|
|
45
45
|
|
|
46
|
+
## Step 0.25 — Unknowns Pass (pre-draft, qualifying reviews only)
|
|
47
|
+
|
|
48
|
+
> **Numbering vs timing**: Step 0 and Step 0.5 execute at review time,
|
|
49
|
+
> immediately before dispatch. Step 0.25 executes **pre-draft** — earlier in
|
|
50
|
+
> wall-clock time than both. The numeric order is document order, not
|
|
51
|
+
> execution order.
|
|
52
|
+
|
|
53
|
+
**Applies to** artifacts that this workflow's decision heuristic routes to
|
|
54
|
+
full multi-LLM review, in the design-phase and knowledge/documentation
|
|
55
|
+
review types (design review, knowledge/documentation-update review, document
|
|
56
|
+
review). Throughout this step, "qualifying review" means exactly this set.
|
|
57
|
+
Artifacts below that threshold (single-LLM review, self-review, skip) are
|
|
58
|
+
exempt — the pass inherits the existing tier heuristic rather than
|
|
59
|
+
introducing a second gate. Implementation-phase reviews are out of scope.
|
|
60
|
+
|
|
61
|
+
The problem this step solves: the workflow otherwise begins at "artifact
|
|
62
|
+
exists," so unknowns the author never considered enter drafts undetected and
|
|
63
|
+
are excavated by reviewers round by round, inflating round counts. The pass
|
|
64
|
+
moves unknown discovery from the expensive channel (review rounds) to the
|
|
65
|
+
cheap channel (pre-draft dialogue), and makes the residue explicit.
|
|
66
|
+
(Source: Thariq Shihipar, "A Field Guide to Fable: Finding Your Unknowns,"
|
|
67
|
+
2026-07-03 — techniques ① blindspot pass and ③ interview. Design record:
|
|
68
|
+
`docs/drafts/multi_llm_review_unknowns_pass_v0.3.1_FROZEN.md`.)
|
|
69
|
+
|
|
70
|
+
### Structure of the pass
|
|
71
|
+
|
|
72
|
+
Step 0.25 runs after the decision to produce a qualifying artifact and
|
|
73
|
+
before its first draft. Two moves, one triage:
|
|
74
|
+
|
|
75
|
+
1. **Blindspot enumeration.** The orchestrator, holding full project
|
|
76
|
+
context, enumerates the questions most likely to change the design if
|
|
77
|
+
answered differently — *without answering them itself*. This is a bounded
|
|
78
|
+
search discipline, not a completeness guarantee: the pass is judged by
|
|
79
|
+
whether it reliably surfaces the highest-impact questions available to
|
|
80
|
+
the orchestrator's current context (nothing-missed is unachievable per
|
|
81
|
+
Prop 6).
|
|
82
|
+
2. **Interview.** The orchestrator puts the enumerated questions to the
|
|
83
|
+
human, one question at a time, ordered by decision impact. The interview
|
|
84
|
+
ends when both parties judge that remaining questions will only be
|
|
85
|
+
answerable once a draft exists. Ending the interview does not discharge
|
|
86
|
+
the triage below: every enumerated unknown — including those the
|
|
87
|
+
interview never reached — still exits through it.
|
|
88
|
+
|
|
89
|
+
**Triage.** Every surfaced unknown exits the pass in exactly one of two
|
|
90
|
+
**terminal** states, possibly via one **transient** state:
|
|
91
|
+
|
|
92
|
+
- **Resolved** (terminal) — answered by the human; the answer feeds the
|
|
93
|
+
Design Direction Block (Step 0.5).
|
|
94
|
+
- **Declared** (terminal) — recorded in the artifact as an *Open Unknown*
|
|
95
|
+
and registered in the artifact's backlog section. A human's explicit
|
|
96
|
+
decision *not* to answer a question is itself a human judgment and routes
|
|
97
|
+
the unknown here — a legitimate attended outcome.
|
|
98
|
+
- **Draft-deferred** (transient, non-terminal) — marked for mandatory
|
|
99
|
+
re-triage after drafting. Re-triage collapses each draft-deferred unknown
|
|
100
|
+
into Resolved or Declared **before review dispatch** (a precondition of
|
|
101
|
+
dispatch under INV-U1). INV-U2 applies at re-triage exactly as at the
|
|
102
|
+
interview: collapsing to Resolved requires the human's answer, so
|
|
103
|
+
unattended re-triage can only collapse to Declared.
|
|
104
|
+
|
|
105
|
+
### Invariants
|
|
106
|
+
|
|
107
|
+
**INV-U1 (front-load gate).** Round 1 of a qualifying review is not
|
|
108
|
+
dispatched for an artifact that has not passed through the Unknowns Pass.
|
|
109
|
+
At dispatch time, every unknown the pass surfaced is in a terminal state:
|
|
110
|
+
resolved by the human, or declared in the artifact. A draft-deferred unknown
|
|
111
|
+
still in its transient state blocks dispatch until re-triaged.
|
|
112
|
+
|
|
113
|
+
**INV-U2 (human gate).** The answerer in the interview is the human. The
|
|
114
|
+
orchestrator posing a question and answering it itself does not constitute
|
|
115
|
+
resolution. (Same conceptual line as ACT-1 in L2 attestation — analogical,
|
|
116
|
+
not a shared implementation: judgment-shaped decisions belong to the human.)
|
|
117
|
+
|
|
118
|
+
**INV-U3 (constitutive recording).** The products of the pass — enumerated
|
|
119
|
+
questions, human answers, declared Open Unknowns — are recorded to L2. A
|
|
120
|
+
discovered unknown is an asset, not a consumable. The record is written
|
|
121
|
+
unconditionally: a pass that surfaces no unknowns writes an explicit
|
|
122
|
+
zero-result record, which is what makes a skipped pass distinguishable from
|
|
123
|
+
a pass that surfaced nothing. The record is instance-local; outbound sharing
|
|
124
|
+
is governed by the instance's existing outbound-sharing discipline — this
|
|
125
|
+
invariant authorizes no new disclosure surface.
|
|
126
|
+
|
|
127
|
+
**INV-U4 (declared unknowns are non-blocking — bounded).** A reviewer
|
|
128
|
+
finding that merely restates a declared Open Unknown is classified (c)
|
|
129
|
+
advisory by default, subject to two bounds:
|
|
130
|
+
|
|
131
|
+
- *Provenance bound*: the demotion applies only to unknowns whose specific
|
|
132
|
+
declaration a human has seen — declared through an attended pass, or
|
|
133
|
+
ratified by a human at a subsequent attended session. Declarations made by
|
|
134
|
+
an orchestrator alone (including unattended declarations under a mandate's
|
|
135
|
+
category-level pre-classification) carry no demotion power until so
|
|
136
|
+
ratified.
|
|
137
|
+
- *Inverted default*: when the orchestrator is uncertain whether a finding
|
|
138
|
+
merely restates a declared unknown or shows that the declared deferral is
|
|
139
|
+
itself unsafe, the finding is treated as blocking. This deliberately
|
|
140
|
+
inverts the usual "unsure between (b) and (c) → (c)" rule, because here
|
|
141
|
+
the doubt concerns whether a gate is being laundered, and gates fail
|
|
142
|
+
closed.
|
|
143
|
+
|
|
144
|
+
Findings about the *integrity* of the design — internal contradiction,
|
|
145
|
+
unrealizable invariant, an Open Unknown whose declared deferral is itself
|
|
146
|
+
unsafe — remain (a)/(b) and block as before.
|
|
147
|
+
|
|
148
|
+
**INV-U5 (classification authority).** The judgment-shaped /
|
|
149
|
+
non-judgment-shaped classification of an unknown is a human prerogative.
|
|
150
|
+
Attended, the human exercises it through the interview itself (answering,
|
|
151
|
+
declining, or judging a question draft-answerable). Unattended, every
|
|
152
|
+
surfaced unknown is judgment-shaped by default; only a human-authored
|
|
153
|
+
mandate may pre-classify named categories as non-judgment-shaped for a given
|
|
154
|
+
run. An orchestrator's self-classification has no force. (Fail-closed:
|
|
155
|
+
unclassified ⇒ judgment-shaped ⇒ stop.)
|
|
156
|
+
|
|
157
|
+
### Unattended execution (autonomous loops)
|
|
158
|
+
|
|
159
|
+
In an unattended context, the "resolved" exit is unavailable. INV-U2 is
|
|
160
|
+
preserved — the system does not self-answer — and INV-U5 governs
|
|
161
|
+
classification:
|
|
162
|
+
|
|
163
|
+
- **Judgment-shaped** (the unattended default for ALL unknowns): fail-closed
|
|
164
|
+
stop; the question is recorded and queued for the next attended session.
|
|
165
|
+
- **Non-judgment-shaped** (only via human-authored mandate
|
|
166
|
+
pre-classification): declared as Open Unknown; the run proceeds, but per
|
|
167
|
+
INV-U4's provenance bound the declaration carries no demotion power until
|
|
168
|
+
human-ratified — reviewer restatements remain blocking.
|
|
169
|
+
|
|
170
|
+
Consequence: an unattended run without a mandate cannot proceed past any
|
|
171
|
+
surfaced unknown. This is intended — it makes "unattended design review
|
|
172
|
+
without human pre-delegation" structurally inert rather than quietly
|
|
173
|
+
self-certifying, and liberal declaring yields no convergence advantage.
|
|
174
|
+
|
|
175
|
+
Stop semantics, as an invariant: repeated unattended encounters with the
|
|
176
|
+
same pending question produce no new side effects and no forward motion —
|
|
177
|
+
re-stopping is idempotent; forward motion requires a human answer. The
|
|
178
|
+
mandate's expressive power, the ratification protocol, and the
|
|
179
|
+
pending-question queue mechanism are owned by the Autonomous Growth Loop
|
|
180
|
+
guard track (see the frozen design's §11 backlog).
|
|
181
|
+
|
|
182
|
+
### Acceptance observation (selective survival)
|
|
183
|
+
|
|
184
|
+
This step is a discipline under observation, not settled infrastructure.
|
|
185
|
+
From adoption onward, during synthesis the orchestrator tags each blocking
|
|
186
|
+
finding — and each finding demoted under INV-U4 — as kind `open-question`
|
|
187
|
+
(unresolved design decision) or kind `defect` (flaw in a made decision),
|
|
188
|
+
recorded in the round's L2 review record. Survival judgment (human's, on
|
|
189
|
+
recorded counts): round-1 `open-question` counts should trend toward zero
|
|
190
|
+
across the first 2–3 qualifying loops; no INV-U4-demoted finding re-tagged
|
|
191
|
+
`defect` within its loop + next revision cycle; interviews staying within
|
|
192
|
+
~5 questions.
|
|
193
|
+
|
|
46
194
|
## Step 0.5 — Design Direction Block (design / docs reviews only)
|
|
47
195
|
|
|
48
196
|
For **design-phase** and **knowledge/documentation-update** reviews, prepend a
|
|
@@ -93,6 +241,10 @@ mistaken for genuine defects.
|
|
|
93
241
|
(e.g., "invariant declaration > mechanism enumeration, per project
|
|
94
242
|
design-by-invariant principle")
|
|
95
243
|
|
|
244
|
+
**Declared Open Unknowns** (carried in from Step 0.25, if any):
|
|
245
|
+
- <unknown>: declared open; findings that merely restate it are (c) advisory
|
|
246
|
+
per INV-U4 (subject to its provenance bound and inverted default)
|
|
247
|
+
|
|
96
248
|
**Where to register additions/objections**:
|
|
97
249
|
- New mechanisms or scope expansions → §11 backlog of the artifact, not body
|
|
98
250
|
- Style/readability concerns not entailed by project principles → (c)
|
|
@@ -250,13 +402,21 @@ The user always has the final say.
|
|
|
250
402
|
## Workflow Pattern
|
|
251
403
|
|
|
252
404
|
```
|
|
405
|
+
[0.25] Unknowns Pass (pre-draft; qualifying reviews only — see Step 0.25)
|
|
406
|
+
|
|
|
407
|
+
├── blindspot enumeration + human interview
|
|
408
|
+
└── L2 context: pass record (unconditional, incl. zero-result)
|
|
409
|
+
|
|
|
253
410
|
[1] Primary LLM creates artifact → outputs artifact + review prompt
|
|
254
411
|
|
|
|
255
412
|
├── artifact: log/{name}_{llm}_{date}.md
|
|
413
|
+
├── re-triage: draft-deferred unknowns → Resolved or Declared
|
|
256
414
|
├── review prompt: log/{name}_review_prompt.md
|
|
257
415
|
└── L2 context: saved via context_save
|
|
258
416
|
|
|
|
259
417
|
[2] Orchestrator sends prompt → N reviewer LLMs execute in parallel
|
|
418
|
+
(for qualifying reviews, conditioned on INV-U1: pass record exists
|
|
419
|
+
and every surfaced unknown is terminal)
|
|
260
420
|
|
|
|
261
421
|
├── Reviewer 1: independent review
|
|
262
422
|
├── Reviewer 2: independent review
|
|
@@ -269,6 +429,10 @@ The user always has the final say.
|
|
|
269
429
|
└── L2 save: consensus + revised artifact
|
|
270
430
|
|
|
|
271
431
|
[4] Classify findings as (a)/(b)/(c) per `multi_llm_reviewer_evaluation`
|
|
432
|
+
Apply INV-U4: findings merely restating a declared Open Unknown → (c)
|
|
433
|
+
advisory (provenance bound + inverted default apply — see Step 0.25)
|
|
434
|
+
Tag each blocking finding and each INV-U4-demoted finding as kind
|
|
435
|
+
`open-question` or `defect` in the round's L2 record
|
|
272
436
|
If no (a)/(b) blocking findings → proceed to next phase
|
|
273
437
|
If any (a)/(b) finding → repeat from [2] with revised artifact
|
|
274
438
|
(c) findings are recorded as advisory; non-blocking
|
|
@@ -375,9 +539,15 @@ When the orchestrator integrates N reviews:
|
|
|
375
539
|
## L2 Save Points
|
|
376
540
|
|
|
377
541
|
Save to L2 context at these moments:
|
|
542
|
+
- **After the Unknowns Pass** (Step 0.25; unconditional, including
|
|
543
|
+
zero-result passes, per INV-U3): enumerated questions, human answers,
|
|
544
|
+
declared Open Unknowns
|
|
378
545
|
- After design/implementation complete (before review)
|
|
379
546
|
- After synthesis of reviews (revised version)
|
|
380
547
|
- After final convergence (implementation-ready / merge-ready)
|
|
548
|
+
- **Per-round finding tagging** (Step 0.25 acceptance observation): each
|
|
549
|
+
blocking finding and each INV-U4-demoted finding tagged `open-question`
|
|
550
|
+
or `defect` in the round's review record
|
|
381
551
|
- **After each review round**: capture per-reviewer observations — verdict,
|
|
382
552
|
(a)/(b)/(c) classification breakdown, briefing-reaction shift (did the
|
|
383
553
|
reviewer change verdict after Step 0.5 design direction?), anomalies
|
|
@@ -818,6 +988,15 @@ Compression ratio: parallel agent raw → Assembly ≈ 2:1
|
|
|
818
988
|
- Self-referential review: v3.2 (4-reviewer update, 2026-04-19) reviewed with new 4-reviewer default (Opus 4.6 + 4.7 + Codex + Composer-2). 4/4 APPROVE WITH CHANGES R1. Findings integrated → v3.3
|
|
819
989
|
- Roster update (v3.5, 2026-06-10): Fable 5 replaces Opus 4.7 as orchestrator/team slot; Opus 4.8 added as second subprocess CLI reviewer alongside Opus 4.6. 4.6 retained for its documented complementary bias (ambiguity-preserving, self-reference-friendly); 4.7 retired as its register is covered by 4.8 and Fable 5. 4.8/Fable 5 bias profiles uncalibrated — record (a)/(b)/(c) breakdowns per round until profiles accumulate in `multi_llm_reviewer_evaluation`
|
|
820
990
|
- Self-referential review of v3.5 (2 rounds, 2026-06-10/11, first run of the 6-reviewer roster): R1 REVISE (1 APPROVE / 4 REJECT — stale pre-v3.5 passages) → fixes → R2 3/6 APPROVE (4.6, 4.8, codex 5.4) with Cursor contributing a code-grounded correction (subprocess strategy keeps the full roster). 4.6/4.8 verdicts split along the predicted lenient/strict axis in R1 and converged to APPROVE in R2
|
|
991
|
+
- Step 0.25 Unknowns Pass added (v3.6, 2026-07-05): designed via its own
|
|
992
|
+
3-round self-referential review (R1 REVISE 2/6 → R2 REVISE 2/6 → R3 4/6
|
|
993
|
+
APPROVE; v0.3.1 FROZEN). The loop demonstrated the step's own thesis:
|
|
994
|
+
R1's two biggest blockers (unattended classification authority, INV-U4
|
|
995
|
+
demotion bounds) were exactly judgment-shaped unknowns a pre-draft
|
|
996
|
+
interview would have caught. Also observed: fix-verification requires
|
|
997
|
+
epistemic diversity — the personas that authored R1 findings all approved
|
|
998
|
+
their own fixes in R2, while subprocess reviewers caught the seams.
|
|
999
|
+
Design record: `docs/drafts/multi_llm_review_unknowns_pass_v0.3.1_FROZEN.md`
|
|
821
1000
|
|
|
822
1001
|
**Key insight**: Design reviews and implementation reviews find
|
|
823
|
-
**categorically different bugs**. Both phases are necessary.
|
|
1002
|
+
**categorically different bugs**. Both phases are necessary.
|
|
@@ -56,6 +56,16 @@ constitutive:
|
|
|
56
56
|
- handoff
|
|
57
57
|
- decision
|
|
58
58
|
- debrief
|
|
59
|
+
# ACT-2 semantic layer (Slice 3): the orchestrator applies this rubric to each
|
|
60
|
+
# proposal's preview before proposing to the human. Revisable — this is the criterion.
|
|
61
|
+
semantic_rubric: >
|
|
62
|
+
Propose a context for attestation when it records a judgment, decision, or outcome
|
|
63
|
+
whose "as recorded at the time" matters for a later audit — e.g. a handoff of state,
|
|
64
|
+
a design decision with rationale, or a debrief of what happened. Do NOT propose pure
|
|
65
|
+
scratch notes, transient status, or content with no decisional weight. When in doubt,
|
|
66
|
+
prefer proposing (the human gate, ACT-1, is the safeguard).
|
|
67
|
+
preview_chars: 300 # bounded excerpt surfaced per proposal
|
|
68
|
+
max_snapshot_bytes: 262144 # 256KB cap on an optional embedded snapshot
|
|
59
69
|
|
|
60
70
|
challenge:
|
|
61
71
|
response_timeout: 3600 # 1 hour
|
|
@@ -23,21 +23,71 @@ module Synoptis
|
|
|
23
23
|
@registry = registry
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# ACT-1: append an approved content-attestation entry to the ledger.
|
|
26
|
+
# ACT-1: append an approved content-attestation entry to the ledger. A first
|
|
27
|
+
# attestation carries no target_ref; a supersession carries the entry_id of the
|
|
28
|
+
# entry it supersedes (LED-2b).
|
|
27
29
|
def append_content_attestation(entry)
|
|
28
30
|
@registry.append(LEDGER, entry.to_h)
|
|
29
31
|
end
|
|
30
32
|
|
|
33
|
+
# ACT-1: append an approved revocation-withdrawal entry to the ledger. It withdraws
|
|
34
|
+
# the referenced target entry (§Kinds); it is itself append-only (LED-2), so the
|
|
35
|
+
# withdrawal is recorded, not an erasure.
|
|
36
|
+
def append_revocation_withdrawal(entry)
|
|
37
|
+
@registry.append(LEDGER, entry.to_h)
|
|
38
|
+
end
|
|
39
|
+
|
|
31
40
|
def entries
|
|
32
41
|
@registry.read(LEDGER)
|
|
33
42
|
end
|
|
34
43
|
|
|
44
|
+
def entries_for(subject_id)
|
|
45
|
+
entries.select { |e| e[:subject_id] == subject_id }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The current "head" of a subject: the fold (LED-2b, §11) over its entries'
|
|
49
|
+
# target references. Walking in append order, a content-attestation (first or
|
|
50
|
+
# supersession) becomes the head; a revocation-withdrawal that targets the current
|
|
51
|
+
# head clears it. Returns the head entry hash, or nil if the subject has never been
|
|
52
|
+
# attested or its latest attestation has been withdrawn.
|
|
53
|
+
#
|
|
54
|
+
# Ordering rule (fixed here per §11): a revocation-withdrawal only clears the head
|
|
55
|
+
# when it targets the current head; a withdrawal of an already-superseded (non-head)
|
|
56
|
+
# entry is recorded but does not change the head.
|
|
57
|
+
def current_head(subject_id)
|
|
58
|
+
head = nil
|
|
59
|
+
entries_for(subject_id).each do |e|
|
|
60
|
+
case e[:kind]
|
|
61
|
+
when 'content_attestation'
|
|
62
|
+
head = e
|
|
63
|
+
when 'revocation_withdrawal'
|
|
64
|
+
head = nil if head && e[:target_ref] == head[:entry_id]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
head
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# A human-readable fold of a subject's trajectory: current status + full history.
|
|
71
|
+
def current_state(subject_id)
|
|
72
|
+
history = entries_for(subject_id)
|
|
73
|
+
head = current_head(subject_id)
|
|
74
|
+
status =
|
|
75
|
+
if history.empty? then 'none'
|
|
76
|
+
elsif head.nil? then 'withdrawn'
|
|
77
|
+
else 'attested'
|
|
78
|
+
end
|
|
79
|
+
{ subject_id: subject_id, status: status, head: head, history: history }
|
|
80
|
+
end
|
|
81
|
+
|
|
35
82
|
# ACT-5: telemetry that the criterion fired and surfaced N proposals. Subject-free
|
|
36
83
|
# and content-free, so it neither indexes L2 (LED-4) nor records a per-context
|
|
37
|
-
# verdict (ACT-4).
|
|
38
|
-
|
|
84
|
+
# verdict (ACT-4). `source` names which trigger point fired (manual /
|
|
85
|
+
# orchestrator_session_end / session_end_hook), so the "at least one defined trigger
|
|
86
|
+
# point" of ACT-5 is distinguishable in the operational log.
|
|
87
|
+
def append_trigger(surfaced_count:, source: 'manual', moment: nil)
|
|
39
88
|
@registry.append(OPLOG, {
|
|
40
89
|
record: 'trigger',
|
|
90
|
+
source: source,
|
|
41
91
|
surfaced_count: surfaced_count,
|
|
42
92
|
moment: moment || Time.now.utc.iso8601
|
|
43
93
|
})
|
|
@@ -15,20 +15,31 @@ module Synoptis
|
|
|
15
15
|
# (ACT-1), not crypto-gated, and an attestation entry does not expire (design §11).
|
|
16
16
|
# This is posture parity with L0/L1 (LED-6), not a regression against Synoptis's
|
|
17
17
|
# signed ProofEnvelope — it is a different kind for a different purpose.
|
|
18
|
+
#
|
|
19
|
+
# A `target_ref` (Slice 2, LED-2b) marks this entry a SUPERSESSION: it commits the
|
|
20
|
+
# entry_id of the prior entry it supersedes. The first content-attestation about a
|
|
21
|
+
# subject carries none; a re-attestation of an already-attested subject is a
|
|
22
|
+
# supersession (§Kinds).
|
|
18
23
|
class ContentAttestationEntry
|
|
19
24
|
KIND = 'content_attestation'
|
|
20
25
|
DEFAULT_DIGEST_ALG = 'sha256'
|
|
21
26
|
|
|
22
|
-
attr_reader :entry_id, :subject_id, :digest, :digest_alg, :moment, :snapshot
|
|
27
|
+
attr_reader :entry_id, :subject_id, :digest, :digest_alg, :moment, :snapshot, :target_ref
|
|
23
28
|
|
|
24
29
|
def initialize(subject_id:, digest:, moment:, digest_alg: DEFAULT_DIGEST_ALG,
|
|
25
|
-
snapshot: nil, entry_id: nil)
|
|
30
|
+
snapshot: nil, entry_id: nil, target_ref: nil)
|
|
26
31
|
@entry_id = entry_id || SecureRandom.uuid
|
|
27
32
|
@subject_id = subject_id
|
|
28
33
|
@digest = digest
|
|
29
34
|
@digest_alg = digest_alg
|
|
30
35
|
@moment = moment
|
|
31
36
|
@snapshot = snapshot
|
|
37
|
+
@target_ref = target_ref
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# A supersession points at the entry it supersedes (LED-2b).
|
|
41
|
+
def supersession?
|
|
42
|
+
!@target_ref.nil?
|
|
32
43
|
end
|
|
33
44
|
|
|
34
45
|
def to_h
|
|
@@ -39,7 +50,8 @@ module Synoptis
|
|
|
39
50
|
digest: @digest,
|
|
40
51
|
digest_alg: @digest_alg,
|
|
41
52
|
moment: @moment,
|
|
42
|
-
snapshot: @snapshot
|
|
53
|
+
snapshot: @snapshot,
|
|
54
|
+
target_ref: @target_ref
|
|
43
55
|
}
|
|
44
56
|
end
|
|
45
57
|
|
|
@@ -62,7 +74,8 @@ module Synoptis
|
|
|
62
74
|
moment: hash[:moment],
|
|
63
75
|
digest_alg: hash[:digest_alg] || DEFAULT_DIGEST_ALG,
|
|
64
76
|
snapshot: hash[:snapshot],
|
|
65
|
-
entry_id: hash[:entry_id]
|
|
77
|
+
entry_id: hash[:entry_id],
|
|
78
|
+
target_ref: hash[:target_ref]
|
|
66
79
|
)
|
|
67
80
|
end
|
|
68
81
|
end
|