V0 Two-Loop Algorithm
Use this for the first experiments. Do not build a general tuple-evolution system yet. The status file is a sparse control surface, and changes happen through two loops:
external loop: choose which tuple part is active
internal loop: propose, verify, and accept/reject one complete candidate state for that part
The tuple for v0 is:
S = (text, claim_ledger, presentation_spec, rubric, gap_policy)
notes may exist in status.yaml, but they are commentary unless the external loop selects them for cleanup.
Inputs
source.md
status.yaml
status.yaml contains:
presentation_specrubricgap_policyclaim_ledgernotes
Status Approval
Before editing, approve the generated status.yaml as the working contract.
For each claim, check:
- Does the
spanactually appear in the source? - Does the normalized
claimmatch the span? - Is the
preservation_testoperational?
Important distinction: status: asserted means "extracted from the current draft." It does not mean "approved invariant" until the status file is approved for the editing run.
Once approved, normal editing must not silently change unselected tuple parts.
External Loop: Select A Tuple Part
At each step, select exactly one active part:
textclaim_ledgerpresentation_specrubricgap_policy
The controller should save this decision as a direction file before invoking the editor.
Direction format:
direction_id: D003
candidate_id: CAND003
active_part: text
target: "opening paragraphs"
intent: "Move the contextual-competence citation into the opening derivation."
rationale: "The concept does work before its theory source is named."
constraints:
- "Do not collapse KB and memory into synonyms."
expected_improvement:
rubric:
primary_goal: improved
risk:
touched_claims: [C1, C2, C3]
failure_modes: []
Selection rule:
- Choose
textwhen the source can be improved under the current contract. - Choose
claim_ledgerwhen a claim is missing, overbroad, badly anchored, duplicated, or no longer matches the source. - Choose
presentation_specwhen repeated edits damage voice, framing, or rhetorical constraints that are not semantic claims. - Choose
rubricwhen the improvement target is wrong or too vague. - Choose
gap_policywhen gap markers are ambiguous or the close-in-text vs extract-candidate distinction is failing.
Only the selected part may change inside the internal loop. All other parts are frozen.
Status-part changes can reveal that the current text is no longer valid under the revised contract. Do not solve that by changing status and text in one candidate. Use a coupled cycle: first accept the status candidate, then immediately run a text-alignment candidate against the new status.
Internal Loop: Propose A Candidate State
The editor proposes one complete candidate state for the selected part. This is deliberately heavier than a diff, because full states are easier for humans to inspect.
Use editor-prompt for this role.
Candidate format:
candidate_id: CAND001
direction_id: D001
active_part: text
candidate_source: editor_loop
base_text: memory-derivation.md
base_status: status.yaml
direction_file: directions/D001.yaml
candidate_file: candidates/CAND001.memory-derivation.md
reason: "what rubric goal this improves"
touched_claims:
- C2
- C5
gap_action:
type: none
marker: null
self_check:
changes_unselected_parts: false
adds_new_requirement: false
changes_claims: false
changes_architecture_framing: false
hides_theory_derivation: false
For non-text candidates, active_part changes and candidate_file points to a complete candidate status.yaml.
Side-Loaded Candidate Intake
A candidate does not have to be produced by the editor prompt. A human or another session may prepare a complete candidate state and submit it directly for verification. Side-loading skips candidate generation only; it does not skip active-part selection, direction, verification, acceptance, or logging.
Use side-loading when a human or separate agent session has already improved the article and wants the result evaluated as a ready-made candidate without running the full editor loop.
Side-loaded candidates must still provide:
- one
candidate_id, - one
direction_id, - exactly one
active_part, - a complete candidate file,
- the current
base_textandbase_status, - a direction file describing the intended improvement and verification focus,
- lightweight metadata marking the candidate as side-loaded.
Side-loaded metadata format:
candidate_id: CAND011
direction_id: D011
active_part: text
candidate_source: side_loaded
producer: human_or_external_session
base_text: memory-system-article.md
base_status: status.yaml
direction_file: directions/D011.yaml
candidate_file: candidates/CAND011.memory-system-article.md
reason: "Improve article coherence under the current approved status."
touched_claims: unknown
gap_action:
type: none
marker: null
self_check:
changes_unselected_parts: unknown
adds_new_requirement: unknown
changes_claims: unknown
changes_architecture_framing: unknown
hides_theory_derivation: unknown
intake_notes:
- "Prepared outside the editor loop."
Use unknown when the intake session cannot honestly self-certify a check. The verifier must treat side-loaded metadata as provenance and triage context, not evidence that the candidate is correct.
The direction file for a side-loaded candidate may be brief, but it must be specific enough for the verifier to decide whether the candidate improved the selected part. A broad rewrite that changes claims and text together is not a valid v0 side-loaded candidate unless the controller explicitly selects a claim-changing status part first or records human approval for the claim change.
Coupled Status-Then-Text Cycle
When active_part is claim_ledger, presentation_spec, rubric, or gap_policy, verification must also answer:
requires_followup_text_candidate: true | false
followup_reason: ""
Use requires_followup_text_candidate: true when accepting the status candidate would make the current source invalid, under-specified, or misleading relative to the revised status file.
Common cases:
- A claim is narrowed, but the text still states the broader version.
- A claim is split, but the text still entangles the two claims.
- A preservation test is sharpened, revealing an ambiguous paragraph.
- A presentation constraint is added, and the text currently violates it.
- A rubric goal is changed, and the current text lacks the required derivation.
gap_policychanges, and the current text has unsupported bridges that now need markers.
The follow-up is not optional cleanup. It is the second half of the same controlled edit cycle:
status candidate -> verify -> accept status -> text-alignment candidate -> verify -> accept/reject text
The status candidate and text candidate still have separate candidate IDs, files, verification reports, and acceptance decisions. If the text-alignment candidate fails, keep the accepted status only if the controller is willing to leave the experiment in a known "status ahead of text" state; otherwise revert or amend the status through another explicit candidate.
Verification
The verifier compares:
old source
old status
candidate source/status
selected active part
For v0, check all claims every time. There are few enough that selective verification is unnecessary.
Use verifier-prompt for this role.
Verification format:
verification:
candidate_id: CAND001
direction_id: D001
active_part: text
direction_file: directions/D001.yaml
candidate_file: candidates/CAND001.memory-derivation.md
frozen_parts_unchanged: true
claim_checks:
C1: pass
C2: pass
C3: pass
C4: pass
C5: warn
presentation_spec:
preserve_items: pass
avoid_items: pass
rubric:
primary_goal: improved
secondary_goals:
contextual_competence_derivation: improved
retrieval_insufficient: worsened
gap_policy: pass
requires_followup_text_candidate: false
followup_reason: ""
decision: needs_human_review
reason: "Candidate improves local derivation but weakens C5 by making retrieval sound like the main interface."
Acceptance Rule
A candidate is accepted only if:
- No unselected tuple part changes.
- No claim preservation test fails.
- No rubric
non_goalis violated. - The candidate improves at least one relevant goal for the active part.
- It does not worsen a higher-priority goal.
- Text candidates do not add new requirements unless the active part is
claim_ledgeror a human approves a claim change. - Any gap marker follows
gap_policy. - Status candidates that require text alignment declare
requires_followup_text_candidate: true.
Examples:
- Clearer but changes a claim: reject.
- Shorter but hides theory derivation: reject.
- More polished but violates
presentation_spec: reject. - Adds a new requirement during a text edit: reject.
- Marks a real unsupported bridge according to
gap_policy: accept or human review.
Update
If accepted:
copy the accepted candidate over the current source.md or status.yaml
append record to edit_log.yaml
append state pointer to history/manifest.yaml
Full-state history is mandatory, but it should not duplicate files unnecessarily. Preserve the initial state as S000 only when the candidates/ directory is empty. After candidates exist, an accepted state may point to the accepted full candidate file plus the unchanged companion file instead of copying both into history/.
State records must make the full state reconstructable:
history/S000.<source-name>.md
history/S000.status.yaml
candidates/CAND001.<source-name>.md
status.yaml
The current root files are head pointers and working copies. The history is the manifest plus the initial snapshot and accepted candidate files.
If rejected:
do not change source.md or status.yaml
keep the rejected candidate for inspection
append failure to failure_log.md or rejection-log.yaml
The failure log is how v1 earns new structure. Do not add ontology in advance.
Stop Condition
Use the local rubric.stop_condition, plus:
- no claim checks warn or fail,
- primary goal is satisfied,
- remaining candidates are mostly tone polish,
- or three consecutive candidates are rejected.
The last rule prevents open-ended polishing loops.
Minimal Loop
1. Generate status.yaml from source.md.
2. Human/controller approves status.yaml.
3. External loop selects one active tuple part.
4. Controller writes a direction file for the selected part.
5. Internal loop proposes one complete candidate state for that part.
6. Verify candidate against source.md, status.yaml, frozen tuple parts, and the direction file.
7. Accept, reject, or request human review.
8. If an accepted status candidate requires text alignment, immediately run a `text` candidate against the new status.
9. Log the decision.
10. Repeat until stop condition.
The key simplification is not that only text can change. The simplification is that only one tuple part changes at a time, and the choice of part is explicit.