The GoalSegment Schema
The typed group contract — new this session. It is the data structure every later phase of the goal hierarchy consumes: build-time gates, group metrics, and the deterministic selector.
What & why
Today a “group” is only a measurement aggregate — an inferred
segment label on a state. To make a group a
goal-directed unit (one that owns a typed objective and can be deterministically driven), it
needs a first-class, authored contract. GoalSegment
(riff/types.py, commit 23432c5) is that contract. It is phase 3 of the
goal-hierarchy design.
segments: block has none, and nothing changes — the common case today.
The schema is the foundation later phases build on; it does not alter the runtime of existing flows.
The schema
GoalSegment is a frozen dataclass. The complex fields are stored as tuples-of-pairs or
JSON strings so the dataclass stays frozen-hashable, with helper properties to decode them.
| Field | Meaning |
|---|---|
name | the segment's stable id |
kind | the typed objective: collect | confirm | act | terminal | handoff |
purpose | one-line statement of the group's goal |
members | the member state ids that form this group |
target_slots | the contract: ((slot, json_spec), …) — each spec carries required, validator, confirmation |
entry_guard / exit_guard | guard expressions that prove entry/exit conditions |
ordering_json | {preferred_order, allow_bundle, max_new_slots_per_turn} |
repair_policy_json | {invalid, low_confidence, max_attempts_per_slot, fallback_state, …} |
Three helper properties decode the packed fields: .target (the slot contract dict),
.repair_policy, and .ordering.
How it's authored (YAML)
The loader (riff/loader.py) parses an optional top-level segments: mapping
into the frozen tuple on the Flow. Absent → empty tuple.
# flows/<flow>.yaml
segments:
collect_customer:
kind: collect
purpose: collect caller identity and service location
members: [ask_contact_bundle, repair_phone]
entry_guard: 'intent in ["book", "request_service"]'
exit_guard: all_required_slots_valid
target_slots:
name: { required: true, validator: person_name, confirmation: implicit }
phone: { required: true, validator: us_phone, confirmation: explicit_if_asr_low }
address: { required: true, validator: serviceable_address, confirmation: explicit }
ordering: { preferred_order: [name, phone, address], allow_bundle: true, max_new_slots_per_turn: 2 }
repair_policy: { invalid: ask_repair, max_attempts_per_slot: 2, fallback_state: collect_customer_failed }
What consumes it
| Phase | Reads | Status |
|---|---|---|
| Build-time gates (4) | target_slots typed, exit_guard proves the contract, fallback_state reachable, completion_slots covered | planned, runs in lint/load + pre-push |
| Group metrics (5) | kind's typed goal — slot_yield for collect, confirmation_success for confirm, … | cohesion proxy shipped |
| GOAP-lite selector (7) | target_slots + ordering to pick the next member deterministically | planned, behind a flag |
repair_policy.fallback_state is reached via the shipped stalled-guard +
sets: recovery primitive (commit e22399d) — the deterministic way to
handle a withholding caller. See Stall recovery.
Use case
An author writing a new booking flow declares its collect phase as a GoalSegment with
three target slots and an exit guard. From that one declaration: the build-time gate verifies every
target slot has a collecting state and the fallback is reachable; the group metric scores
slot_yield against the declared contract; and (once enabled) the selector drives the
members deterministically. The goal and its metric exist from the first commit, because the group
is the goal.
Example
# load a flow and inspect its segments programmatically
python -c "from riff.loader import load_flow; \
f = load_flow('flows/austin_plumbing.yaml'); \
print([(s.name, s.kind, list(s.target)) for s in f.segments])"
(For flows without a segments: block this prints [] — the inferred
segment labels used by the metrics still come from segment
inference.)
Where it fits
GoalSegment is the bridge between the goal-hierarchy
design and the running engine. It turns the middle tier from a label into a contract — the
prerequisite for build-time gates, contract-backed group metrics, and the deterministic member
selector that keeps the LLM language-only.