Files
vdm/daemon/docs/core-requests-m1.md
T
samiandClaude Sonnet 5 ecedfac903 daemon: propose ADR 0011 — admission control vs. the segment budget
Settles the open interface question in AGENT-DAEMON.md before sched/ is
written: DAEMON's concurrency governor (global/per-queue/per-host, task
units) and CORE's maxActiveSegments (segment units) are two governors on
two axes with non-overlapping enforcement — each lane enforces exactly
the ceilings counted in the units it owns, with one narrow task-unit
clamp against maxActiveSegments. Records the fairness rule DAEMON needs
from CORE (min-1-before-seconds) so admission implies progress even
when one download could otherwise hold the entire segment budget.

Companion daemon/docs/core-requests-m1.md is the concrete engine API
ask (budget()/segments_active()/on_budget_changed, live-apply semantics
for set_max_active_segments, set_host_segment_cap, probe pool sizing)
plus one contract gap for PROTO (connection.maxActiveSegments missing
from Settings.schema.json).

Status: proposed, pending CORE sign-off on the five open items at the
end of the ADR. daemon/src/sched/ does not land until that lands.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Upd9WhG9oppieig5nRDLig
2026-09-09 23:20:09 +04:00

4.1 KiB

DAEMON → CORE — the engine API sched/ needs before it can be written

Status: open. Companion to docs/adr/0011-admission-control-and-the-segment-budget.md, which settles who enforces what. This doc is the concrete API ask that ADR depends on. Also carries one contract gap (C4) that is PROTO's, not CORE's, filed here because it was found while writing the ADR.

Ranking follows contracts/README.md rule 4 conventions even though this isn't a contracts/ change: new API surface = cheap, land anytime; a behavioural promise (min-1 fairness) = needs CORE's explicit sign-off before DAEMON builds on the assumption.


C1. Occupancy read-out, not inference

DAEMON's governor (ADR 0011 §1) must never count segments to make an admission decision, which means it needs to read engine occupancy rather than derive it from how many tasks it thinks it started. Requesting:

  • EngineBudget budget() const{ total: uint32, active: uint32, tasks_starved: uint32 } where tasks_starved is the count of running tasks currently holding zero segments (ADR 0011 §3.6 — DAEMON asserts this is 0 in steady state and logs if it isn't).
  • uint32_t segments_active(TaskId) const — for TaskSummary.segments projection (ADR 0010: the effective count) and for velox ls --json.
  • A coalesced on_budget_changed(callback), batched at the same ≤4 Hz as event.task.progress (brief item 5) — DAEMON is not polling this on a tighter loop.

C2. set_max_active_segments(uint32_t) and its live-apply semantics

Brief already has this as a settings value CORE enforces. DAEMON needs to know: does lowering it live drain existing segments down to the new ceiling (segments finish, no new ones start until under budget), or does it kill in-flight segments? DAEMON's assumption, pending your answer, is drain-not-kill — a live cut to 8 must not abort 24 in-flight segments and lose their partial ranges. ADR 0011 open item 3.

C3. set_host_segment_cap(host, uint32_t)

The per-host table is DAEMON state (SQLite settings/a host-cap table); CORE enforces it in segment units. Requesting a push API so there is one source of truth for the table and two enforcement points, per ADR 0011 §4. DAEMON derives its own per-host task cap from the same table value — same number, different unit, pushed once.

C4. Contract gap: connection.maxActiveSegments isn't in Settings.schema.json

Not CORE's item — flagging because ADR 0011 depends on it and core/docs/buffer-sizing.md already asked for it. SettingKey is a closed enum (additionalProperties: false), so DAEMON cannot expose this key through settings.get/settings.set until PROTO lands it. Until then DAEMON holds the value (default 32) locally and passes it to set_max_active_segments() at startup only — no wire exposure, so Options → Connection can't show or set it yet. Same ask as CORE's B2a bundle; landing them together is fine.

C5. Fairness rule sign-off (ADR 0011 §3)

Not new API — a behavioural contract. DAEMON's governor is built assuming:

  1. Min-1 before seconds: no running task gets a second slot while another running task has zero.
  2. Admission implies progress, subject only to connect timeout and per-host cap.
  3. A steal is slot-neutral.
  4. Priority order for slot distribution beyond the first comes from DAEMON (queue order, then FIFO by admission time, unless you'd rather take an explicit ordered list per tick — ADR 0011 open item 4).

If any of this can't be built as stated inside the stealer, say so before sched/ exists — this is the one thing in this doc that changes DAEMON's design rather than just its plumbing.

C6. Probe pool sized outside the segment budget

ADR 0011 §5: download.probe must not spend a segment slot, and capture.offer's 750 ms deadline can't wait on the segment budget being free. Confirming CORE's probe path already runs its own small worker pool (proposed size 4) independent of maxActiveSegments — if not, this is a request to make it so.


Raise objections against ADR 0011 directly; this doc is downstream of it and changes if the ADR does.