Progressive Crystallization
One of the costliest anti-patterns in production AI engineering is permanent stochasticity: keeping every routing decision and multi-step tool sequence inside an LLM call long after the underlying domain patterns have stabilized.
Early in a feature’s lifecycle, you want the flexibility of DecidedBy.Model routers and multi-tool @Agent loops so your system can handle messy, unanticipated user inputs. Over weeks of production traffic, however, 80–95% of executions often follow repeatable structural patterns:
- A
DecidedBy.Modelrouter consistently picksEnterpriseApprovalAgentwheneverorder.amountCents > 100_000ororder.region === "EU". - An
@Agentwith four@Toolbindings callsFetchInvoiceToolfollowed immediately byValidateTaxIdToolin the exact same order on 99% of runs.
Progressive Crystallization is the architectural practice of systematically identifying these settled stochastic Desire Paths via content-addressed revision hashes (agboss describe --json), multi-run pass@k vs pass^k reliability benchmarks, and execution traces (ExecutionRecord and Langfuse / OpenTelemetry spans), and replacing them in place without changing neighbor contracts:
DecidedBy.Model@Routerand@Arbiter$\to$DecidedBy.Code(0msLLM latency,$0.00token spend)DecidedBy.Model@InputGuardrail,@OutputGuardrail, and@Judge$\to$DecidedBy.Code(GuardrailHandler/ deterministicJudgeHandlerinspecting DTOs andtoolCallsat$0.00cost)- Repetitive
@Toolsequences inside an@Agent$\to$ deterministic@Actionsteps or@ToolInterceptorgates - Model-summarized
@Compaction$\to$CompactionStrategy.Custom(CompactionHandler) writing structured facts into a knowledge graph or vector store - Human-in-the-loop
ChannelOutcome.awaitReply()pauses on low-risk cases $\to$ immediate auto-approvedChannelOutcome.decided(...)
Step 1: Content-Addressed Revision Snapshots (agboss describe --json)
Section titled “Step 1: Content-Addressed Revision Snapshots (agboss describe --json)”In typical agent frameworks, production traces in Langfuse or OpenTelemetry quickly drift from the Git repository: when looking at a trace from yesterday, neither an engineer nor an AI coding agent can tell whether the prompt text, DTO schema, or graph topology has changed since that run.
In AGBoss, npx agboss describe --json exports a deterministic, machine-readable snapshot of your entire @Application without requiring any API keys:
npx agboss describe --jsonThe JSON envelope includes three cryptographic content hashes:
| Hash Field | Scope | What It Hashes |
|---|---|---|
configHash |
Per @Workflow |
Complete graph topology: start/finish nodes, step sequence, .switch() / .chooseOne() edges, .joinQuorum() rules, and .onError() paths |
promptHash |
Per @Agent / @Router / @Arbiter / @Guardrail |
Resolved system prompt template (prompt + promptUrls) and model configuration |
schemaHash |
Per DTO Class | Normalized JSON Schema compiled from @Text, @Integer, @Decimal, @Flag, @OneOf, @ListOf, and @Nested decorators |
Every ExecutionRecord (result.record) and step trace emitted to OnExecutionEnd records these exact hashes (configHash, promptHash, schemaHash).
Step 2: Crystallizing a DecidedBy.Model Router into DecidedBy.Code
Section titled “Step 2: Crystallizing a DecidedBy.Model Router into DecidedBy.Code”Suppose you launch a workflow with a DecidedBy.Model router (OrderTriageModelRouter) that classifies incoming orders using a fast decision model (typesafe/jev-1.13). Once execution traces for OrderTriageModelRouter’s promptHash and OrderTriageDto’s schemaHash confirm that the routing boundary is a pure function of OrderTriageDto fields (or a deterministic database lookup via an @Injectable() service), you (or your coding agent) crystallize the router in place by switching decidedBy to DecidedBy.Code:
import { DecidedBy, Router } from "@agboss/core";import { Flag, Integer, Text } from "@agboss/core/dto";
export class OrderTriageDto { @Text({ prompt: "Customer tier: free or enterprise" }) tier!: string;
@Integer({ min: 0, prompt: "Total order value in cents" }) amountCents!: number;
@Flag({ prompt: "Whether the shipping address is international" }) isInternational!: boolean;}
@Router({ name: "order-triage-model-router", description: "Routes orders to compliance review or fast-track capture", decidedBy: DecidedBy.Model, model: "typesafe/jev-1.13", prompt: "Route international orders or orders over $5,000 to compliance review; fast-track the rest.", input: { order: OrderTriageDto }, routes: [ { target: ComplianceReviewAgent, prompt: "Order requires manual or automated export/value compliance review", }, { target: FastTrackCaptureAction, prompt: "Domestic standard order eligible for immediate capture", }, ],})export class OrderTriageModelRouter {}import { DecidedBy, Router, type InputOf, type Route, type RouterHandler,} from "@agboss/core";
@Router({ name: "order-triage-router", description: "Deterministically routes international or high-value orders to compliance review", decidedBy: DecidedBy.Code, input: { order: OrderTriageDto }, routes: [ { target: ComplianceReviewAgent }, { target: FastTrackCaptureAction }, ],})export class OrderTriageRouter implements RouterHandler<OrderTriageRouter> { route(input: InputOf<OrderTriageRouter>): Route<OrderTriageRouter> { const { amountCents, isInternational } = input.order.value; return isInternational || amountCents > 500_000 ? ComplianceReviewAgent : FastTrackCaptureAction; }}import { expect, it } from "vitest";import { testRouter } from "@agboss/core/testing";import { ComplianceReviewAgent, FastTrackCaptureAction, OrderTriageRouter, TriageStart,} from "../src/routers/order-triage.router.js";
it("verifies crystallized DecidedBy.Code router with zero LLM calls", async () => { const runRouter = testRouter(OrderTriageRouter);
const highValue = await runRouter({ order: { value: { tier: "enterprise", amountCents: 750_000, isInternational: false }, producer: TriageStart, }, }); expect(highValue.output).toBe(ComplianceReviewAgent);
const standard = await runRouter({ order: { value: { tier: "free", amountCents: 25_000, isInternational: false }, producer: TriageStart, }, }); expect(standard.output).toBe(FastTrackCaptureAction);});Notice what did not change:
- The
@Workflowdefinition (*.workflow.ts) is completely untouched —.toRouter(OrderTriageRouter).chooseOne(...)works identically whetherOrderTriageRouterisDecidedBy.ModelorDecidedBy.Code. - Downstream Path Scope availability (
Produced<Dto, P>) is preserved. - Latency for this node drops from ~150–400ms to
<0.1ms, and token spend drops to$0.00.
Step 3: Crystallizing Repetitive @Tool Chains into an @Action
Section titled “Step 3: Crystallizing Repetitive @Tool Chains into an @Action”A second prime target for crystallization is an @Agent whose traces show a fixed sequence of tool calls before producing a structured DTO.
Instead of paying an LLM to orchestrate ToolA -> ToolB on every turn:
- Extract the deterministic API/database calls into an
@Injectable()service or a dedicated@Actionnode upstream of the@Agent. - Wire
.toAction(FetchAndEnrichAction).returns(EnrichedContextDto).toAgent(AnalystAgent)in the workflow. - Now the deterministic data fetching executes with
0LLM tokens and fullExecutionContext.step.idempotencyKeydeduplication, whileAnalystAgentreceives the pre-fetchedEnrichedContextDtoin a single, KV-cache-friendly turn.
Step 4: Discovering Crystallization Candidates (pass@k vs pass^k) & Verifying Zero Regression
Section titled “Step 4: Discovering Crystallization Candidates (pass@k vs pass^k) & Verifying Zero Regression”When running an @EvalSuite with --runs <k> (for example, k = 5), AGBoss reports two complementary metrics for every case:
pass@k($\ge 1$ of $k$ runs passed): Measures whether the model can solve the task.pass^k($k$ of $k$ runs passed): Measures whether the workflow solves the task reliably on every production execution.
A case with high pass@k (1.00) but low pass^k (0.20–0.60) is the canonical signal of a stochastic step that should be crystallized into a DecidedBy.Code router, guardrail, @ToolInterceptor, or @Action.
Before promoting a crystallized component to production, verify that end-to-end quality, routing accuracy, and @Judge pass rates remain identical (while cost and latency drop):
# 1. Capture recent production/staging tasks into a reusable golden dataset:npx agboss golden add --name order-triage-core --from-last 20
# 2. Compare baseline vs candidate profile on the exact same golden tasks:npx agboss compare --profiles base,candidate --golden order-triage-core
# 3. Run the evaluation suite across k=5 trials and gate against the main branch baseline:npx agboss eval run --suite order-triage-eval-suite --runs 5 --baseline .artifacts/eval/main.json --fail-on-regression
# 4. Export execution history and inspect the Progressive Crystallization Radar in AGBoss Mesh Hub (#401):npx agboss describe --json > .artifacts/describe.jsonnpx agboss records export --all-tenants --out .artifacts/records.jsonlnpx agboss hub show .artifacts --html .artifacts/hub.htmlnpx agboss hub ui .artifactsThe Progressive Crystallization Radar in agboss hub joins agboss describe --json (configHash, promptHash, schemaHash), stored ExecutionRecords (steps[].decision, inputHash, toolCalls), and EvalRunReport artifacts to highlight DecidedBy.Model routers whose decisions are invariant per inputHash and @Agent steps with deterministic tool sequences or low pass^k. See Evaluation, Golden Sets & A/B Profiles for full details on @EvalSuite, pass@k / pass^k, and CI regression gates.