Evaluation budget and cancellation: a host cannot bound or interrupt a running step
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 20/100
Research direction
This is a broad design and implementation effort, and the issue delegates delivery to sub-issues #1819–#1824; start with #1819 for the foundation contract and its named source paths and tests. The issue’s plan identifies #1824 as the owner of final documentation and qualification. Done means the sub-issue behaviors have merged and final-candidate evidence passes; the parent explicitly remains open until then.
Written by the indexing model from the issue text.
Description
Requirement
A host must be able to bound the work a single evaluation performs, and stop
one already in flight. Today it can do neither: wl_session_step()
(wirelog/session.h:239) runs to completion, and the only cancellation the API
offers is of delta notifications, which the header is explicit about:
Cancellation does not cancel evaluation: a failed step still requires retry
—wirelog/session.h:252
So a host that hands wirelog a valid but expensive program — a deep recursion,
a product join over a large EDB — has no lever. It cannot cap the work, cannot
set a deadline, and cannot abandon the step. Its only recourse is to kill the
process.
This is a resource-bounding requirement, not a hostile-input one. The threat
model already names the program text as trusted (docs/SECURITY_MODEL.md §1)
and this request does not ask to change that. Trusted programs can still be
accidentally expensive, and an embedding host is the only party that knows how
much work it is willing to pay for.
Why the memory governor is not enough
Version 2 of wl_session_options_t (#1473) added a caller-retained memory
admission governor, and WIRELOG_MEMORY_BUDGET gives an explicit managed-memory
ceiling (docs/MEMORY.md §6). That bounds one axis. It does not bound time or
work: a program that stays inside its memory budget while iterating for an
unbounded period is admitted the whole way, and docs/MEMORY.md §6 records
that the governor's only consumer today is the join operator.
The memory governor is, however, the right precedent for the shape of this
feature: a caller-supplied, caller-owned limit passed through versioned session
options rather than resolved from the environment.
Suggested shape, not a decision
wl_session_options_t already carries size and version
(wirelog/session_options.h:35-41), so a version 3 adding one field is the
natural path. Candidates, in rough order of how much they constrain the engine:
- A cancellation token the host sets from another thread; the evaluator
checks it at an existing safe point (stratum boundary, semi-naive iteration
boundary, join batch) and returns a distinct error. - A step/work budget — a cap on iterations or derived rows — that fails the
step deterministically when exceeded. Deterministic, reproducible, and
testable, unlike a wall-clock deadline. - A wall-clock deadline, which is the easiest for a host to reason about
and the hardest for the engine to make reproducible.
The distinct error matters as much as the mechanism: a bounded-out step must be
distinguishable from an evaluation failure, because the host's response differs
(surface a budget refusal to the operator vs. retry).
The existing step contract says a failed step delivers nothing and must be
retried, with the baseline preserved. A bounded-out step should keep that same
contract — the session stays usable, and the host may retry with a larger
budget — rather than poisoning the session.
Acceptance criteria
- A host can supply a per-session evaluation bound and/or a cancellation
handle through versioned session options, without setting an environment
variable. - An evaluation that exceeds the bound, or is cancelled, terminates within
a documented granularity and returns an error distinct from an evaluation
failure. - A bounded-out or cancelled session remains usable: the documented
wl_session_stepbaseline/retry contract holds, and the host can retry
with a larger bound. - The bound is observable — the host can tell how much of it was consumed —
so it can be calibrated rather than guessed. - Behaviour is documented in
docs/SEMANTICS.mdalongside the existing
evaluation contracts, and indocs/MEMORY.mdwhere it interacts with the
memory governor. - Tests cover: bound not reached, bound reached mid-evaluation, cancellation
from another thread, retry after a bounded-out step, and interaction with
a recursive program. - No behaviour change for a caller that sets neither, on either the easy or
the advanced facade.
Discovery context
Raised from downstream wyrelog work (semantic-reasoning/wyrelog#1206) against
the released 0.70.0 pin (c353350). wyrelog rebuilds a fact graph's engine on
every fact mutation and swaps the result in atomically, so evaluation cost lands
on the write path. It wants to admit operator-registered derivation rules, but
cannot allow recursion in them: an unbounded recursive rule would stall writes
to that graph with nothing able to interrupt it. wyrelog recorded the same
precondition earlier in its own commit 960427ae — "keep query input as a parsed
allowlisted atom until cancellation and recursion budgets exist."
That is one host's motivation, not the scope of this issue. Any host embedding
wirelog behind a request boundary has the same gap.
Implementation plan — 2026-09-22
Analyzed current origin/main at e8e977ec6ffcc0bcc0b0a4aae56bb842da34b389. This plan preserves the original requirement and acceptance above. Independent Architect, Critic and Reviewer reviewed the six issue drafts; this is planning approval, not implementation or passing-test evidence.
Agreed first implementation scope
Deliver cooperative cancellation plus a per-evaluation-attempt work budget, observable consumption and retry with a larger allowance. Include evaluating STEP/SNAPSHOT calls, typed wrappers, both easy and advanced public facades, serial operators and active parallel paths. Defer engine-managed wall-clock deadlines; hosts can drive cancellation from their own timer. Cooperative cancellation does not preempt host callbacks or promise a wall-clock return deadline.
Iteration/output-row limits alone cannot bound a large acyclic JOIN or rejected-candidate scan. Charge input traversal, probes/candidates and bounded internal processing, including no-output work, under a documented metric and maximum polling quantum. One allowance covers coordinator and all workers; internal replay/fallback does not reset it. Different worker counts/optimizer choices need not report equal work.
Registered implementation issues
Every item below is an actual GitHub sub-issue and contains its own context, source paths, implementation scope, acceptance tests, dependencies and existing-issue boundaries.
| Order | Issue | Deliverable | Hard prerequisites |
|---|---|---|---|
| Foundation | #1819 | Retained control, versioned options, status/usage accounting and concurrency contract | None |
| Recovery | #1820 | STEP/SNAPSHOT attempt lifecycle, original-baseline retry and publication safety | #1819 |
| Serial enforcement | #1821 | Operator, row/candidate and expensive-fallback metering | #1819, #1820 |
| Parallel enforcement | #1822 | Global allowance, cooperative worker stop, replay accounting and safe drain | #1819, #1820, #1821 |
| Public enablement | #1823 | Installed easy/advanced options, cancel/configure/report APIs and distinct errors | #1819–#1822 |
| Qualification | #1824 | Final-SHA conformance matrix, documentation and host examples | #1819–#1823 |
Implementation can use smaller atomic commits within each issue. API design and test preparation can proceed against the foundation contract; exposing an enforceable public option waits for the serial and parallel coverage gates.
Contracts that the implementation must preserve
- Options are borrowed only during creation; the control has independent retained lifetime. One control attaches to one logical session, including easy lazy-open ownership before inner session creation. Workers borrow the same attempt.
- No control and zero/unlimited allowance preserve existing behavior. Decode accepted internal v2/v3 prefixes explicitly; retain legacy easy option prefixes and old advanced constructor signatures.
- Cancellation is sticky; only requesting it is concurrently supported. Change the next attempt's allowance/reset the token while quiescent, including when pending retry forbids ordinary input mutation.
- Each caller retry starts a new consumption report while the original logical-step notification baseline survives. One evaluating snapshot that internally resumes a step remains one attempt.
- Failed evaluation publishes nothing. Once publication commits, finish the successful callback batch; late cancellation applies to the next attempt. Notification-only cancellation remains separate.
- Callback-free retry requires explicit recovery proof: current observer state is conditional on a delta callback, and plain-step completion bookkeeping does not by itself prove arbitrary-stop safety.
- Distinct exhaustion/cancellation statuses must survive expression errors, worker aggregation, cleanup and public facade mapping. Do not repurpose EOVERFLOW/EAGAIN, which existing paths can interpret as fallback.
- Stop must preserve input/retraction/frontier/trace state and owned buffers/leases/reservations. Mandatory cleanup/drain is allowed after work exhaustion; no task context is freed before its submitted workers finish.
- Document real polling units, uninterruptible library/host calls and cleanup costs. Do not call an arbitrary whole-input sort a bounded checkpoint.
Existing issue ownership
Reuse #1369/#1382 memory admission, #1370 bounded batches and #1777 pipeline integration. #1373/#1383 own bounded channels and memory backpressure; consume their stop/wake contract if present without making the entire memory project a prerequisite. Test real current transport behavior and do not claim a nonexistent blocked-producer scenario.
#1765/#1805 own existing cleanup defects. Audit current merged fixes and require any still-reachable defect resolved before corresponding cancellation acceptance; do not duplicate ownership mechanisms. Closed #1812 supplies lifetime evidence. #1375 retirement does not excuse missing coverage for currently enabled K-Fusion.
Parent completion mapping
| Original acceptance | Implementation/evidence owner |
|---|---|
| Versioned host-supplied bound/cancel handle | #1819, #1823 |
| Finite documented polling and distinct errors | #1819, #1821, #1822, #1823 |
| Usable session and baseline-preserving retry | #1820, #1822, #1823 |
| Observable consumed work | #1819, #1823 |
| Semantics and memory documentation | #1824 |
| Bound/cross-thread cancellation/recursion/retry tests | Focused tests in #1819–#1823; final matrix in #1824 |
| Unchanged easy/advanced defaults | #1823, #1824 |
Keep #1817 open until those behaviors have merged and the final-candidate evidence passes. Publishing this plan and its sub-issues does not satisfy implementation acceptance.
- Dominant language
- C
- Stars
- 18
- Forks
- 2
- Avg merge
- 6h 19m
- Merged PRs (30d)
- 280
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from semantic-reasoning/wirelog
-
Doc: insert executor and incremental consolidation leave sorted runs, not globally sorted headsOpen
Difficulty 1/5 Under an hour Newbie friendliness 83/100
semantic-reasoning/wirelog#2133 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
semantic-reasoning/wirelog#2042 ·
Maintainers usually reply within 1 day
-
p2-medium
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
semantic-reasoning/wirelog#1914 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 8/100
semantic-reasoning/wirelog#2186 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 3/5 1-2 days Newbie friendliness 62/100
semantic-reasoning/wirelog#2142 ·
Maintainers usually reply within 1 day
All issues in semantic-reasoning/wirelog
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
FujiNetWIFI/fujinet-lib#186 ·
Maintainers usually reply within 1 day
-
VIEW_TYPE set for computer playersPossibly taken @Loobinex claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 2 days
-
bug
Difficulty 2/5 Under an hour Newbie friendliness 78/100
brunodev85/winlator-app#90 ·