Files
rune/docs/capability-selection.md

3.4 KiB
Raw Permalink Blame History

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 into LocalToolRuntime(tools=...) at both construction sites, so unselected capabilities contribute zero prompt tokens (schemas are excluded from the native tools payload, 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_files plus 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_capability is 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-tool risk metadata 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 in src/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 one CapabilityBroker per composition root.