Skip to content

Commit 260b11c

Browse files
feat(evidence): gather source paper/refs as cached Markdown to lift the [source-missing] ceiling
Running ntt-fpga to v23 showed the loop working but stuck: reproducibility oscillated 62→74→70 and never hit 80 even though the self-check now PASSES. The cause is an information ceiling, not a bug — the scene holds only the CFNTT paper's URL + a summary, so its signature structures (conflict-free bank map, crossbar permutation, twiddle addressing, radix-4 schedule, FSM) are never in the loop. Two engineers each invent a different valid reconstruction → permanent divergence; amend rightly won't fabricate a [source-missing] value. - evidence: `visually evidence <scene>` fetches metadata.info.sources via web tools, transcribes the technical sections to Markdown, caches under ~/.visually-3d/evidence/<id>/, falling back to a checked-in examples/<id>/ seed. --refs also searches GitHub for reference impls (secondary, [ref-impl]). - runner: add a `tools` option so a run can opt into web access. This is the ONLY tool-enabled step; the core reproduce/judge/amend loop stays tool-less. - amend: buildAmendPrompt injects the gathered evidence and a provenance scheme ([paper] authoritative, [ref-impl] secondary); promotes a [source-missing] note to [paper] when evidence now supplies the value. Evidence enters amend ONLY — reproduce keeps grading the SPEC, not the paper. - examples/ntt-fpga: distil the v1–v23 trial-and-error honestly — what's settled, what's still [source-missing], and which resource/ATP/timing claims python-smt can never verify (out-of-scope, not failures). - paths: evidence/ workspace dir + packaged examples/ resolution; ship examples/. - docs/CLAUDE.md: document the evidence substrate and the evidence→amend-only invariant. Tests: evidence store/loader/prompt + amend evidence injection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent eca2589 commit 260b11c

14 files changed

Lines changed: 641 additions & 18 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ __pycache__/
2828
/lib/create.js
2929
/lib/reproduce.js
3030
/lib/amend.js
31+
/lib/evidence.js
3132
/lib/arith-audit.js
3233
/bin/visually.js
3334
/lib/tui/app.js

CLAUDE.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ When converting/adding a CLI file: write `.ts`, add its generated `.js` to
6565

6666
- `bin/visually.ts` — command dispatcher. Bare `visually` in a TTY launches the
6767
Ink TUI; otherwise/with args it routes to subcommands (`serve`, `create`,
68-
`improve`, `reproduce`, `check`, `upload`). Keeps a `#!/usr/bin/env node`
69-
shebang (preserved through tsc emit).
68+
`improve`, `reproduce`, `amend`, `evidence`, `refine`, `check`, `upload`).
69+
Keeps a `#!/usr/bin/env node` shebang (preserved through tsc emit).
7070
- `lib/tui/app.ts` — Ink + htm control panel (JSX without a build step). htm
7171
template markup is opaque to the type-checker; put real types on component
7272
props, hooks, and effects.
@@ -80,11 +80,19 @@ When converting/adding a CLI file: write `.ts`, add its generated `.js` to
8080
`defaultBackendFor(mode)` picks one (algorithm→python-smt, else→sim).
8181
- `lib/impls.ts` — canonical per-scene impl store under
8282
`~/.visually-3d/impls/<id>/` (`impl.<ext>` + `verify.txt` + `meta.json`).
83+
- `lib/evidence.ts``visually evidence <scene>`: fetch the scene's source
84+
(paper/datasheet URLs in `metadata.info.sources`) via the runner's web tools
85+
and cache a Markdown transcription under `~/.visually-3d/evidence/<id>/`,
86+
falling back to the checked-in `examples/<id>/` seed. **Invariant: evidence
87+
feeds `amend` ONLY, never reproduce's reverse-implementers** — reproduce must
88+
keep grading the SPEC, not the paper. This is the only tool-enabled step
89+
(`runClaudeStreaming({ tools: [...] })`); the rest of the loop is tool-less.
8390
- `lib/serve.ts` — static GUI server + SSE bridge to the local CLI. Endpoints:
8491
`/api/health`, `/api/analyze/stream`, `/samples/...`, `/api/impl/<id>`,
8592
`POST /api/impl/<id>/verify` (streams a live backend run).
8693
- `lib/paths.ts` — package vs workspace paths. Workspace is `$VISUALLY_HOME`
87-
(default `~/.visually-3d`): `scenes/`, `runs/`, `impls/`.
94+
(default `~/.visually-3d`): `scenes/`, `runs/`, `impls/`, `evidence/`. Curated
95+
seed evidence ships checked-in under the package's `examples/<id>/`.
8896

8997
## Web app (`src/`)
9098

bin/visually.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,13 @@ Usage:
2727
visually amend <scene> fold reproduce's findings back into the
2828
[--n 2] [--model <m>] scene's functional spec (parts[].spec /
2929
[--backend <id>] [--no-verify] metadata.spec) so it becomes reproducible
30+
visually evidence <scene> fetch the scene's source (paper/datasheet)
31+
[--refs] [--model <m>] via web tools, cache it as Markdown so
32+
amend can QUOTE it (--refs: also search
33+
GitHub for reference implementations)
3034
visually refine <scene> closed 3D ⇄ implementation loop: each
3135
[--rounds 3] [--visual 90] round runs improve → reproduce → amend
32-
[--repro 80] [--iters 2] until the visual score and reproducibility
36+
[--repro 80] [--iters 1] until the visual score and reproducibility
3337
[--backend <id>] [--no-amend] both clear their thresholds (or max rounds)
3438
visually check <scene> [--png] inspect a scene (browser, or PNG contact sheet)
3539
[--out <file.png>] [--no-open]
@@ -73,6 +77,8 @@ async function main() {
7377
return (await import('../lib/reproduce.js')).reproduce(rest);
7478
case 'amend':
7579
return (await import('../lib/amend.js')).amend(rest);
80+
case 'evidence':
81+
return (await import('../lib/evidence.js')).gatherEvidence(rest);
7682
case 'refine':
7783
return (await import('../lib/refine.js')).refine(rest);
7884
case 'check':

docs/visioned-self-improvement-changelog.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,45 @@ work that is still open. The same reproduce-findings seed that already steered t
261261
visual pass (`seedFromReport`) is what makes the reactive pass *feedback-driven*
262262
rather than a blind re-render.
263263

264+
## 11. Source evidence — fixing the information ceiling, not the loop
265+
266+
Running `ntt-fpga` to v23 showed the loop working correctly yet stuck:
267+
`reproducibility` oscillated 62 → 74 → 70 and never reached 80, while the
268+
self-check finally PASSED (the timeout was gone). The cause was not a loop bug —
269+
it was an **information ceiling**. The scene held only the CFNTT paper's URL and a
270+
summary, so the paper's signature structures (the conflict-free bank/offset map,
271+
the per-stage crossbar permutation, the twiddle-ROM addressing, the radix-4
272+
schedule and FSM) were never in the loop. Two reverse-implementers each invented a
273+
different valid reconstruction → permanent divergence → reproducibility capped;
274+
`amend` could not help because it (correctly) will not fabricate a
275+
`[source-missing]` value.
276+
277+
The fix is to give the loop the missing information instead of asking it to invent
278+
more:
279+
280+
- **`visually evidence <scene>` (`lib/evidence.ts`).** Fetches the scene's
281+
`metadata.info.sources` with web tools, transcribes the technical sections to
282+
Markdown, and caches them under `~/.visually-3d/evidence/<id>/`. `--refs` also
283+
searches GitHub for reference implementations, kept strictly secondary
284+
(`[ref-impl]`). This is the **only tool-enabled step**; `runClaudeStreaming` now
285+
takes a `tools` option, and the rest of the loop stays tool-less and
286+
deterministic.
287+
- **Checked-in seed (`examples/<id>/`).** Curated learnings ship with the package
288+
and are the fallback when no evidence has been fetched. `examples/ntt-fpga/`
289+
distills the v1–v23 trial-and-error honestly: what is settled and matches the
290+
source, what is still `[source-missing]`, and which resource/ATP/timing claims
291+
a functional backend can *never* verify.
292+
- **`amend` quotes the evidence.** `buildAmendPrompt` injects the gathered
293+
evidence and a provenance scheme (`[paper]` authoritative, `[ref-impl]`
294+
secondary, `[src]`/`[conv]`/`[calc]` as before); a value the evidence supplies
295+
for a prior `[source-missing]` note is promoted to `[paper]`.
296+
297+
**Invariant preserved.** Evidence enters `amend` ONLY. reproduce's
298+
reverse-implementers still see the spec alone, so reproducibility keeps measuring
299+
the spec's completeness — evidence enriches the spec, then reproduce grades the
300+
richer spec. What evidence cannot lift (synthesis-only resource/ATP/timing claims)
301+
is documented as out-of-scope for `python-smt` rather than counted as failure.
302+
264303
## References
265304

266305
The hardening borrows directly from prior recursive-self-improvement work:

docs/visioned-self-improvement.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,38 @@ are just one instantiation of the pattern; the backend prompt
156156
(`lib/backends/python-smt.ts`) states it for any subject so a timed-out heroic
157157
check never masquerades as "implementation broken".
158158

159+
### 3.3a Source evidence — lifting the `[source-missing]` ceiling (`lib/evidence.ts`)
160+
161+
A scene carries only a paper URL plus a short summary, so the source's *signature*
162+
structures (an exact memory map, an addressing function, an FSM) are absent.
163+
`reproduce` then finds two engineers each inventing a different valid version, and
164+
`amend` correctly refuses to fabricate, tagging the gap `[source-missing]`. No
165+
number of rounds can climb past this — the information genuinely isn't in the
166+
loop.
167+
168+
`visually evidence <scene>` closes that gap: it fetches the scene's
169+
`metadata.info.sources` with **web tools** (the one tool-enabled step;
170+
`runClaudeStreaming({ tools: ['WebFetch','WebSearch'] })`), transcribes the
171+
technical sections to Markdown, and caches them under
172+
`~/.visually-3d/evidence/<id>/` — falling back to a checked-in `examples/<id>/`
173+
seed. `--refs` also searches GitHub for reference implementations, tagged
174+
secondary (`[ref-impl]`, never authoritative over the paper).
175+
176+
**Invariant: evidence flows into `amend` ONLY.** reproduce's reverse-implementers
177+
never see it, because reproduce measures *"can you rebuild from the SPEC alone"*
178+
handing them the paper would measure paper-completeness instead. The flow is:
179+
180+
```
181+
evidence(paper.md) → amend QUOTES it ([paper]) → spec gains the real values
182+
→ next reproduce grades the richer spec → divergence falls
183+
→ reproducibility & fidelity rise
184+
```
185+
186+
What evidence *cannot* fix: claims that need synthesis (resource/area/ATP/timing,
187+
e.g. "~50% hardware saved", LUT/FF ratios). A functional `python-smt` model
188+
cannot verify those at all; they stay `unverifiable` by construction and must not
189+
be counted as fidelity failures.
190+
159191
### 3.4 Automatic backend selection (`lib/backends/index.ts`)
160192

161193
The verification substrate is chosen from **what the subject is**, so no manual

examples/ntt-fpga/index.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"id": "ntt-fpga",
3+
"sources": [
4+
{
5+
"title": "CFNTT: Scalable Conflict-Free NTT Multiplication Architecture (TCHES 2022)",
6+
"url": "https://tches.iacr.org/index.php/TCHES/article/view/9291"
7+
},
8+
{
9+
"title": "CFNTT PDF (IACR TCHES)",
10+
"url": "https://tches.iacr.org/index.php/TCHES/article/download/9291/8857"
11+
},
12+
{
13+
"title": "DOI 10.46586/tches.v2022.i1.94-126",
14+
"url": "https://doi.org/10.46586/tches.v2022.i1.94-126"
15+
}
16+
],
17+
"items": [
18+
{ "kind": "notes", "title": "Curated learnings (v1–v23)", "file": "notes.md" }
19+
]
20+
}

examples/ntt-fpga/notes.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Curated learnings — ntt-fpga (CFNTT, TCHES 2022)
2+
3+
Distilled from many `refine` rounds (v1–v23). This is honest about what is
4+
settled, what is still missing, and what this backend simply cannot grade —
5+
it is NOT a transcription of the paper. To fill the gaps below with the paper's
6+
real values, run `visually evidence ntt-fpga` (it fetches the source and writes
7+
`paper.md` next to this file), then `visually amend` will quote it.
8+
9+
Source: "CFNTT: A Scalable Conflict-Free NTT Multiplication Architecture"
10+
(IACR TCHES 2022). See `index.json` for URLs.
11+
12+
## Settled — matches the source, keep pinned in the spec
13+
The numeric/algebraic core is reproducible and the two independent reverse
14+
implementations agree on it. These belong in `metadata.spec` / `parts[].spec`
15+
with `[src]`/`[calc]` provenance and should not regress:
16+
- transform size N, modulus q, coefficient bit-width, number of butterfly units;
17+
- Barrett reduction constants (mu, k) and the negacyclic (psi-twisted) flavor;
18+
- psi / omega / N^-1 and the generator choice (derivable, machine-checked by the
19+
arithmetic guard);
20+
- round-trip identity NTT∘INTT = id (proven on small N by the self-check).
21+
22+
## Still missing — the paper's SIGNATURE structures (the real ceiling)
23+
These are the contributions a short summary omits, so two engineers each invent a
24+
different valid version → permanent divergence → reproducibility caps in the
25+
mid-70s. They are `[source-missing]` until the paper text is gathered:
26+
- the **conflict-free bank-index and bank-offset functions** (the Sec. III
27+
headline result) — a reconstructed XOR-fold is NOT the paper's map and fails
28+
per-stage operand-distinctness at large strides;
29+
- the **per-stage crossbar permutation σ_s** routing table;
30+
- the **twiddle-ROM address function** f(stage, block) under the reuse loop;
31+
- the **radix-4 full-transform index/stride schedule** and how the radix-2 lanes
32+
reconfigure into radix-4 BUs per stage;
33+
- the **complete FSM** state list and transition table (the scene has carried two
34+
contradictory FSM lists and an unwired DONE state — fix at the source);
35+
- the exact **Barrett correction** output bound / number of conditional
36+
subtractions, and the internal coefficient ordering (natural vs bit-reversed).
37+
38+
## Out of scope for python-smt — do NOT count these against fidelity
39+
These are RTL/synthesis/area-timing claims a functional Python+Z3 model can never
40+
verify. Record them in the spec as documentary `[src]` facts, but treat their
41+
`property_checks` as `unverifiable` by construction, not as failures:
42+
- "~50% butterfly hardware saved via symmetric operators";
43+
- ATP / resource ratios (LUT / FF / DSP / BRAM vs radix-2);
44+
- pipeline latency in cycles/lane and clock frequency;
45+
- the "33% fewer mults / 20% fewer add-sub" reductions (need a stated naive
46+
baseline; verify only the per-BU operation count, which IS finite).
47+
48+
## Process lessons (apply to any paper-derived scene)
49+
- A timed-out self-check is a HARNESS fault, not a wrong implementation — keep
50+
default verification finite (see `lib/backends/python-smt.ts`).
51+
- `amend` must never fabricate a `[source-missing]` value; gather evidence first.
52+
- Resource/timing claims need a different backend (RTL synthesis) — until then,
53+
reproducibility, not fidelity-on-hardware-claims, is the axis that can move.

lib/amend.ts

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { runClaudeStreaming } from './runner.js';
2121
import { extractScene, parseScene, validateScene, specCoverage } from './scene.js';
2222
import { repairArithmeticClaims } from './arith-audit.js';
2323
import { reproduce } from './reproduce.js';
24+
import { loadEvidence, evidenceExcerpt, type LoadedEvidence } from './evidence.js';
2425

2526
interface AmendOpts {
2627
positional: string[];
@@ -185,9 +186,12 @@ export function hasFindings(report: any): boolean {
185186
}
186187

187188
// Build the prompt that asks the model to MERGE verification findings into the
188-
// scene's spec substrate. Pure (no I/O) so it is unit-testable.
189+
// scene's spec substrate. Pure (no I/O) so it is unit-testable. When `ev` is
190+
// present, the gathered source evidence is injected so the model can QUOTE the
191+
// real paper/datasheet instead of guessing — the only way to pin the specific
192+
// system's structures and raise FIDELITY, not just reproducibility.
189193
// eslint-disable-next-line @typescript-eslint/no-explicit-any
190-
export function buildAmendPrompt(scene: any, report: any): string {
194+
export function buildAmendPrompt(scene: any, report: any, ev?: LoadedEvidence): string {
191195
type MissingField = { kind?: string; item?: string; where?: string };
192196
type VerifyFinding = { impl?: number; counterexample?: string };
193197
type ParamFidelity = { param?: string; reference_value?: string; impl_value?: string; match?: boolean; where?: string };
@@ -232,6 +236,21 @@ export function buildAmendPrompt(scene: any, report: any): string {
232236
null, 1,
233237
).slice(0, 4000);
234238

239+
// Gathered ground-truth evidence (from `visually evidence`), if any. This is
240+
// the authoritative transcription of the SOURCE — quoting it is how a
241+
// [source-missing] gap finally gets filled with the paper's real value.
242+
const excerpt = ev ? evidenceExcerpt(ev) : '';
243+
const evidenceBlock = excerpt
244+
? `\nGATHERED SOURCE EVIDENCE (transcribed from the actual source by \`visually
245+
evidence\`; origin: ${ev?.origin}). This is AUTHORITATIVE — prefer it over the
246+
summary above. QUOTE concrete values from it to fill [source-missing] gaps, and
247+
tag them \`[paper]\` (or \`[ref-impl]\` if from a secondary reference
248+
implementation). Do NOT exceed what it states:
249+
\`\`\`markdown
250+
${excerpt}
251+
\`\`\`\n`
252+
: '';
253+
235254
return `You are closing the loop of a "visioned self-improvement" system. A scene
236255
descriptor doubles as the SPEC for a real system (a circuit, an algorithm, a
237256
machine, a building). Independent engineers tried to rebuild the system from
@@ -256,7 +275,7 @@ the SPECIFIC system's real values; do NOT invent values it does not support):
256275
\`\`\`json
257276
${sourceBlock}
258277
\`\`\`
259-
278+
${evidenceBlock}
260279
MISSING FIELDS the spec must now carry (each says which part's spec to write):
261280
${missing || '(none reported)'}
262281
@@ -301,14 +320,19 @@ RULES for choosing values:
301320
right or leave the operands symbolic. A self-inconsistent counterexample above
302321
(e.g. "spec N != computed N") MUST be fixed at its numeric source, not annotated.
303322
5. PROVENANCE — for each fact you commit, mark where it came from in \`spec.notes\`
304-
using a tag: \`[src]\` quoted/derived from the SOURCE above, \`[conv]\` a
305-
convention you chose to break a tie (not stated by the source), \`[calc]\` a
306-
value you derived by computation. \`[conv]\` facts are reproducibility aids, NOT
307-
claims about the real system — never present a guessed value as the source's.
308-
6. CEILING — if a missing field is genuinely NOT determinable from the source and
309-
is NOT a free convention (it is specific architectural detail the scene simply
310-
lacks), do not fabricate it. Record what is needed in \`spec.notes\` prefixed
311-
\`[source-missing]\` so the gap is visible instead of papered over.
323+
using a tag: \`[paper]\` quoted/derived from the GATHERED SOURCE EVIDENCE,
324+
\`[ref-impl]\` from a secondary reference implementation in that evidence,
325+
\`[src]\` from the source metadata above, \`[conv]\` a convention you chose to
326+
break a tie (not stated by any source), \`[calc]\` a value you derived by
327+
computation. \`[conv]\`/\`[ref-impl]\` facts are reproducibility aids, NOT
328+
authoritative claims about the real system — never present a guessed or
329+
secondary value as the paper's.
330+
6. CEILING — if a missing field is genuinely NOT determinable from any source
331+
(neither the gathered evidence nor the metadata) and is NOT a free convention,
332+
do not fabricate it. Record what is needed in \`spec.notes\` prefixed
333+
\`[source-missing]\` so the gap is visible instead of papered over. If the
334+
GATHERED EVIDENCE above now supplies a value a previous round marked
335+
\`[source-missing]\`, REPLACE that note with the real value tagged \`[paper]\`.
312336
313337
Existing part ids you may target: ${ids}
314338
@@ -362,7 +386,14 @@ export async function amendScene(
362386
writeFileSync(path.join(dir, 'prev.json'), JSON.stringify(scene, null, 2));
363387
writeFileSync(path.join(dir, 'report.json'), JSON.stringify(report, null, 2));
364388

365-
const prompt = buildAmendPrompt(scene, report);
389+
// Load any gathered source evidence so amend can QUOTE the real source. This
390+
// is the ONLY place evidence enters the loop — reproduce's engineers never see
391+
// it, so reproducibility keeps measuring the SPEC, not the paper.
392+
const ev = loadEvidence(id);
393+
if (ev.origin !== 'none') {
394+
console.log(` ↳ using source evidence (${ev.origin}) to ground the spec`);
395+
}
396+
const prompt = buildAmendPrompt(scene, report, ev);
366397
writeFileSync(path.join(dir, 'prompt.txt'), prompt);
367398
const { text } = await runClaudeStreaming({ prompt, model: opts.model, quiet: true });
368399
writeFileSync(path.join(dir, 'raw.txt'), text);

0 commit comments

Comments
 (0)