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
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user