fix: isolate recoverable native preflight faults per call #375
Closed
nsaspy
wants to merge 1 commit from
fix/313-per-call-preflight into main
pull from: fix/313-per-call-preflight
merge into: nsaspy:main
nsaspy:main
nsaspy:feat/prolog-rlm-language
nsaspy:codex/claude-api-provider
nsaspy:codex/symbolic-experts
nsaspy:codex/openai-api-provider
nsaspy:feat/zai-provider-protocols
nsaspy:hardening/project-semantic-20260911
nsaspy:hardening/ws7-nix-ci-pinning
nsaspy:chore/track-prolog
nsaspy:rage/98-semantic-project-knowledge
nsaspy:fix/339-agent-zero-skill-graph
nsaspy:ci/tree-sitter-runner-labels
nsaspy:issue-293
nsaspy:research/text-streaming
nsaspy:docs/agent-editor-handoff
nsaspy:icon-add
nsaspy:hydra/fleet-jobs-20260904
nsaspy:rage/355-d6-11-plan-native-dispatch
nsaspy:cd/nix-installer-action-url
nsaspy:issue-355
nsaspy:rage/97-query-capture-apis
nsaspy:research/expert-direct-tool-projection
nsaspy:rage/96-versioned-syntax-facts
nsaspy:fix/direct-deadline-typing
nsaspy:dogfood/auto-dig-native-tools
nsaspy:feature/rage-feature-freeze
nsaspy:fix/323-native-batch-cardinality
nsaspy:fix/328-direct-user-namespace-text-string
nsaspy:fix/transient-provider-retry-2026-09-01
nsaspy:fix/openrouter-dotted-tool-result-name
nsaspy:rage/325-native-call-isolation
nsaspy:fix/316-original-batch-effect-isolation
nsaspy:fix/312-peek-schema-contract
nsaspy:docs/agents-worktree-rule
nsaspy:fix/child-native-capability-narrowing
nsaspy:test/paid-lane-glm-53-flash
nsaspy:docs/spec-seeded-symbolic-plans
nsaspy:rage/290-spec-plan-authority
nsaspy:fix/304-context-peek-selector-contract
nsaspy:fix/298-native-any-json-schema
nsaspy:rage/288-spec-plan-graph-executor
nsaspy:feat/spec-plan-flow-api
nsaspy:prolog-rlm-v1
nsaspy:gpt-5-6-sol-high/questions-for-rlm-prolog-and-lambda-rlm
nsaspy:rage/277-planner-protocol-context
nsaspy:research-approval/rlm-research-026-task-deadlines-20260827135838
nsaspy:research-approval/rlm-research-025-lem-ui-20260827135751
nsaspy:rage/183-live-operator-behavior
nsaspy:agent/127-agentprolog-config
nsaspy:recovery/132-agentprolog-monorepo
nsaspy:rage/223-durable-context-mount-recovery
nsaspy:rage/223-constraint-benchmark-recovery
nsaspy:research-approval/rlm-research-011-managed-context-tool-discovery-20260827054019
nsaspy:feat/research-approval-schema
nsaspy:agent/rrlm-control-plane-research
nsaspy:docs/219-adrrd-review
nsaspy:salvage/231-runtime-status
nsaspy:salvage/231-runtime-status-run
nsaspy:tmp
nsaspy:tmp2
nsaspy:tmp3
nsaspy:rage/245-planner-structural-retry
nsaspy:rage/168-skill-selection-eval
nsaspy:rage/250-skill-catalog-graph
nsaspy:rage/56-result-acceptance
nsaspy:rage/172-parent-resume-replan
nsaspy:rage/175-deadline-policy-recovery
nsaspy:rage/257-provider-tool-choice-normalization
nsaspy:agent/tool-result-projection-presets
nsaspy:fix/234-numeric-schema-bounds
nsaspy:feature/231-rlm-cli-reference-harness
nsaspy:feature/223-durable-context-mounts
nsaspy:feature/223-real-constraint-benchmark
nsaspy:feature/223-real-constraint-benchmark-clean
nsaspy:feature/223-real-constraint-benchmark-final
nsaspy:feature/223-real-constraint-benchmark-impl
nsaspy:feature/223-real-constraint-benchmark-now
nsaspy:feature/223-real-constraint-benchmark-tdd
nsaspy:feature/223-real-constraint-benchmark-work
nsaspy:rage/176-root-planner-tool-projection
nsaspy:rage/172-typed-delegation-policy
nsaspy:rage/175-subagent-deadline-policy
nsaspy:rage/206-prompt-command-runtime
nsaspy:rage/203-subagent-skill-role-provenance
nsaspy:rage/200-permanent-rlm-context
nsaspy:fix/190-cli-help-success
nsaspy:archive/pr-132-agentprolog-config-20260827
nsaspy:feature/117-prolog-skill-activation-linear
nsaspy:feature/117-prolog-skill-activation
nsaspy:fix/194-completion-budget-usage
nsaspy:fix/191-capability-filtered-tool-schemas
nsaspy:fix/185-reasoning-effort
nsaspy:ci/report-workflow-failures-20260825
nsaspy:cleanup/186-remove-legacy-harnesses
nsaspy:fix/185-reasoning-routing
nsaspy:agent/evolution-async-evaluator
nsaspy:rage/181-binding-replay-race
nsaspy:agent/124-deepseek-harness-prolog
nsaspy:rage/144-fallback-closure
nsaspy:rage/164-conversation-metadata
nsaspy:agent/subagent-supervised-call-conformance
nsaspy:fix/160-context-adapter-closed-data
nsaspy:codex/move-agent-zero-adaptor
nsaspy:codex/sol-high-integration
nsaspy:fix/151-plunit-gate
nsaspy:fix/165-evolution-closed-data
nsaspy:fix/162-async-control-exceptions
nsaspy:fix/158-prompt-compiler-closed-dicts
nsaspy:fix/151-plunit-main-ownership
nsaspy:fix/156-registry-destroy-hooks
nsaspy:fix/154-anonymous-dict-canonicalization
nsaspy:fix/flake-lock-reproducibility
nsaspy:agent/issue-142-evolution-kernel
nsaspy:agent/141-flake-runtime-package
nsaspy:agent/rlm-subagent-runtime
nsaspy:agent/144-subagent-fallback-a
nsaspy:agent/107-bound-adapter-metadata
nsaspy:validation/clean-pack-install
nsaspy:fix/45-authoritative-nested-model-events
nsaspy:fix/46-router-safe-live-streaming
nsaspy:agent/prompt-context-compiler
nsaspy:agent/42-canonical-recursive-fingerprints
nsaspy:agent/136-static-load-errors-fail-ci
nsaspy:agent/44-completion-error-usage
nsaspy:fix/67-loader-registry-cleanup
nsaspy:agent/opentui-solid-reference-client-current
nsaspy:agent/95-project-source-registry
nsaspy:feature/issue-117-prolog-skill-compiler
nsaspy:backlog/roadmap-86-merged
nsaspy:agent/opentui-solid-reference-client
nsaspy:79-tool-effect-boundary
nsaspy:agent/prolog-agent-ui-research
nsaspy:agent/conversation-cold-context
nsaspy:agent/94-tree-sitter-ffi
nsaspy:agent/conversation-warm-context
nsaspy:111-opentui-solid-reference-client
nsaspy:109-prolog-agent-ui-v1
nsaspy:agent/conversation-runtime
nsaspy:agent/spec-mode-language
nsaspy:agent/spec-verify-foundation
nsaspy:agent/prompt-compiler-research
nsaspy:reconcile-backlog-postmerge
nsaspy:reconcile-backlog-issues
nsaspy:agent/prolog-agent-roadmap
nsaspy:feature/issue-84-effect-store-migration
nsaspy:fix/issue-80-effect-substrate-adversarial-hardening
nsaspy:feature/issue-57-effect-identity
nsaspy:agent/mcp-declaration-security
nsaspy:agent/external-tool-category-boundary
nsaspy:feature/issue-53-authority-pending-async
nsaspy:feature/issue-54-agent-graph-canonical-async
nsaspy:feature/issue-54-tools-mcp-canonical-async
nsaspy:feature/issue-54-async-canonical-runtime
nsaspy:agent/dual-sync-async-runtime
nsaspy:agent/reconcile-todo-status
nsaspy:feature/issue-20-deep-recursion-experiments
nsaspy:feature/issue-19-cli-demo-trace
nsaspy:feature/issue-18-benchmark-conformance
nsaspy:feature/issue-17-adaptive-recursion
nsaspy:feature/issue-16-durable-artifacts
nsaspy:feature/issue-15-mcp-2026-dual-version
nsaspy:feature/issue-14-mcp-2025-11-25
nsaspy:hotfix/live-tool-native-openrouter
nsaspy:feature/issue-13-chain-runtime
nsaspy:fix/stable-live-repair-gate
nsaspy:fix/live-repair-strategy-parser
nsaspy:feature/issue-12-durable-graph
nsaspy:feature/issue-11-agent-supervision
nsaspy:feature/issue-10-structured-outcomes-repair
nsaspy:feature/issue-9-rlm-completion
nsaspy:feature/issue-8-capability-tools
nsaspy:feature/issue-7-typed-plan-runtime
nsaspy:feature/issue-6-context-store
nsaspy:fix/openrouter-reasoning-response
nsaspy:fix/live-openrouter-smoke-stability
nsaspy:feature/issue-5-openrouter-provider
nsaspy:feature/issue-4-swi-bootstrap
nsaspy:agent/agentic-harness-research
nsaspy:agent/prolog-rlm-foundation
No reviewers
Labels
Clear labels
bug
Something isn't working
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
wontfix
This will not be worked on
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
nsaspy/prolog-rlm!375
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/313-per-call-preflight"
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?
Closes #313
Runtime invariant
One malformed call in a native tool-call batch no longer aborts the whole
direct run, while batch-fatal protocol/policy violations still reject the
entire requested batch before any operation executes.
after_call_normalization/5now runs one side-effect-free classificationpass (
classify_calls/4) that produces either a batch-fatal cause or theper-call statuses every later phase consumes. Preflight is never re-run, so
classification cannot double-charge or double-execute anything.
Recoverability whitelist (fatal by default)
recoverable_fault_kind/1is the single centralized positive policy:malformed_argumentsunavailable_tool_schemaOnly these two per-call preflight fault kinds become structured,
model-repairable fault observations. Every other
direct_faultescapesclassification and is batch-fatal, so a fault kind introduced later cannot
silently become model-repairable just because it happened inside the
per-call catch (the previous WIP caught every
direct_faultper call).Batch-fatal invariants are evaluated against the ORIGINAL request
(
fresh_call_idsagainstseen_call_ids, which includes faulted callIDs) stay terminal.
fails the whole batch (
effectful_batch_unsupported) before anyoperation executes — including when sibling calls failed preflight.
Recovery can never launder an unsafe requested effect batch into an
executable effectful singleton. Tests cover
effectful+malformed / malformed+effectful / effectful+unavailable /
unavailable+effectful / effectful+valid-read in both orderings with an
externally observable mutation counter pinned at zero.
execute (resolved classifications). Faulted calls never execute, are
never charged, and stay bounded by the model-call, iteration, token and
output budgets. Exhausted budgets are batch-fatal.
missing_assistant_message,assistant_tool_calls_mismatch) is still validated before anyexecution.
All-recoverably-invalid batches continue the bounded loop
When every call in a batch is individually recoverable (e.g. malformed
context call + unavailable tool), the runtime appends a deterministic
fault observation for each call, makes no tool/context charge, and
continues the bounded direct loop so the model can repair on the next
model call. The repair loop stays inside
max_model_calls(
model_call_budget_exhaustedafter the configured budget) — nounbounded repair loop. The old
Valid == [] => terminalbehavior was anunforced WIP decision, not a repository invariant; the loop bounds
(
model_admission) already make continuation safe.Fault observations
Each rejected call produces a deterministic tool result carrying the
original call ID, tool name, stable fault kind, bounded repair-relevant
detail, and error status. The WIP's
trace:Causeembedded raw internaldirect_error/tool_errorstructures (includingschema_validation_failedinternals) into the model-visible toolmessage; it is replaced by a bounded
valuepayload plus a boundedpreflight_trace{phase,kind,tool}. Observations are appended in theoriginal requested call order, interleaved with valid results, so the
provider protocol sees one tool result per call ID in request order.
Cancellation
check_cancelled/1also fires at the fault-observation boundary.rlm_cancelledkeeps its established terminal semantics and is neverconverted into a repairable
native_call_rejectedobservation.Accounting
Verified and pinned by tests: model calls and iterations advance once per
provider turn; valid siblings are charged exactly once; faulted calls are
never charged as successful native executions; batch-fatal rejection
leaves no partial effects and no second model call.
#314 dependency
#314 is merged into
main(b8206c7); this branch is rebased onto it andcontains only #313 work. Integration regressions cover the malformed
selector
{type:"metadata",index:-1}classified as recoverablemalformed_arguments(with the boundedinvalid_selector_fielddetail)beside an executable read-only sibling, plus the valid Auto-Dig shape
{type:"head",index:0,count:20}executing in order.Non-goals
SPEC/PLAN design, plan graph execution, symbolic mode, provider redesign,
OpenRouter behavior, scheduler changes, a generic exception framework,
#314 selector redesign, and durable distributed execution are untouched.
Test evidence
test/rlm_direct_partial_batch_test.pl: 29 tests (new suite). 8 wereRED against the pre-fix implementation (recorded): both
effectful-laundering orderings, both all-faults cases, the
internal-trace leak, and the three positive-whitelist policy tests.
test/rlm_direct_test.pl: the two unavailable-tool fail-closed testsupdated to the new per-call semantics (handler still never runs —
invocation count pinned at zero — and the observation still carries
unavailable_tool_schema).swipl -q -s test/check_runtime.plswipl -q -s test/load_all.plswipl -q -s test/run_tests.pl— 1121/1121 pass, 0 failed/timeout/blockedswipl -q -s scripts/validate_research_approval.plswipl -q -s scripts/design_gate.plbenchmark/run.pl -- deterministic— 16/16benchmark/run.pl -- deep-experiment— 15/15bin/prolog-rlm.pl -- demo --json+ graph trace smoke + trace-viewgit diff --checkgates are the correctness proof here.
[CI] Status after fix commit dff6bb9
The failing lane is the pre-existing recurring paid OpenRouter live-lane
failure (same signature as #291/#287/#293/#302/#307, tracked in
#315). It also fails on current
mainand is not caused bythis change.
Local verification for this branch:
rlm_direct_partial_batch): 3/3 GREENswipl -q -s test/run_tests.pl:1069/1069 passed, exit 0
Mergeable:
true(unstableonly due to the known live-lane failure).Pull request closed