Advanced Routing - agbossjs/agboss GitHub Wiki
A cheat sheet of the flow: block of @Workflow: routing, parallelism and barriers. Everything here
is in the root entry agboss (from, chain, parallel, optional, catchError, node,
Self, @BatchParallelStrategy, @QuorumRouter). The basics are on Workflow.
The nodes used below (agents, routers, starts and finishes — ordinary components):
from(StartNode).next(AgentA),
from(AgentA).next(AgentB),
from(AgentB).next(FinishNode),
// or, as one step: chain(StartNode, AgentA, AgentB, FinishNode)chain(...) is a straight line of unconditional steps; a router may only be its last node.
The graph splits into a known number of branches and waits for all of them:
from(StartNode).next(PreparationAgent),
from(PreparationAgent).nextParallel(AgentA, AgentB, AgentC), // three branches
from(AgentA, AgentB, AgentC).join(AggregatorAgent), // the barrier: waits for all three
from(AggregatorAgent).next(FinishNode),When the number of items is known only at run time, a @BatchParallelStrategy() class extracts them
from the state; the target runs once per batch of batchSize items, at most concurrencyLimit at a
time. An agent sees its item as {{item}} in its prompt, an action as ctx.item.
from(StartNode).next(DispatcherAgent),
// 10,000 URLs in batches of 100, at most 10 ScraperAgent runs at once
from(DispatcherAgent).batchParallel(ScraperAgent, UrlExtractorStrategy, {
batchSize: 100,
concurrencyLimit: 10,
}),
from(ScraperAgent).next(SummaryAgent),
from(SummaryAgent).next(FinishNode),A strategy is a decorated class with extract(state):
@BatchParallelStrategy()
export class UrlExtractor {
extract(state: CrawlState): string[] {
return [...state.urls];
}
}A batch that pauses (an approval) resumes with the items already done kept; batchSize and
concurrencyLimit must be positive (batch.invalid-batch-size, batch.invalid-concurrency-limit),
the strategy decorated (batch.strategy-not-decorated).
A router picks one of its routes; from(Router).routes() takes them from the router, so the
list exists once:
from(StartNode).next(ValidatorRouter),
from(ValidatorRouter).routes(), // SuccessAgent or ErrorAgent
from(SuccessAgent, ErrorAgent).next(Finalizer),
from(Finalizer).next(FinishNode),A route's target may be parallel(...) — several branches at once — and optional(...) marks a
branch whose failure is reported and skipped instead of failing the run:
@Router({
name: "compliance",
description: "Adds a legal review when the work needs one",
prompt: "Does the work need a legal review?",
model: "typesafe/jev-1.13",
routes: [
{ prompt: "Needs a legal review", target: parallel(MainWorker, optional(LegalAgent)) },
{ prompt: "No legal review", target: MainWorker },
],
})
export class LegalCheckRouter {}from(StartNode).next(ComplianceRouter),
from(ComplianceRouter).routes(),
from(MainWorker, LegalAgent).join(Finalizer), // waits only for the branches the router started
from(Finalizer).next(FinishNode),Each route is one choice; a choice of several branches is a route to parallel(...):
@Router({
name: "expertise",
description: "Picks the reviewers",
prompt: "Who must review the request? Finance always does.",
model: "typesafe/jev-1.13",
routes: [
{ prompt: "Money matters only", target: FinanceAgent },
{ prompt: "Money and people", target: parallel(FinanceAgent, HrAgent) },
{ prompt: "Money and tech", target: parallel(FinanceAgent, TechAgent) },
],
})
export class ReviewersRouter {}Self as a target sends the work back to the agent that ran last (the router then needs
maxVisits). Routes are sent sorted by target name, so their order never changes the request.
chain(StartNode, AgentA, FinishNode),
catchError(AgentA, NetworkError).next(ErrorAgent), // instead of failing the run
from(ErrorAgent).next(FinishNode),catchError(Node, ErrorClass) matches by the class's stable code (a class without a static code
by its name): the class, its subclasses and errors caused by it. The default Error catches
everything. The match is made on a plain record in the state, so it holds after a resume.
joinQuorum is an active barrier for parallel branches: unlike join, which waits for all of
them, it counts votes as branches finish, and can stop early, fail fast or time out. A
@QuorumRouter class implements QuorumStrategy: filterVote(state) runs as each branch finishes
(is this a vote?), route(state, hasQuorum) once at the end (where to go).
@QuorumRouter({ name: "critical-findings" })
export class CriticalFindings implements QuorumStrategy<AuditState> {
filterVote(state: AuditState): boolean {
return state.reports.at(-1)?.severity === "CRITICAL";
}
route(_state: AuditState, hasQuorum: boolean) {
return hasQuorum ? EmergencyLockdown : FinishNode;
}
}from(StartNode).nextParallel(StaticAnalyzer, DynamicAnalyzer, DependencyChecker),
from(StaticAnalyzer, DynamicAnalyzer, DependencyChecker)
.joinQuorum(SecurityQuorum, {
min: 2, // at least 2 critical reports
max: 3, // with 3, stop at once
timeoutSeconds: 45, // do not wait longer for the slow branches
})
.routes(EmergencyLockdown, FinishNode),How min, max and timeoutSeconds combine:
-
Only
{ min }(max=min): as soon asminvotes are in, the other branches are cancelled androutegetshasQuorum = true; oncemincannot be reached any more (too many branches finished without a vote), they are cancelled andhasQuorum = false. -
{ min, max }: reachingmindoes not cancel the others — the barrier waits formax. Atmaxthe rest are cancelled (true); when all finish,hasQuorumisvotes >= min. -
{ min, max, timeoutSeconds }: as 2, and after the timeout the remaining branches are cancelled andhasQuorumisvotes >= min. Too many early non-votes fail fast without waiting. -
{ min, timeoutSeconds }(max=min):minvotes end it at once (true); the timeout ends it withfalse.
A @Workflow class is a node of another flow — from(StartAgent).next(SubWorkflow) — and runs as a
subgraph from its start to its finish, sharing the parent's limits and spend. See
Workflow → Nested workflows.
An @Agent({ compensate: UndoWorkflowOrAction }) declares how to undo it; a
catchError(Node).compensateWith(Compensator) step runs the compensation when a later step fails.
See Saga and cancellation.