Your first flow
One writer → reviewer flow, run end to end on your machine, with no model key.
- Python 3.12+ and
uv. git.- nothing else: the run uses the dry harness and needs no model key.
- a folder with the writer → reviewer flow of scenario S1.
- a configuration that validates.
- a finished ticket: the writer writes, the reviewer checks, you approve.
Steps
-
Clone the repository, install its dependencies and put
afeon the path:git clone <repository-url> afecd afeuv syncsource .venv/bin/activate -
Create a folder for the tutorial and a
config/folder inside it:mkdir -p afe-first-flow/config/promptscd afe-first-flow -
Write the flow,
config/flow.yaml. It is a writer → reviewer loop. The writer produces thetext. The reviewer answersokorko. Akosends the text back as feedback, and theapprovehuman node ends the flow:config/flow.yaml# yaml-language-server: $schema=../../../../schemas/afe.dev-v1alpha1.jsonapiVersion: afe.dev/v1alpha1kind: Flowmetadata:name: writer-reviewerannotations:afe.dev/description: Scenario S1, a text written, reviewed and approvedlabels:scenario: S1spec:input:artifact: text-requestbudget:costUsd: 0.5wallTime: 30mnodes:writer:type: agentagent: writerreviewer:type: agentagent: reviewerapprove:type: humanactions:- approve- rejectrequireComment:- rejectwrites: decisionmessage: Publish "{text.title}"?edges:- from: STARTto: writer- from: writerto: reviewer- from: reviewerwhen: review.outcome == 'ko'to: writerfeedback: truemaxRounds: 2- from: reviewerto: approve- from: approvewhen: decision.action == 'reject'to: writernewRound: truemaxRounds: 2- from: approveto: END -
Write the two agents,
config/agents.yaml:config/agents.yaml# yaml-language-server: $schema=../../../../schemas/afe.dev-v1alpha1.jsonapiVersion: afe.dev/v1alpha1kind: Agentmetadata:name: writerspec:model: cheapsystemPromptFile: prompts/writer.mdreads:- text-request- review?- decision?writes: textbudget:tokens: 20000costUsd: 0.1wallTime: 5m---apiVersion: afe.dev/v1alpha1kind: Agentmetadata:name: reviewerspec:model: cheapsystemPromptFile: prompts/reviewer.mdreads:- text-request- textwrites: reviewbudget:tokens: 20000toolCalls: 3costUsd: 0.1wallTime: 5mlimits:maxSubmitRetries: 2 -
Write the artifacts,
config/schemas.yaml:config/schemas.yaml# yaml-language-server: $schema=../../../../schemas/afe.dev-v1alpha1.jsonapiVersion: afe.dev/v1alpha1kind: Schemametadata:name: text-requestspec:fields:topic:type: stringlength:type: enum[short|medium|long]---apiVersion: afe.dev/v1alpha1kind: Schemametadata:name: textspec:fields:title:type: stringbody:type: string---apiVersion: afe.dev/v1alpha1kind: Schemametadata:name: reviewspec:fields:outcome:type: enum[ok|ko]notes:type: list[string]optional: true---apiVersion: afe.dev/v1alpha1kind: Schemametadata:name: decisionspec:fields:action:type: enum[approve|reject]comment:type: stringoptional: true -
Write the model alias,
config/models.yaml. The dry harness never calls it, so the key is not needed today:config/models.yaml# yaml-language-server: $schema=../../../../schemas/afe.dev-v1alpha1.jsonapiVersion: afe.dev/v1alpha1kind: Modelmetadata:name: cheapannotations:afe.dev/description: A cheap model for drafts and reviewsspec:provider: openroutermodel: deepseek/deepseek-chatapiKeyEnv: OPENROUTER_API_KEY -
Write the two prompts,
config/prompts/writer.mdandconfig/prompts/reviewer.md:config/prompts/writer.mdWrite the text the request asks for. If a review or a rejection is present, address it.config/prompts/reviewer.mdCheck the text against the request. Answer ok, or ko with concrete notes. -
Write the ticket input,
request.yaml, next toconfig/:request.yamltopic: beeslength: short -
Validate the configuration:
afe validate -c config/It prints
8 manifests, no errors. -
Break a reference to see the error, then fix it. Change
agent: reviewertoagent: reviewrinconfig/flow.yamland validate again. The report names the unknown Agent and the pathspec.nodes.reviewer.agent. Putreviewerback. -
Run the flow with the dry harness:
afe run writer-reviewer -i request.yaml -c config/ --local --dry -
Answer the approval at the prompt: type
approveand press Enter twice. The events scroll by and the run ends withT-0001 ticket_done.
The --local engine keeps nothing after the command ends. The printed events are the whole
result: nothing is left to inspect afterwards.
Now read In the web UI to run the same flow through the server. The next tutorial, On a real model, calls a real model.
Troubleshooting
Symptom: the shell prints afe: command not found.
Cause: the virtual environment from step 1 is not active in this shell.
Fix: run source .venv/bin/activate again. It does not carry over to a new terminal tab.
Symptom: afe run exits at once with code 2 and prints no prompt.
Cause: the command has no terminal attached, for example inside a script or a CI job.
Fix: use the afe resume command it prints instead, with the action you want to answer.