Documentation
¶
Overview ¶
Pattern derivato da google/adk-go v1.4.0 agent/workflowagents/loopagent/agent.go (Apache 2.0). Adattato per Aura con SC#2 budget exhaustion + SC#3 child-inherits-remaining + SC#4 UUIDv7 OTel-compat.
Aura divergences from adk's LoopAgent control flow:
- adk LoopAgent has NO budget; Aura threads ic.Budget through workflow-owned steps (tool calls for non-budget-owning children, plus empty-pass charges) so a runaway sub is bounded (SC#2).
- termination on budget exhaustion is signalled by an EXPLICIT Event (Actions.Escalate + StateDelta), NEVER through the iter error slot (D-04).
- two-phase dedup: BeforeToolCall blocks a repeating tool call before its side effect re-runs, AfterToolResult records a bounded result preview as a progress veto (D-18); the CALLER canonicalizes args (B2).
- subs run under ic.WithSubAgent (the SAME dedup ring across iterations, D-09) so cross-iteration repeats are visible; Branch gains .iter-<N> per pass (D-15).
Pattern derivato da google/adk-go v1.4.0 agent/workflowagents/parallelagent/agent.go (Apache 2.0). Adattato per Aura con SC#2 budget exhaustion + SC#3 child-inherits-remaining + SC#4 UUIDv7 OTel-compat.
The channel choreography (errgroup fan-out + per-event ack backpressure + a fan-in results channel yielded SERIALLY from the iterator frame) is stolen near-verbatim from adk's parallelagent. Two behaviours DIVERGE from adk, both marked inline below:
- D-03: escalate from any child fires a captured context.CancelFunc to stop siblings — NOT a fake sentinel error. A fake error would pollute errgroup.Wait() and surface through the iter error slot, which D-04 reserves for REAL LLM/tool failures only.
- D-05: a sibling cancelled by that escalate (or by a real first-error) drains by returning nil, NOT ctx.Err() (adk yields ctx.Err() at agent.go:142). Intentional cancellation is not error noise, so the error slot stays clean.
Concurrency invariants (D-22/D-23): the iterator NEVER yields from a spawned goroutine — children fan-in to `results` and the iterator frame yields each pair serially (footgun 3). Every channel op in runSub is a multi-arm select with a `case <-done`/`case <-ctx.Done()` exit so a consumer break-early leaks no goroutine; the frame runs `defer cancel()` + `defer close(done)`; the spawn loop is guarded by `if egCtx.Err() != nil` (Go #61611).
Budget (D-09/D-10): each child forks ic.Budget.Child(len(subs)) — a DISTINCT dedup ring (no cross-branch false positives) that still shares the one *atomic.Int32 step counter by pointer. That shared pointer is what makes SC#3 true: a depth-3 fan-3 tree consumes ≤ max_steps total, NOT max_steps³.
Package workflow holds the deterministic, non-concurrent orchestrators (SequentialAgent, LoopAgent) plus the concurrent ParallelAgent (Plan 06). They compose the cornerstone agent.Agent interface: constructors return the interface (factory pattern, like net.Pipe / context.WithCancel) so trees nest cleanly, while the structs stay exported for the compile-time satisfaction asserts the SPEC wants (D-02).
Pattern derivato da google/adk-go v1.4.0 agent/workflowagents/sequentialagent/agent.go (Apache 2.0). Adattato per Aura con SC#2 budget exhaustion + SC#3 child-inherits-remaining + SC#4 UUIDv7 OTel-compat.
Aura divergences from adk's sequential control flow:
- adk seals its Agent interface and reads SubAgents() off the InvocationContext agent; Aura's interface is open (D-01) and SequentialAgent holds subs directly.
- early-return on a sub Escalate event (Req#4) — adk's sequentialAgent has no escalate short-circuit; Aura adds it so a sub can stop the chain.
- each sub runs under ic.WithSubAgent(sub), sharing the SAME Budget + dedup ring (one logical reasoning thread, D-09); Branch is dot-joined per child (D-15).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewLoop ¶
NewLoop returns an agent.Agent (the interface, D-02 factory). maxIter==0 means "iterate until escalate or budget exhaustion" (no iteration ceiling). The typed-nil guard (D-02) is implicit: a non-nil *LoopAgent is always boxed.
func NewParallel ¶
NewParallel returns an agent.Agent (the interface, D-02 factory) that runs subs concurrently. The typed-nil guard (D-02) is implicit: a non-nil *ParallelAgent is always boxed in the interface, never a typed nil.
func NewSequential ¶
NewSequential returns an agent.Agent (the interface, D-02 factory) that runs subs once each in order. The typed-nil guard (D-02) is implicit: this always returns a non-nil *SequentialAgent boxed in the interface, never a typed nil.
Types ¶
type LoopAgent ¶
type LoopAgent struct {
// contains filtered or unexported fields
}
LoopAgent re-runs its sub-agents until one of three terminal conditions fires: (a) maxIterations passes completed, (b) a sub emits Actions.Escalate=true, or (c) the shared Budget is exhausted (hard max_steps/wallclock) or a tool call is detected as a dedup loop. On (c) it emits the explicit budget-exhausted Event (SC#2 shape). Exported per D-02; construct via NewLoop.
func (*LoopAgent) Description ¶
Description is the human/LLM-facing one-liner.
func (*LoopAgent) FindAgent ¶
FindAgent returns self when name matches, else recurses into the subs (D-01).
func (*LoopAgent) Run ¶
Run drives the iteration loop. Workflow-owned children spend budget on tool calls and each consumed tool call is surfaced as exactly one step Event (WR-05). If a non-budget-owning child completes a pass without any tool-call step, the loop charges one workflow step so maxIter=0 cannot hot-spin forever. Budget-owning children are observed without parent-side charging, preserving the single-owner contract for composed trees. Every yield is guarded (D-22); sub escalations are yielded before return (D-21).
type ParallelAgent ¶
type ParallelAgent struct {
// contains filtered or unexported fields
}
ParallelAgent runs its sub-agents concurrently, fans their Events into a single channel, and yields each (Event, error) serially from the iterator frame. An Escalate Event from any child cancels the siblings (D-03); a real failure from any child surfaces once through the error slot (D-04). Exported per D-02; construct via NewParallel.
func (*ParallelAgent) Description ¶
func (*ParallelAgent) Description() string
Description is the human/LLM-facing one-liner.
func (*ParallelAgent) FindAgent ¶
func (a *ParallelAgent) FindAgent(name string) agent.Agent
FindAgent returns self when name matches, else recurses into the subs (D-01).
func (*ParallelAgent) Name ¶
func (a *ParallelAgent) Name() string
Name is the Event Author / FindAgent key for this orchestrator.
func (*ParallelAgent) Run ¶
func (a *ParallelAgent) Run(ic agent.InvocationContext) iter.Seq2[*agent.Event, error]
Run fans the subs out under one errgroup, fans their Events into `results`, and yields each pair serially from this frame. It steals adk's channel structure with the two documented divergences (D-03 captured-cancel, D-05 drain-nil).
func (*ParallelAgent) SubAgents ¶
func (a *ParallelAgent) SubAgents() []agent.Agent
SubAgents returns the direct children in declaration order.
type SequentialAgent ¶
type SequentialAgent struct {
// contains filtered or unexported fields
}
SequentialAgent runs its sub-agents exactly once each, in declaration order, streaming every sub Event upward. It returns early the moment a sub emits an Event with Actions.Escalate=true: that sub's escalate Event is yielded, then no further sub is invoked (Req#4). Exported per D-02; construct via NewSequential.
func (*SequentialAgent) Description ¶
func (*SequentialAgent) Description() string
Description is the human/LLM-facing one-liner.
func (*SequentialAgent) FindAgent ¶
func (a *SequentialAgent) FindAgent(name string) agent.Agent
FindAgent returns self when name matches, else recurses into the subs (D-01 FindAgent contract); nil when not found anywhere in the subtree.
func (*SequentialAgent) Name ¶
func (a *SequentialAgent) Name() string
Name is the Event Author / FindAgent key for this orchestrator.
func (*SequentialAgent) Run ¶
func (a *SequentialAgent) Run(ic agent.InvocationContext) iter.Seq2[*agent.Event, error]
Run iterates the subs once each in order. Each sub runs under ic.WithSubAgent(sub) (shared budget + dedup ring, D-09) with the Branch dot-joined to <branch>.<childName> (D-15). Every yield is guarded (if !yield(...) { return }, D-22 footgun 2). When a sub emits an escalate Event the iterator yields it and returns without invoking later subs (Req#4).
func (*SequentialAgent) SubAgents ¶
func (a *SequentialAgent) SubAgents() []agent.Agent
SubAgents returns the direct children in declaration order.