Two-session arc where engine derives connective+object from corpus decomposition; operator ratifies rather than authors. Distinguishes from learning-loop (operator-authored) and directly exercises W-018 checkpoint contemplation and W-017 auto-proposal provenance path.
9.6 KiB
Brief: W-019 — core demo learning-arc
Status: Ready to dispatch. Requires W-007, W-018, W-017 merged to main first.
ADR: ADR-0151 (create alongside implementation)
Dispatch to: Gemini or Codex
Test suite to run: uv run pytest tests/test_learning_arc_demo.py tests/test_learning_loop_demo.py tests/test_chat_runtime.py -q
Headline claim
CORE, encountering a gap it cannot ground, enriches the discovery candidate autonomously through contemplation, then proposes its own teaching chain without a human crafting the connective or object. An operator ratifies with a single acceptance call. The same prompt now produces a deterministic teaching-grounded surface — and the engine authored the proposal.
This is categorically different from core demo learning-loop (ADR-0055..0057),
where the human operator authors the proposal structure (connective, object,
evidence pointer). Here the operator only reviews and ratifies.
Prerequisites (confirm before starting)
RuntimeConfig.auto_contemplate: bool = Falseexists incore/config.pyRuntimeConfig.auto_proposal_enabled: bool = Falseexists incore/config.py(W-017)checkpoint_engine_state()inchat/runtime.pyrunscontemplate()whenauto_contemplate=True(W-018)_load_engine_state()inchat/runtime.pygenerates proposals from enriched candidates whenauto_proposal_enabled=True(W-017)ProposalSource(kind="contemplation", ...)is a valid source (already sealed inteaching/source.py)accept_proposal(proposal_id, log, review_date)exists inteaching/proposals.py
If any prerequisite is missing, stop and report which W is incomplete.
Scene structure (5 scenes)
S1 — Cold Session 1
import tempfile
from pathlib import Path
from chat.runtime import ChatRuntime
from core.config import RuntimeConfig
tmpdir = Path(tempfile.mkdtemp())
cfg = RuntimeConfig(auto_contemplate=True, auto_proposal_enabled=False)
rt = ChatRuntime(config=cfg, engine_state_dir=tmpdir)
response = rt.chat(_DEMO_PROMPT)
rt.checkpoint_engine_state()
Assert:
response.grounding_sourceis NOT"teaching"(cold — ungrounded or OOV)(tmpdir / "discovery_candidates.jsonl").exists()is True- The JSONL file contains at least one line
S2 — Contemplation enrichment visible in persisted state
Read (tmpdir / "discovery_candidates.jsonl"). Parse the first line as a DiscoveryCandidate.
Assert:
candidate.polarityis not None and not"undetermined"(contemplation ran and resolved)candidate.domainsis not emptycandidate.evidenceis not emptycandidate.sub_questionsis not empty
This is Jaw 1: the engine deepened its understanding of the gap without human input.
Choosing the cold subject: Before finalising
_DEMO_PROMPT, runcontemplate(candidate)interactively on candidate subjects to find one that produces at least oneEvidencePointerwithsource == "corpus". The W-017 gate requiresany(e.source == "corpus" for e in evidence)."narrative"is a strong candidate —cause_creation_reveals_meaningand cognition-saturation chains are related enough that sub-question traversal finds corpus hits. Verify empirically and document the chosen subject with a comment in the demo file.
S3 — Auto-proposal surfaces on load
cfg2 = RuntimeConfig(auto_contemplate=True, auto_proposal_enabled=True)
rt2 = ChatRuntime(config=cfg2, engine_state_dir=tmpdir)
# Loading triggers _load_engine_state() → W-017 proposal gate runs
Retrieve proposals via ProposalLog (same log path W-017 writes to).
Assert:
- At least one proposal in
log.pending() proposal.source.kind == "contemplation"proposal.subjectmatches the cold subject from S1proposal.state == "pending"proposal.connectiveandproposal.objectare non-empty strings (engine filled these, not the operator)
This is Jaw 2: the engine generated a complete, reviewable proposal from its own contemplation.
If no proposal is found (corpus evidence condition not met), do not fail silently. Report:
S3 PARTIAL: enriched candidate present but auto-proposal gate did not fire.
Reason: no corpus-evidenced EvidencePointer in candidate.evidence.
Choose a different _DEMO_SUBJECT with corpus-evidenced contemplation output.
Then halt — fix the subject choice before proceeding to S4/S5.
S4 — Operator ratifies against transient corpus
from teaching.proposals import accept_proposal, ProposalLog
from teaching import replay as _replay
# Accept against transient corpus (same swap pattern as learning-loop demo)
transient_corpus = tmpdir / "transient_corpus.jsonl"
with _replay._swap_corpus_path(transient_corpus):
chain_id = accept_proposal(
proposal.proposal_id,
log=log,
review_date="2026-05-25",
)
Assert:
chain_idis a non-empty stringtransient_corpus.exists()is True- Active corpus on disk is byte-identical to before S4 (demo does not mutate production corpus)
S5 — Session 2 grounded response
from chat import teaching_grounding as _tg
original_path = _tg._CORPUS_PATH
_tg._CORPUS_PATH = transient_corpus
try:
cfg3 = RuntimeConfig(auto_contemplate=False, auto_proposal_enabled=False)
rt3 = ChatRuntime(config=cfg3, engine_state_dir=tmpdir)
response2 = rt3.chat(_DEMO_PROMPT)
finally:
_tg._CORPUS_PATH = original_path
Assert:
response2.grounding_source == "teaching"response2.surface != response.surface(measurably different from S1)- Subject word from the ratified chain appears in
response2.surface.lower()
Demo file location
evals/learning_arc/
__init__.py (empty)
run_demo.py (implements run_demo(emit_json=True) -> dict)
run_demo() returns a dict matching this shape:
{
"learning_arc_closed": bool, # True iff all 5 scenes pass
"active_corpus_byte_identical": bool, # S4 safety check
"prompt": str,
"cold_subject": str,
"before": {"grounding_source": str, "surface": str},
"after": {"grounding_source": str, "surface": str},
"scenes": [
{"scene": "S1_cold_session", "passed": bool, "detail": dict},
{"scene": "S2_contemplation_enrichment", "passed": bool, "detail": dict},
{"scene": "S3_auto_proposal", "passed": bool, "detail": dict},
{"scene": "S4_operator_ratifies", "passed": bool, "detail": dict},
{"scene": "S5_grounded_session", "passed": bool, "detail": dict},
],
}
CLI registration
In core/cli.py:
- Add
core demo learning-arctoEPILOGexamples string (afterlearning-loop) - In
cmd_demo(), add handling fortarget == "learning-arc":if target == "learning-arc": from evals.learning_arc.run_demo import run_demo as run_arc_demo report = run_arc_demo(emit_json=emit_json) return 0 if report.get("learning_arc_closed") else 1 - In
core demo all: addlearning-arcas scene 9 (afterlearning-loop) - In the tabular summary string, add entry:
"learning-arc: ADR-0151 — two-session contemplation → autonomous proposal → grounded" - Add
"learning-arc"to thecore demo list-resultsentries
Tests
File: tests/test_learning_arc_demo.py
Use a module-scoped fixture for run_demo() (same pattern as test_learning_loop_demo.py — one execution shared across all tests in the file).
@pytest.fixture(scope="module")
def demo_report() -> dict:
return run_demo(emit_json=True)
8 tests:
test_learning_arc_closes—demo_report["learning_arc_closed"] is Truetest_active_corpus_untouched—demo_report["active_corpus_byte_identical"] is Truetest_before_is_ungrounded—before["grounding_source"] != "teaching"test_after_is_teaching_grounded—after["grounding_source"] == "teaching"test_s2_enrichment_has_polarity_domains_evidence— S2 detail has non-empty polarity, domains, evidence, sub_questionstest_s3_proposal_source_is_contemplation— S3 detail hassource_kind == "contemplation"and non-empty connective + objecttest_s4_corpus_byte_identical_after_accept— S4 detail confirms production corpus unchangedtest_before_and_after_surfaces_differ—before["surface"] != after["surface"]
ADR-0151 (create alongside)
Minimal ADR covering:
- What
core demo learning-arcdemonstrates and why it differs fromlearning-loop - The two "jaws": checkpoint contemplation enrichment (W-018) + autonomous proposal generation (W-017)
- Trust boundary: demo writes only to
tmpdirandtransient_corpus; active corpus is read-only - Which flags enable it:
auto_contemplate=True,auto_proposal_enabled=True - Determinism contract: same engine state + same corpus = same scenes, same surfaces
What NOT to do
- Do not mutate the active teaching corpus on disk — use the transient swap pattern from
learning-loop - Do not add any stochastic sampling, LLM calls, or approximate recall
- Do not weaken
versor_condition(F) < 1e-6 - Do not write to
vault/store.py,generate/stream.py,field/propagate.py - Do not auto-accept proposals — S4 must call
accept_proposal()explicitly (simulates operator ratification) - Do not skip the corpus-evidence check in S3 — if it doesn't fire, report and stop rather than faking success
Verification
After implementation, run:
uv run python -m core.cli demo learning-arc
uv run pytest tests/test_learning_arc_demo.py tests/test_learning_loop_demo.py -q
uv run python -m core.cli test --suite smoke -q
Expected: all tests pass, learning_arc_closed: true in JSON output.