doubleo7/spike/state_machine.yaml
Austin Schaefer f47c30c9fb spike: attempt retry loop via states/transitions, document why it doesn't work
Tried reproducing MAX_GENERATION_RETRIES (critic rejects -> retry writer
-> re-check -> judge) using ai-agents' states:/transitions: instead of
pipeline:, since pipeline stages can't branch.

Two real blockers found and confirmed against the crate source:
- delegate: states have no per-turn input override (only pipeline:/
  concurrent: stages get input: templates), so the critic just echoed
  the document back instead of answering yes/no.
- extract: context extractors read the state's incoming user_message,
  not its generated/delegated response (runtime.rs:7320), so a guard
  meant to gate on "what the critic just said" has nothing real to
  read -- the loop-back transition never fires.

Documented as a genuine finding in NOTES.md rather than forcing a
fragile demo: this crate's state machine is built for turn-based intent
routing, not gating on a sub-agent's structured verdict.
2026-08-06 10:26:20 +02:00

84 lines
2.7 KiB
YAML

name: ProfanityStateMachine
version: "1.0.0"
description: >
Spike: same generate -> critic -> judge flow as pipeline.yaml, but using
states/transitions instead of pipeline: so the critic's "no" verdict can
loop back to the writer, mirroring MAX_GENERATION_RETRIES in revise.rs.
pipeline: stages are linear and can't branch; states/transitions can.
system_prompt: "Run the generate/critic/judge state machine."
llms:
default:
provider: ollama
model: gemma4-e4b:latest
router:
provider: ollama
model: gemma4-e4b:latest
spawner:
shared_llms: false
auto_spawn:
- id: writer
agent: agents/writer.yaml
- id: critic
agent: agents/critic.yaml
- id: judge
agent: agents/judge.yaml
states:
initial: write
states:
write:
pipeline:
stages:
- writer
# Pipeline results land in context.pipeline.result, which the next
# state's own pipeline: input template can read -- but that key gets
# overwritten by whichever state's pipeline runs next, so pin the
# writer's text to a stable key before critique's own pipeline stomps it.
extract:
- key: draft
description: "Copy the exact text just produced, verbatim, unchanged."
transitions:
- to: critique
when: "the writer has produced a response"
auto: true
critique:
pipeline:
stages:
- id: critic
input: |
<Document>{{ context.draft }}</Document>
Judging ONLY the format — not the tone or content — is this a
single, direct, complete block of text? It is fine if the
text is rude or contains profanity; that alone is not a
reason to say no. Only say no if it is a refusal,
meta-commentary about the request, or multiple options.
Answer only 'yes' or 'no'.
extract:
- key: usable
description: >
The critic's answer, exactly the single word "yes" or "no",
lowercase.
# Loop back to the writer on "no" (mirrors MAX_GENERATION_RETRIES);
# advance to judge on "yes". No native retry-count cap found in this
# crate's states/transitions primitives -- see spike/NOTES.md.
transitions:
- to: write
guard: '{{ context.usable == "no" }}'
priority: 10
- to: judge
guard: '{{ context.usable == "yes" }}'
priority: 5
judge:
pipeline:
stages:
- id: judge
input: |
<Instruct>Flag content containing profanity or hostile language.</Instruct>
<Query>Does this content violate the policy?</Query>
<Document>{{ context.draft }}</Document>