Cognitive Router¶
The cognitive router compiles natural-language tasks onto proven
orchestration shapes. The LLM fills one typed GoalFrame; everything
after that — protocol selection, policy gating, compilation — is
rule-based.
Dispatch¶
Router ¶
Router(*, extractor: Agent, compiler: CognitiveCompiler, on_frame: Callable[[GoalFrame], None] | None = None)
One-shot dispatcher: text in, :class:RunnableResult out.
Parameters¶
extractor:
:class:~locus.Agent configured with
output_schema=GoalFrame. Its parsed result drives selection.
compiler:
:class:CognitiveCompiler set up with protocols, capabilities,
policy, and model.
on_frame:
Optional callback fired right after a frame is extracted. Useful
for telemetry / workbench display.
Source code in src/locus/router/runtime.py
extract
async
¶
Run the extractor and return the parsed :class:GoalFrame.
Emits router.frame.extracted on success or
router.frame.failed on schema rejection, scoped to
run_id when supplied. Raises :class:FrameExtractionError
on failure — the compiler can't recover from that.
Source code in src/locus/router/runtime.py
dispatch
async
¶
Extract a frame, compile a runnable, execute it.
run_id scopes every emitted :class:StreamEvent to one
cognitive dispatch. Defaults to a fresh uuid4 if omitted —
callers that want their own correlation id (a request id, a
Slack thread, etc.) should pass one in. The id is also
attached to the :class:RunnableResult raw payload so
downstream callers can correlate without re-parsing the bus.
Source code in src/locus/router/runtime.py
RunnableResult ¶
Bases: BaseModel
Normalized result envelope for any compiled router execution.
Goal Frame¶
GoalFrame ¶
Bases: BaseModel
The single typed object the LLM produces.
Everything downstream of GoalFrame — protocol selection,
capability binding, policy verdict, builder dispatch — is
deterministic. The LLM never authors graph topology, only this
schema.
TaskType ¶
Bases: StrEnum
The kind of cognitive work the user is asking for.
Every protocol declares which TaskType values it can handle, so
the registry can filter candidates by frame.primary_goal alone.
Risk ¶
Bases: _OrderedStrEnum
Complexity ¶
Bases: _OrderedStrEnum
Protocol registry¶
Protocol ¶
Bases: BaseModel
Declarative description of an orchestration shape.
ProtocolRegistry ¶
Deterministic mapping from :class:GoalFrame to :class:Protocol.
Source code in src/locus/router/protocol.py
filter_candidates ¶
filter_candidates(frame: GoalFrame, *, available_capabilities: set[str] | None = None) -> list[Protocol]
Return every protocol that passes the three gates.
The shared filter for both select() (rule-based ranker) and
any opt-in selector that wants to choose among already-qualified
candidates. Gates:
frame.primary_goal in p.handlesp.risk_max >= frame.riskset(p.requires_capabilities).issubset(available_capabilities)
Returns the survivors in registration order. Empty list is a
valid result — callers raise :class:NoMatchingProtocolError
when appropriate (this method is filter-only by design).
Source code in src/locus/router/protocol.py
select ¶
Pick the best protocol for frame.
Filters by handles ∋ primary_goal ∧ risk_max ≥ frame.risk
∧ required_capabilities ⊆ available_capabilities, then ranks
candidates by complexity-fit + cost.
Source code in src/locus/router/protocol.py
builtin_protocols ¶
The full eight-protocol catalogue. Compose your own
:class:ProtocolRegistry from these (or a subset).
Source code in src/locus/router/protocol.py
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 | |
Policy gate¶
PolicyGate ¶
Pre-flight risk check for a (frame, protocol) pair.
Two thresholds:
max_risk— anything strictly above is denied.require_approval_above— anything strictly above (and withinmax_risk) is allowed but flagged for an approval interrupt.
Defaults: max_risk=Risk.HIGH (nothing denied by default) and
require_approval_above=Risk.MEDIUM (HIGH-risk frames need
explicit approval).
Source code in src/locus/router/policy.py
PolicyVerdict ¶
Bases: BaseModel
One of three outcomes from :meth:PolicyGate.check.
Compiler¶
CognitiveCompiler ¶
CognitiveCompiler(*, protocols: ProtocolRegistry, capabilities: CapabilityIndex, policy: PolicyGate, model: Any, skills: SkillIndex | None = None, a2a_endpoint: str | None = None, on_approval: ApprovalCallback | None = None, protocol_picker: LLMProtocolPicker | None = None)
Glue between protocols, capabilities, policy, and the model.
Parameters¶
protocols:
:class:ProtocolRegistry populated with built-in or custom
protocols.
capabilities:
:class:CapabilityIndex over the surrounding ToolRegistry.
The index resolves capability ids to real tools at compile time.
policy:
:class:PolicyGate that runs between selection and build.
model:
A locus model instance (or model string) injected into every
builder. Builders pass it to :class:~locus.Agent / specialist
constructors.
skills:
Optional :class:SkillIndex. When provided, every emitted
:class:Agent is configured with a
:class:~locus.skills.SkillsPlugin containing the skills tagged
for frame.domain (plus any globally-tagged skills). The
agent loop's L1 / L2 / L3 progressive disclosure surfaces them
at runtime.
on_approval:
Optional async callback fired when the verdict requires
approval. Defaults to denying — wire your workbench / CLI
approval flow here.
protocol_picker:
Optional :class:LLMProtocolPicker. When present, the compiler
delegates the last-mile protocol pick to the model whenever
the filter leaves more than one candidate. When absent (the
default), selection is the deterministic
:func:_rank_key-based ranker. The picker is the only part
of the routing pipeline that uses the model; everything else
— filtering, policy gating, capability binding, builder dispatch
— remains rule-based.
Source code in src/locus/router/compiler.py
compile
async
¶
Pick a protocol, run the gate, build the runnable.
run_id (when provided) scopes every emitted
:class:StreamEvent so the workbench's SSE consumer can
correlate selection / verdict / compile events with one
cognitive dispatch.
Source code in src/locus/router/compiler.py
Capabilities¶
Capability ¶
Bases: BaseModel
A tool (or human action) tagged with router-relevant metadata.
CapabilityIndex ¶
View over a :class:ToolRegistry that adds router metadata.
Not a replacement for ToolRegistry — the underlying Tool
instances live there. CapabilityIndex only holds the metadata
overlay (domain, risk) and resolves capabilities back to tools on
demand.
Source code in src/locus/router/capability.py
annotate ¶
annotate(cap_id: str, *, tool_name: str, description: str, domain: str, risk: Risk = Risk.LOW) -> Capability
Register router metadata for an existing tool (or a human step).
tool_name must already exist in the underlying
:class:ToolRegistry, except for the $human sentinel which is
treated as a non-tool capability.
Source code in src/locus/router/capability.py
lookup ¶
Resolve a list of capability ids; raises if any are missing.
Source code in src/locus/router/capability.py
for_domain ¶
all ¶
resolve_tool ¶
Return the underlying :class:Tool. Raises for human capabilities.
Source code in src/locus/router/capability.py
SkillIndex ¶
A domain-tagged view over a list of :class:Skill instances.
Source code in src/locus/router/skill_index.py
register ¶
Add a skill to the index with an optional domain tag.
domain="" (the default) means "applies to every domain";
:meth:for_domain always returns these alongside domain-specific
matches.
Source code in src/locus/router/skill_index.py
register_many ¶
get ¶
Return a skill by name. Raises :class:KeyError if missing.
Source code in src/locus/router/skill_index.py
for_domain ¶
Skills tagged with domain, plus every globally-tagged skill.
The router calls this with :attr:GoalFrame.domain to pick the
catalogue handed to each compiled :class:Agent. Globally-tagged
skills (registered with domain="") are always included so a
common catalogue (e.g. "communication tone", "safety checks") is
available everywhere.