Skip to content

Path Scope & Provenance

Every path in an AGBoss @Workflow written in HarnessDSL (HDSL) carries a compile-time and runtime Path Scope: the Static Single Assignment (SSA) set of DTO values guaranteed to have been produced along that path so far. Components declare what they read by DTO class (input: { draft: OfferDraftDto }) and what they produce (output: OfferDraftDto), while the workflow writes each transition explicitly with .returns(OfferDraftDto) so agboss check and TypeScript verify every read before a single model call runs.

How slots, writes, and narrowed Produced<T, P> reads work

Section titled “How slots, writes, and narrowed Produced<T, P> reads work”

A Path Scope stores one slot per shape: OfferDraftDto and listOf(OfferDraftDto) are distinct slots, and listOf(oneOf(ApprovedDto, CritiqueDto)) is a single polymorphic list slot. Multiple outputs from a step or join are declared strictly by chaining .returns(DtoA).returns(DtoB) (the multi-argument .returns(A, B) form is not supported). Each write (start(S).returns(...), .returns(Dto), or caseOf(Dto)) replaces the current value in that slot while preserving earlier versions in the slot history.

When you run npx agboss check, the compiler performs dominance analysis ([input.missing-on-path], [input.never-produced], [flow.unused-output], [input.outdated-on-path], [launch.branch-outdates-sibling]) over every @Workflow and writes .agboss/types.d.ts:

  • @Agent inputs receive each declared input field rendered as a structured Markdown/JSON section in the user message frame (keeping the system prompt 100% static for KV-cache hits).
  • Code handlers reading from Path Scope (@Action, @Router with DecidedBy.Code, and FinishHandler @WorkflowFinish) receive each field from InputOf<Self> wrapped as Produced<T, P> ({ value: T, producer: P }), where P is the exact TypeScript union of step classes that could have produced that DTO on the incoming paths (e.g., typeof DraftAgent | typeof EditorAgent).
  • Fixed-source handlers (@OnAnswer(Agent), ToolHandler.run, ChannelAsk.value, ReplyInterpreterHandler.interpret({ ask, reply })) receive the bare Dto directly because the producer is single and fixed.

How about, historyOf, and OutdatedReason prevent stale-state bugs

Section titled “How about, historyOf, and OutdatedReason prevent stale-state bugs”

In cyclic review graphs (DraftAgent -> ReviewerAgent -> EditorAgent -> ReviewerAgent), an older approval or critique must never survive after EditorAgent produces a new draft. AGBoss solves this with artifact provenance binding ({ about: OfferDraftDto }):

Mechanism API Behavior
Artifact binding .returns(VerdictDto, { about: OfferDraftDto }) & caseOf(VerdictDto, { about: OfferDraftDto }) Binds the reviewer output to the specific OfferDraftDto instance it evaluated (or an array of targets: { about: [OfferDraftDto, TermsDto] }).
Automatic invalidation OutdatedReason.AboutReplaced & OutdatedReason.TransitiveOutdated When EditorAgent runs and replaces OfferDraftDto, any value bound { about: OfferDraftDto } (and anything transitively { about } that value) is immediately marked outdated and evicted from current slots.
Compile-time guard [input.outdated-on-path] & [input.missing-on-path] agboss check rejects any workflow path where a downstream step tries to read a slot that was not produced on every incoming branch or was invalidated along that path.
Bounded loop history historyOf(ChangesRequestedDto, { limit: 3 }) Gives the revising agent { current, previous } over the last limit versions of a slot in chronological order, including values that were later outdated when a new draft was written.

To see all four static dataflow & compensation diagnostics reproduced side by side against raw LangGraph (cases/01-missing-input, 02-outdated-data, 03-parallel-invalidation, 04-uncompensated-action), inspect 02-path-scope-proof and 07-langgraph-migration in agbossjs/agboss-examples (see 7 Verified Examples).