workflow

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 8 Imported by: 0

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

func NewLoop(name string, maxIter uint, subs ...agent.Agent) agent.Agent

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

func NewParallel(name string, subs ...agent.Agent) agent.Agent

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

func NewSequential(name string, subs ...agent.Agent) agent.Agent

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

func (*LoopAgent) Description() string

Description is the human/LLM-facing one-liner.

func (*LoopAgent) FindAgent

func (a *LoopAgent) FindAgent(name string) agent.Agent

FindAgent returns self when name matches, else recurses into the subs (D-01).

func (*LoopAgent) Name

func (a *LoopAgent) Name() string

Name is the Event Author / FindAgent key for this orchestrator.

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).

func (*LoopAgent) SubAgents

func (a *LoopAgent) SubAgents() []agent.Agent

SubAgents returns the direct children in declaration order.

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

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

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL