Documentation
¶
Overview ¶
Package routers ships Harbor's runtime routing surface — Phase 14 of the runtime kernel chain (RFC §6.1).
Three router shapes:
- PredicateRouter: ordered list of (predicate, target) pairs. The first predicate that matches selects the target.
- UnionRouter: payload-tag dispatch (string discriminator → target). Used for sum-type-shaped payloads (e.g. planner Decision variants).
- RoutePolicy: explicit-target override. Set on an envelope's Meta["route_policy"] to bypass predicate / union routing and route to a specific target. The planner-driven path.
Routers wrap as engine.Node via AsNode(name); they consume the Phase 10 NodeContext.Emit surface to write into the chosen branch. A router does NOT transform payloads — it decides where to send them.
Index ¶
Constants ¶
const MetaKeyRoutePolicy = "route_policy"
MetaKeyRoutePolicy is the Envelope.Meta key under which RoutePolicy can ride. Centralised so callers and tests reference one symbol rather than the string literal.
Variables ¶
var ErrRouteNotFound = errors.New("routers: no branch matched and no default configured")
ErrRouteNotFound — predicate / union routing produced no target and the router has no Default. Wraps with a human-readable suffix indicating which router (predicate vs union) and what went wrong. Phase 11's RunError will subsume this error code; Phase 14 ships the typed sentinel so callers can errors.Is.
Functions ¶
This section is empty.
Types ¶
type PredicateBranch ¶
PredicateBranch is one (predicate, target) pair on a PredicateRouter. Predicates are evaluated in slice order; the first one returning true selects the target.
type PredicateRouter ¶
type PredicateRouter struct {
Branches []PredicateBranch
Default *engine.NodeRef
}
PredicateRouter selects the first branch whose predicate matches the incoming envelope. Branch order matters; the Default target catches "no match" (or returns ErrRouteNotFound when nil).
Routing happens via RoutePolicy: the router-as-Node returns the envelope unchanged but writes the chosen target into Meta[MetaKeyRoutePolicy]. The engine's worker honors that policy when it emits to the router's outgoing channels (every adjacency receives the envelope, but only the targeted node will see it via downstream filtering — see "Honoring RoutePolicy in adjacency" below).
For Phase 14, the recommended adjacency shape is:
router → branchA, branchB, branchC
where every branch node guards its NodeFunc with a RoutePolicy check at entry (returning a no-op envelope if the policy does not target it). This keeps the engine's worker loop unchanged from Phase 10 — routers are pure node-level concerns.
A more elegant adjacency shape (engine-level RoutePolicy honoring) is reserved for a follow-up phase that extends Phase 10's worker; Phase 14 stays out of engine.go to avoid colliding with the parallel Phase 11 fork.
func (*PredicateRouter) AsNode ¶
func (r *PredicateRouter) AsNode(name string) engine.Node
AsNode wraps the router as an engine.Node. The wrapped NodeFunc examines the envelope, evaluates predicates in order, writes the chosen target into Meta[MetaKeyRoutePolicy], and returns the envelope unchanged. The engine's adjacency map then routes the output to all branches; each branch's NodeFunc reads Meta and drops the envelope if it isn't the targeted branch.
Returns a Node whose Name is `name` and whose Func wraps the router. AllowCycle defaults to false — routers are downstream-only.
func (*PredicateRouter) Select ¶
Select evaluates the router's predicates and returns the chosen target. Returns ErrRouteNotFound when no branch matches and no default is set. A nil receiver is rejected with ErrRouteNotFound rather than panicking — defensive coding, since a misconfigured graph might construct a PredicateRouter with no branches.
type RoutePolicy ¶
type RoutePolicy struct {
// ExplicitTarget names a single downstream node by name. nil means
// "no override."
ExplicitTarget *engine.NodeRef
}
RoutePolicy is the override mechanism that bypasses predicate / union routing when set on an envelope's Meta["route_policy"]. Useful for planner-driven branch selection where the planner already knows the target (e.g. a deterministic-planner step that names the next node explicitly).
ExplicitTarget is a *engine.NodeRef so a nil pointer means "no override; defer to predicate/union routing." A non-nil pointer pinning a specific target overrides whatever the wrapped router would have selected.
func FromMeta ¶
func FromMeta(meta map[string]any) (RoutePolicy, bool)
FromMeta extracts a RoutePolicy from env.Meta if present. Returns (zero, false) when Meta is nil, the key is absent, or the value is not a RoutePolicy. Callers use the bool to decide whether to honor the override; the zero value is a safe fall-through.
type UnionRouter ¶
type UnionRouter struct {
Tag func(any) string
Branches map[string]engine.NodeRef
Default *engine.NodeRef
}
UnionRouter dispatches based on payload tag — a string discriminator the Tag function extracts from env.Payload. The result keys into Branches; Default catches the unknown-tag case.
Used for sum-type-shaped payloads. Example:
router := &UnionRouter{
Tag: func(p any) string {
switch p.(type) {
case planner.CallTool: return "call_tool"
case planner.Finish: return "finish"
}
return ""
},
Branches: map[string]engine.NodeRef{
"call_tool": toolDispatcher.Ref(),
"finish": finalizer.Ref(),
},
Default: nil, // unknown tag → ErrRouteNotFound
}
func (*UnionRouter) AsNode ¶
func (r *UnionRouter) AsNode(name string) engine.Node
AsNode wraps the router as an engine.Node. The wrapped NodeFunc extracts the tag, looks up the branch, writes the chosen target into Meta[MetaKeyRoutePolicy], and returns the envelope unchanged.