Skip to main content

Your first flow

One writer → reviewer flow, run end to end on your machine, with no model key.

You need
  • Python 3.12+ and uv.
  • git.
  • nothing else: the run uses the dry harness and needs no model key.
At the end you have
  • 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​

  1. Clone the repository, install its dependencies and put afe on the path:

    git clone <repository-url> afe
    cd afe
    uv sync
    source .venv/bin/activate
  2. Create a folder for the tutorial and a config/ folder inside it:

    mkdir -p afe-first-flow/config/prompts
    cd afe-first-flow
  3. Write the flow, config/flow.yaml. It is a writer → reviewer loop. The writer produces the text. The reviewer answers ok or ko. A ko sends the text back as feedback, and the approve human node ends the flow:

    config/flow.yaml
    # yaml-language-server: $schema=../../../../schemas/afe.dev-v1alpha1.json
    apiVersion: afe.dev/v1alpha1
    kind: Flow
    metadata:
    name: writer-reviewer
    annotations:
    afe.dev/description: Scenario S1, a text written, reviewed and approved
    labels:
    scenario: S1
    spec:
    input:
    artifact: text-request
    budget:
    costUsd: 0.5
    wallTime: 30m
    nodes:
    writer:
    type: agent
    agent: writer
    reviewer:
    type: agent
    agent: reviewer
    approve:
    type: human
    actions:
    - approve
    - reject
    requireComment:
    - reject
    writes: decision
    message: Publish "{text.title}"?
    edges:
    - from: START
    to: writer
    - from: writer
    to: reviewer
    - from: reviewer
    when: review.outcome == 'ko'
    to: writer
    feedback: true
    maxRounds: 2
    - from: reviewer
    to: approve
    - from: approve
    when: decision.action == 'reject'
    to: writer
    newRound: true
    maxRounds: 2
    - from: approve
    to: END
  4. Write the two agents, config/agents.yaml:

    config/agents.yaml
    # yaml-language-server: $schema=../../../../schemas/afe.dev-v1alpha1.json
    apiVersion: afe.dev/v1alpha1
    kind: Agent
    metadata:
    name: writer
    spec:
    model: cheap
    systemPromptFile: prompts/writer.md
    reads:
    - text-request
    - review?
    - decision?
    writes: text
    budget:
    tokens: 20000
    costUsd: 0.1
    wallTime: 5m
    ---
    apiVersion: afe.dev/v1alpha1
    kind: Agent
    metadata:
    name: reviewer
    spec:
    model: cheap
    systemPromptFile: prompts/reviewer.md
    reads:
    - text-request
    - text
    writes: review
    budget:
    tokens: 20000
    toolCalls: 3
    costUsd: 0.1
    wallTime: 5m
    limits:
    maxSubmitRetries: 2
  5. Write the artifacts, config/schemas.yaml:

    config/schemas.yaml
    # yaml-language-server: $schema=../../../../schemas/afe.dev-v1alpha1.json
    apiVersion: afe.dev/v1alpha1
    kind: Schema
    metadata:
    name: text-request
    spec:
    fields:
    topic:
    type: string
    length:
    type: enum[short|medium|long]
    ---
    apiVersion: afe.dev/v1alpha1
    kind: Schema
    metadata:
    name: text
    spec:
    fields:
    title:
    type: string
    body:
    type: string
    ---
    apiVersion: afe.dev/v1alpha1
    kind: Schema
    metadata:
    name: review
    spec:
    fields:
    outcome:
    type: enum[ok|ko]
    notes:
    type: list[string]
    optional: true
    ---
    apiVersion: afe.dev/v1alpha1
    kind: Schema
    metadata:
    name: decision
    spec:
    fields:
    action:
    type: enum[approve|reject]
    comment:
    type: string
    optional: true
  6. 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.json
    apiVersion: afe.dev/v1alpha1
    kind: Model
    metadata:
    name: cheap
    annotations:
    afe.dev/description: A cheap model for drafts and reviews
    spec:
    provider: openrouter
    model: deepseek/deepseek-chat
    apiKeyEnv: OPENROUTER_API_KEY
  7. Write the two prompts, config/prompts/writer.md and config/prompts/reviewer.md:

    config/prompts/writer.md
    Write the text the request asks for. If a review or a rejection is present, address it.
    config/prompts/reviewer.md
    Check the text against the request. Answer ok, or ko with concrete notes.
  8. Write the ticket input, request.yaml, next to config/:

    request.yaml
    topic: bees
    length: short
  9. Validate the configuration:

    afe validate -c config/

    It prints 8 manifests, no errors.

  10. Break a reference to see the error, then fix it. Change agent: reviewer to agent: reviewr in config/flow.yaml and validate again. The report names the unknown Agent and the path spec.nodes.reviewer.agent. Put reviewer back.

  11. Run the flow with the dry harness:

    afe run writer-reviewer -i request.yaml -c config/ --local --dry
  12. Answer the approval at the prompt: type approve and press Enter twice. The events scroll by and the run ends with T-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.

See also​