3.4 KiB
Capability Selection — the broker is the authority (#24, P1-01)
What changed
capability_broker.py is now the single capability-selection authority
(authority map capability_selection → v1_owner
ContextBroker.capability_selector). Before #24 every run exposed all 19
tool schemas to the model and the broker was advisory. Now:
select_for_job(task_description, need_tools=True)returns an ordered tool-schema selection with a recorded reason. The operator feeds that exact list intoLocalToolRuntime(tools=...)at both construction sites, so unselected capabilities contribute zero prompt tokens (schemas are excluded from the nativetoolspayload, not just hidden from the prompt block).- Verified floor: every tool-using job sees at least
FLOOR_TOOLS = read_file, write_file, edit_file, list_directory, grep_filesplus the escape hatch — selection is never empty and never misses the floor for coding tasks. A zero-score task falls back to the floor with reason"floor (no matches)". - Escape hatch:
find_capabilityis a native read-only tool that is always exposed. Executing it queries the broker (find_capability(query)) and returns matching capability summaries with argument schemas; the runtime then dynamically exposes the discovered native tools for the rest of the run (within the existing 8-iteration budget — no extra iterations granted). Unknown-tool errors name the hatch so the model can recover in one step. - Selection is deterministic (keyword scorer over TOOLS-derived cards,
ties broken by name) and bounded (
MAX_SELECTED = 10). - Task-family input reuses
infer_task_family(#29) for coherence with model routing.
Ownership boundaries
- Selection authority:
capability_broker.select_for_job. - Execution authority: unchanged in
tool_executor(#21) — the approval split (tool_requires_approval/PreparedTool) stays authoritative for gating; per-toolriskmetadata informs selection, never bypasses approval. - Degrade-not-crash (P0-06): any broker failure falls back to the full
TOOLS set (the pre-#24 behavior) and records a tier-1
record_failure("capability_selection", ...).
Cards
load_cards validates the card index and rebuilds when the file is
missing, empty, corrupt, or stale (mtime older than the registry).
Native-tool cards are synthesized from tool_executor.TOOLS metadata —
the single source for risk (read-only | write | irreversible),
preconditions, and side_effects — so selection has full coverage even
with no plugins installed.
Resume round-trip
tool_names is validated on resume, so the operator persists
exposed_tools (the selected set plus any tool dynamically exposed via
find_capability mid-run) into pending_state; _runtime_for_model
rebuilds exactly that set for the resumed runtime.
Trace
Schema v3 (additive): selected_tools, capability_reason,
find_capability_calls. v1/v2 rows and readers stay compatible.
Verification
- Frozen tests:
tests/test_p1_01_capability_authority.py. - Canary:
src/rune/canary_p1_06.py-style self-bootstrap insrc/rune/canary_p1_01.py(registered in.gitea/workflows/ci.yml) — asserts ~5–10 exposed schemas, a byte-size drop in the tools payload, the escape-hatch round-trip, and the floor guarantee. - Composition:
compose_capability_selection(root, state_dir)registers exactly oneCapabilityBrokerper composition root.