[EPIC] Real constraint-solving RLM benchmark + durable context mounts #223
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
nsaspy/prolog-rlm#223
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Turn the current live deep experiment into a real end-to-end reasoning benchmark that exercises planning, recursive RLM execution, constraint solving, and trusted verification, then immediately add a first-class durable context-mount abstraction without bloating the core model prompt.
This epic is intentionally ordered:
The architectural rule is:
Slice 1 — Replace token-echo depth test with a real constraint-solving benchmark
Problem
benchmark/rlm_live_deep_experiment.plcurrently injects a fixed typed plan and asks nested model calls to returnLIVE_DEEP_OK.That is useful for provider/recursion plumbing, but it does not prove:
The benchmark must stop treating a magic token as correctness.
Benchmark contract
Create a deterministic, hard, uniquely-solvable finite-domain constraint problem tailored to the runtime.
Target properties:
Prefer a structured logic-grid/scheduling/resource-assignment CSP whose entire instance can be generated/represented declaratively and checked with
clpfdor equivalent trusted Prolog constraints.Required execution lanes
A.
core-minimalThe authoritative architecture test.
The benchmark supplies only the actual task and normal runtime metadata/capabilities. It must not inject an exact plan or a giant benchmark-specific planner prompt.
Core should continue to provide only the minimal typed-plan runtime contract already owned by
rlm_completion.This lane tells us whether core prompting/runtime semantics are sufficient.
B.
harness-guidedA comparison lane owned by benchmark/CLI/downstream code.
It may add task-specific planning guidance, decomposition advice, output-shape reminders, or other scaffolding.
This lane must not change trusted verification.
The delta between A and B is diagnostic:
Do not silently move harness guidance into the core just to make the benchmark green.
Depth / RLM comparison
Run meaningful depth variants where supported, at minimum depth 0/1/2.
Depth must not simply add identical echo calls. The selected/generated plan must actually use recursion/decomposition for the deeper lanes when RLM is selected.
Record at least:
core-minimalvsharness-guided).Trusted verification
Correctness must be decided by Prolog, not by substring matching and not by the LLM saying "verified".
Implement a verifier that:
Prefer reusing the existing Spec/Verify semantic split where practical:
Do not create executable authority from model-generated Prolog.
TDD / deterministic tests
Before relying on real provider runs, add deterministic tests proving:
core-minimaldoes not install benchmark-specific exact-plan instructions;harness-guidedguidance remains downstream-only;Existing provider/plumbing benchmark
Do not lose the current useful provider/recursion smoke coverage. Either:
The main
deep-integrationreasoning benchmark should become the real CSP test.CLI / benchmark runner
Update
benchmark/run.plas needed so users can explicitly run the real benchmark and compare lanes. Keep existing deterministic/integration behavior backwards-compatible where reasonable.Suggested modes may include:
Exact naming can follow existing conventions; avoid redundant aliases.
Slice 2 — Durable context mounts / lifetime + visibility
Begin immediately after the benchmark slice is landed/working.
Current behavior
rlm_contextcurrently supports process-local context records. Caller-ownedcontext_ref/context_handlevalues can survive multiplerlm_completion/4calls because completion does not delete caller-owned handles.However the built-in memory backend is explicitly non-persistent, so handles do not survive a process restart.
Design principle
Do not add only
permanent(true).Separate two independent semantic axes:
Lifetime
Model visibility
At minimum:
Default persistent context visibility must be
opaque.Persistent does not mean inject into every prompt forever.
The planner should receive bounded metadata / mount identity and use normal context operations when content is required.
Proposed host-facing abstraction
Conceptually:
Exact names are open to implementation review, but preserve the separation of lifetime, scope, visibility and source identity.
Persistence model
Persist the mount/source identity and policy, not a magical serialized live handle.
Conceptually:
Adapters remain trusted host boundaries. Model-generated data must not register executable adapter callbacks.
Scope
Support explicit scope so durable context does not become accidental global ambient state.
Initial scope vocabulary can be small, e.g.:
Avoid user-global implicit injection as a default.
Required tests
visibility(prompt)is opt-in and bounded;Prompt ownership / benchmark interpretation
Keep the library core intentionally minimal.
rlm_completionmay describe:It should not grow benchmark-specific chain-of-thought coaching, exact plans, or domain-specific CSP tutorials just to improve benchmark results.
CLI, benchmark harnesses, applications and downstream agents may add richer guidance deliberately and observably.
The real benchmark must report both minimal and guided behavior so prompt changes can be evaluated instead of guessed.
Implementation order
mainand existing live benchmark/test APIs.rlm_contextadapters and existing artifact/storage abstractions.Acceptance gate
The epic is complete only when:
rlm_completion;Start implementation with Slice 1 immediately.
Implementation started on draft PR #225.
Current Slice 1 state:
rlm_verify;core_minimalvsharness_guided, depth ceilings 0/1/2;deep-smoke;CI and the credentialed live OpenRouter depth gate are running now. Slice 2 (durable context mounts) stays next in this epic once this benchmark vertical slice is working.
Slice 2 has now started as stacked draft PR #228 (
feature/223-durable-context-mountson top of #225). It reusesrlm_artifactfor persistent mount descriptors, defaults visibility to opaque, rehydrates fresh context handles after restart, and keeps adapter callbacks process-local/trusted. Static production-module loading is already green on the stacked CI run; focused mount tests are intest/run_context_mount.pl.RAGE live-provider diagnosis for Slice 1 / PR #225, exact candidate
75b04abc96f8520bdf762e43e8e40d0c179edc36(synthetic merge611eedc9bf70fb4b12d2825273719d2104466beainto unchangedmain267697bef10a3fffff7c093e1435ece770e7444b).Deterministic/runtime packaging evidence is healthy: deterministic CI job, Nix, clean SWI pack and Tree-sitter are green. The failing surface is specifically the new credential-backed
deep-integrationCSP benchmark, and it reproduces in both normal REAL OpenRouter (openai/gpt-oss-120b) and pinned Paid OpenRouter lanes.Observed exact failure matrix: all six
core_minimal|harness_guided × depth 0|1|2cases fail before execution/verification withcompletion_error{phase:planner,kind:plan_parse_failed,...cause:plan_error{phase:parse,kind:invalid_plan,detail:no_json_object,...}}. Each case consumed the configured two planner attempts; no candidate plan parsed, no CSP verification was reached, and no plan operation executed. The ordinary live core, structured-repair, tool, recursive-completion and direct integration benchmark tests immediately before this step all passed on the same workflow, so this is not evidence of general OpenRouter/runtime outage.Adversarial interpretation / decision: HOLD Slice 1 as a live behavioral failure, not a deterministic-runtime failure and not a verifier failure. Do not weaken the benchmark, inject a fixed plan, or move task-specific CSP coaching into core to make it green. The next repair should preserve the
core_minimalcontract and first inspect the actual provider response/output-channel behavior that producedno_json_object;harness_guidedmay add downstream-only guidance, but both lanes currently failing identically means we do not yet have evidence that recursion depth or the trusted verifier is the limiting factor.This also means stacked Slice 2 PR #228 must not be treated as epic-ready merely because its own focused/static checks advance: #223 explicitly orders the real benchmark first. No branch mutation performed here because #225/#228 are already active owned transactions.
RAGE follow-up on Slice 1 / PR #225 after inspecting the exact REAL OpenRouter job and current planner/provider code.
The earlier output-channel hypothesis is now partially disproven.
rlm_completion:real_response_plan_input/3already tries nonemptytextfirst, then nonemptyreasoning, then structured response fallback. On the same live workflow, ordinary plan generation succeeds fromtext, while the structured-repair suite succeeds fromreasoning. So this is not simply “planner ignores reasoning output.”The remaining live evidence points at planner termination/budget observability as the next falsifiable boundary:
planner_attempts(2)andplanner_max_tokens(1800);completion_tokens:3600across the two failed attempts; the sixth reports 4013;no_json_objectbefore any plan operation executes;finish_reason, butplanner_parse_result/…drops provider-summary/finish information from the final parse error, so the benchmark cannot currently distinguishstopfromlength/truncation or otherwise prove why the JSON object never appeared.Decision remains HOLD, but the next experiment should not change CSP semantics or inject a plan. Before changing prompting/runtime policy, capture the planner attempt's output channel +
finish_reason(and only bounded/safe diagnostics, never raw private reasoning) in trusted trace/error evidence. Then test the specific hypothesis that the planner response is exhausting generation/reasoning budget before emitting the typed JSON plan. If that hypothesis is confirmed, evaluate the existing host-ownedplanner_reasoning_effort/1/ planner token-budget policy through normal provider capability semantics rather than adding CSP-specific core coaching.Security/non-goals unchanged: do not log raw reasoning, do not weaken
plan_parse, do not move harness guidance into core, and do not treat Slice 2 #228 as epic-ready while Slice 1 is still red.RAGE exact-head recheck for recovery PR #264 at
5b7891f575a5d7b181d597344478fc7058f96e90on canonicalmain2e1264d80d02fecfb9f946e1328caaf1053e7a3b.Current evidence is still a genuine live behavioral HOLD, not an infra/deterministic failure: deterministic CI is green, and the same credential-backed jobs pass the ordinary OpenRouter core/repair/integration suites before failing only at
Run REAL depth 0/1/2 recursion experiment. The recovery benchmark still runs the sixcore_minimal|harness_guided × depth 0|1|2cases withplanner_attempts(2)andplanner_max_tokens(1800), no fixed plan, and trusted verification; the current live failures still occur before verification.Adversarial check of the recovery sequence: the recent commits after the recovery primarily expose/validate the six-lane report (
suite, lane presence, requested/model-selected depth assertions). That is good anti-false-green instrumentation, but it does not itself repair the planner failure. Do not interpret stricter report assertions as progress on the underlying live behavior.Decision remains HOLD for Slice 1. The next useful generic-runtime experiment is still bounded planner-attempt diagnostics at the provider boundary: preserve output channel + normalized
finish_reason(and safe token/attempt metadata) in trusted error/trace evidence, without logging raw reasoning. That makes the currentno_json_objectfailure falsifiable as truncation/budget termination vs normal-stop malformed output before changing prompt semantics or token/reasoning policy. Do not weaken the CSP, parser, verifier, depth contract, or inject a plan. Stacked #228 remains non-promotable until Slice 1 has real live evidence.RAGE Slice 2 recovery design — PR #228 after Slice 1 landed
Slice 1 is now on canonical
main; Slice 2 can be evaluated on its own merits, but PR #228 is not safe to recover/merge as-is. Re-reading exact #228 headf87d98afe3849e50ed047a6e5bbbf8071c2cacdaconfirms the previously recorded adversarial findings are real code-level defects, not theoretical concerns.Deterministic defects to pin TDD-first
Cross-store cache leakage.
persistent_mount_cache/3is keyed only by mount key + artifact version. The artifact store identity is absent. Two simultaneously open stores can therefore each have(scope,name,version=1)with different source descriptors and the second store may reuse the first store's live context handle. The artifact API already gives a closed host-owned store identity term (artifact_store(memory, Id)orartifact_store(persist, File)), so the mount cache can be partitioned by that identity without exposing content or credentials.Resolve race can invalidate a returned winner.
ensure_persistent_context/4checks the cache outside the mutex, creates a fresh context, thenreplace_persistent_cache/3retracts/deletes all old entries under the mutex. Two concurrent misses for the same store/key/version can each create a handle; the later installer can delete the earlier caller's already-returned handle.Concurrent identical mount is not actually idempotent.
persistent_publish_or_reuse/4performsartifact_latestand conditionalartifact_putas separate mount-level operations. Two callers can observe the same pre-state and both append identical immutable versions.Smallest recovery design
TDD first on the recovered Slice 2 transaction, using deterministic synchronization/barriers rather than sleeps:
Realization boundary:
latest -> reusable? -> conditional puttransaction for a given store/mount identity so idempotency is true under concurrency. Do not mutaterlm_artifactsemantics merely to make #228 pass;Adversarial decision
GO for TDD-first recovery; HOLD realization/promotion until those three regressions fail before implementation and pass on a fresh changed head. Do not solve this by global reset, sleeps, accepting duplicate versions, or broadening prompt visibility.
PR #228 is still a draft stacked on obsolete
feature/223-real-constraint-benchmark, exact headf87d98af...; recover the existing transaction onto currentmainrather than opening a replacement, preserving only the owned Slice 2 diff plus the new regressions.RAGE Slice 2 recovery — corrected bug boundary
Current canonical
mainremains7f0050f524db6de5293163241176ac4660f04349. Recovery PR #270 is draft on top of that immutable base.Deterministic evidence
5b252c735e6ae0bcc7c20e5ad57cccdb879102e8: canonical PlUnit discovered 991 tests, passed 990, with exactly one failure:persistent_mount_cache_is_partitioned_by_artifact_store.ab942104d71bc857c2f36eb4e6da4ee4dbcb59a3: first realization partitionspersistent_mount_cacheby artifact store, but the exact same 991/990/1 failure remains. Therefore store-qualified cache identity is necessary design work but not sufficient, and that realization is not accepted as the root fix.f87d98afe3849e50ed047a6e5bbbf8071c2cacdafrom stale #228 already failed three basic persistent-mount tests (defaults_opaque,prompt_visible,idempotent_versions_changes_and_tombstones) before the concurrency hardening was added.Root cause found by source reconciliation
rlm_artifact:canonical_value/2intentionally canonicalizes persisted dicts by rebuilding them with tagartifact_data. A normalized mount source such ascontext_source{kind:text,value:...}is therefore stored/retrieved asartifact_data{kind:text,value:...}.rlm_context_mount:register_descriptor/3andprompt_source_text/2currently exact-match the transientcontext_source{...}tag. Persistent mount realization callsregister_descriptor(Artifact.value.source, ...), so a valid canonical persisted descriptor has no matching clause and the private goal fails.mount_outcome/3catches exceptions but not ordinary goal failure, so the public mount call can fail as a predicate instead of returning a structurederror(...).Adversarial decision
RESTART BUG/TDD/ANALYZE before concurrency realization. Do not change artifact canonicalization, weaken persistence, or paper over the failing mount path. First pin a smallest single-store public regression proving a canonical persisted source rehydrates into a valid live context. Then make mount-source decoding tag-agnostic but shape/kind validated at the trusted host boundary. Only after that base contract is green should the store-qualified cache fix be re-evaluated and the two deterministic concurrency races be realized.
Authority/visibility invariants remain unchanged: persisted data stays closed; adapters stay host-registered/trusted; persistent defaults to opaque; no model-controlled executable callback is introduced.
RAGE Slice 2 — restarted TDD boundary on canonical persisted source rehydration
Current canonical
mainis still7f0050f524db6de5293163241176ac4660f04349. Recovery PR #270 was re-read immediately before the write and is still the active draft transaction.The previous store-qualified cache realization is preserved as necessary but unaccepted: exact head
f9e9d959f46666d070abf77521f931b3ea1b4211ran the canonical deterministic gate with 991 discovered / 990 passed / exactly one failure,persistent_mount_cache_is_partitioned_by_artifact_store; REAL OpenRouter, Paid OpenRouter, Nix, clean pack and Tree-sitter were green on that same candidate. Source reconciliation shows the remaining base failure occurs earlier than cache reuse:rlm_artifactcanonicalizes persisted dict tags toartifact_data, while mount rehydration still exact-matches transientcontext_source{...}tags.TDD restart commit:
d5a9f0a521612eb50da14bf8d7932a0e54bcd66e(test: pin canonical persisted mount rehydration). It adds the smallest single-store public contract: a persistent text mount must successfully rehydrate its canonical persisted descriptor into a live handle whose slice returns the exact bytes. No production code changed in this commit; the existing cross-store test remains intact.Decision: HOLD realization until canonical CI observes this falsifiable contract on
d5a9f0a.... Once red is confirmed, the approved smallest repair is tag-agnostic but exact-shape/kind-validated mount-source decoding at the trusted host boundary; do not change artifact canonicalization, visibility, authority, or serialize executable adapter callbacks. Only after the base rehydration contract is green should the two deterministic concurrency races be TDD-pinned/realized.TDD red confirmed on immutable
d5a9f0a521612eb50da14bf8d7932a0e54bcd66eCanonical CI discovered 992 tests: 990 passed, exactly 2 failed, zero timeout/blocked/fixme. The only failures are:
persistent_mount_rehydrates_canonical_persisted_text_source— the new smallest single-store public contract;persistent_mount_cache_is_partitioned_by_artifact_store— the existing two-store contract.This is the required falsification before production repair. It confirms the cross-store failure is currently dominated by the earlier persisted-descriptor rehydration bug; cache partitioning alone cannot make the public mount path work.
Decision gate: GO for the already-approved minimal production repair: decode persisted mount-source dicts independent of transient dict tag while requiring the exact allowed source shape/kind at the trusted host boundary. Keep artifact canonicalization, opaque visibility, adapter trust, and authority semantics unchanged. After that changed head proves both base tests green, resume the separately approved concurrency TDD (first-live-winner resolve + serialized identical publish/reuse).
RAGE Slice 2 durable-context recovery update.
Exact red TDD head:
d5a9f0a521612eb50da14bf8d7932a0e54bcd66eon canonical base7f0050f524db6de5293163241176ac4660f04349.Deterministic evidence is cleanly falsifiable: 992 tests discovered/completed, 990 passed, exactly 2 failed, zero timeout/blocked/fixme. The only failures are
persistent_mount_rehydrates_canonical_persisted_text_sourceandpersistent_mount_cache_is_partitioned_by_artifact_store. On that same head REAL OpenRouter, Paid OpenRouter, Nix, clean SWI pack, and Tree-sitter all pass.Root cause confirmed:
rlm_artifactcanonicalizes nested dict tags during persistence, so a transientcontext_source{...}returns with a canonical artifact-data tag. The mount layer was incorrectly treating that transient dict tag as persistent wire semantics and passedArtifact.value.sourcedirectly to exact-tagregister_descriptor/3.Design/adversarial decision: GO for a mount-local decoder only. Persisted source decoding is tag-agnostic but exact-key-shape/kind validated; it reconstructs the canonical transient
context_sourcedescriptor before context registration or prompt projection. Unknown/extra shapes fail closed withinvalid_persisted_source_shape. No artifact canonicalization change, authority/capability widening, prompt visibility change, serialized callback, or second persistence plane.Realization:
8e144cef0ce45e125b1c4c8177fe9ee729d81930(fix: decode canonical persisted mount sources). Adversarial diff audit against the TDD head is one production file, +38/-6, limited to persisted-source decode + its two consumers; the adapter prompt pattern is also corrected to retain its requiredsource_reffield.Decision: HOLD PR #270 at this changed exact head until fresh repository-native verification proves the two red contracts green. Do not reuse
d5a9f0a...evidence. The two separately identified concurrency races remain next and must still be pinned with deterministic synchronization before their production repairs.RAGE Slice 2 recovery update on PR #270.
BUG/TDD evidence is now exact on immutable pre-fix head
73007eb9b4ba8f5d1b688e654a6aaf28cb13ac6e: canonical deterministic CI discovered 993 tests, passed 990, failed exactly 3, with zero timeout/blocked/fixme. The first failure is the new contractpublic_projection_binds_artifact_value_before_field_access, raising an instantiation error. The two persisted-mount contracts (persistent_mount_rehydrates_canonical_persisted_text_sourceand cross-store cache partitioning) then fail downstream through that same projection path, so they are not valid evidence against the persisted-source decoder/cache-key repair yet.Analysis/design:
public_from_artifact/2placedValue.name/etc. in the output dict in the predicate head, whileValue = Artifact.valueoccurred only in the body. SWI therefore evaluates dict field access beforeValueis bound. The smallest repair is ordering-only: bindValue = Artifact.valuefirst, then construct the same publiccontext_mount{...}dict. No persistence representation, visibility, scope, authority, source content, or artifact semantics change.Adversarial decision: GO for that exact one-predicate ordering repair; do not broaden the change or treat the two dominated mount failures as separate implementation defects yet.
Realization: commit
a4c8be71cee38bbd6a48ef38933775af3d227461(fix: bind artifact value before mount projection). Adversarial diff review shows exactly one file and one predicate changed, preserving every projected field.Verification gate: HOLD until this changed exact head earns fresh deterministic evidence. If the three failures clear, resume the already-approved bug-first concurrency sequence: deterministic concurrent same-key/version resolve winner/liveness, then concurrent identical mount idempotence; no sleep-based races and no artifact-layer semantic rewrite.