Optimization Cycle

This guide walks you through the Build → Run → Analyze → Update optimization cycle — the core workflow for iteratively improving your Yagra workflows using AI agents.

Overview

Yagra’s optimization cycle closes the loop between workflow execution and workflow improvement:

┌─────────────────────────────────────────────────────────┐
│                  Optimization Cycle                      │
│                                                          │
│  1. Build     →  Define workflow in YAML                 │
│  2. Run       →  Execute and capture traces              │
│  3. Analyze   →  Identify improvement opportunities      │
│  4. Propose   →  Generate a YAML diff                    │
│  5. Test      →  Validate with golden cases              │
│  6. Apply     →  Commit the change (or rollback)         │
│                                                          │
│       ↑                                     │            │
│       └─────────── iterate ─────────────────┘            │
└─────────────────────────────────────────────────────────┘

All steps run locally — no external SaaS or cloud service required.

Prerequisites

Install Yagra with LLM and MCP support:

uv add 'yagra[llm,mcp]'

Verify the installation:

yagra --help

Set your LLM API key (example: OpenAI):

export OPENAI_API_KEY=sk-...

Step 1: Build — Define Your Workflow

Create a workflow YAML. Use a template to get started quickly:

yagra init --template branch --output my-workflow
cd my-workflow

Validate before proceeding:

yagra validate --workflow workflow.yaml

Expected output:

✓ ワークフローは valid です。

See Workflow YAML Reference for the full schema.

Step 2: Run & Observe — Capture Traces

Enable observability when building the graph, then invoke it with trace=True to persist the trace to disk:

from yagra import Yagra
from my_handlers import registry, AgentState

app = Yagra.from_workflow(
    workflow_path="workflow.yaml",
    registry=registry,
    state_schema=AgentState,
    observability=True,
)

result = app.invoke({"query": "What is the refund policy?"}, trace=True)
print(result)

Traces are saved under .yagra/traces/<workflow_name>/ as JSON files:

.yagra/traces/my-workflow/my-workflow_20260222T120000_a1b2c3d4.json

You can also retrieve the latest trace in memory without file I/O:

last_trace = app.get_last_trace()  # WorkflowRunTrace | None

Step 3: Analyze — Identify Improvement Opportunities

Use the MCP tools to analyze accumulated traces. Start the MCP server:

yagra mcp

Then call analyze_traces from your AI agent or MCP client:

{
  "tool": "analyze_traces",
  "arguments": {
    "workflow_name": "my-workflow",
    "limit": 20
  }
}

Example response:

{
  "workflow_name": "my-workflow",
  "total_traces": 20,
  "node_summaries": [
    {
      "node_id": "classifier",
      "avg_latency_ms": 342,
      "error_rate": 0.0
    },
    {
      "node_id": "answer_faq",
      "avg_latency_ms": 1820,
      "error_rate": 0.05,
      "notes": "5% of responses were empty — likely a prompt gap"
    }
  ],
  "suggestions": [
    "answer_faq: add fallback instructions to the system prompt"
  ]
}

To inspect individual traces, call get_traces:

{
  "tool": "get_traces",
  "arguments": {
    "workflow_name": "my-workflow",
    "limit": 5
  }
}

Step 4: Propose & Review — Generate a YAML Diff

The propose_update tool takes the full candidate YAML you want to apply, not a natural-language instruction. The agent is responsible for generating the improved YAML first (e.g., by editing the prompt file or modifying the workflow), then submitting it for preview.

{
  "tool": "propose_update",
  "arguments": {
    "workflow_path": "workflow.yaml",
    "candidate_yaml": "version: \"1.0\"\nstart_at: classifier\n...",
    "reason": "Add fallback instruction to answer_faq's system prompt"
  }
}

Parameters:

  • workflow_path (required): path to the current workflow YAML

  • candidate_yaml (required): full YAML content to apply

  • reason (optional): human-readable explanation of the change

Example response:

{
  "workflow_path": "workflow.yaml",
  "is_valid": true,
  "issues": [],
  "diff": "--- a/workflow.yaml\n+++ b/workflow.yaml\n@@ -12,6 +12,8 @@\n     params:\n       prompt_ref: \"../prompts/support_prompts.yaml#faq\"\n+      fallback_message: \"I'm sorry, I don't have information on that topic. Please contact support.\"\n       model:\n         provider: openai",
  "reason": "Add fallback instruction to answer_faq's system prompt",
  "current_yaml": "...",
  "candidate_yaml": "..."
}

Review the diff and is_valid fields before applying. If is_valid is false, fix the candidate YAML and call propose_update again.

Step 5: Regression Test — Validate with Golden Cases

Before applying the change, verify that existing behavior is preserved using golden cases.

Save a golden case from a successful trace

yagra golden save \
  --trace .yagra/traces/my-workflow/my-workflow_20260222T120000_a1b2c3d4.json \
  --name happy-path

Run golden tests via CLI

yagra golden test --workflow workflow.yaml

Example output:

Running golden tests for workflow.yaml
  ✓ happy-path  (2/2 nodes passed)

Results: 1 passed, 0 failed

Run golden tests via MCP tool

{
  "tool": "run_golden_tests",
  "arguments": {
    "workflow_path": "workflow.yaml"
  }
}

Example response:

{
  "results": [
    {
      "case_name": "happy-path",
      "passed": true,
      "execution_path_match": true,
      "node_results": [
        {"node_id": "classifier", "status": "pass", "strategy_used": "exact"},
        {"node_id": "answer_faq", "status": "pass", "strategy_used": "structural"}
      ],
      "summary": "All 2 nodes passed"
    }
  ],
  "total": 1,
  "passed": 1,
  "failed": 0
}

If any test fails, revise the proposal before applying.

Step 6: Apply or Rollback — Commit the Change

Apply the proposal

Once golden tests pass, apply the change by passing the same candidate_yaml you previewed:

{
  "tool": "apply_update",
  "arguments": {
    "workflow_path": "workflow.yaml",
    "candidate_yaml": "version: \"1.0\"\nstart_at: classifier\n..."
  }
}

Parameters:

  • workflow_path (required): path to the workflow YAML to update

  • candidate_yaml (required): the new YAML content to write

  • base_revision (optional): expected current revision for conflict detection

  • backup_dir (optional): backup directory (default: .yagra/backups)

The workflow YAML is updated in place. A backup is created automatically and a backup_id is returned.

Example response:

{
  "success": true,
  "workflow_path": "workflow.yaml",
  "backup_id": "workflow_20260222T130000_a1b2c3d4",
  "saved_revision": "sha256:abcd1234..."
}

Rollback if needed

If the applied change causes issues, roll back using the backup_id from the apply_update response:

{
  "tool": "rollback_update",
  "arguments": {
    "workflow_path": "workflow.yaml",
    "backup_id": "workflow_20260222T130000_a1b2c3d4"
  }
}

Parameters:

  • workflow_path (required): path to the workflow YAML to restore

  • backup_id (required): backup ID returned by apply_update

  • backup_dir (optional): backup directory (default: .yagra/backups)

The workflow YAML is restored to the backed-up version. The current state is saved as a safety backup before overwriting.


Worked Example: Improving a Translation Workflow

This end-to-end example shows how to improve a simple translation workflow step by step.

Initial Setup

Create the workflow directory:

mkdir translate-workflow && cd translate-workflow

Create workflow.yaml:

version: "1.0"
start_at: translate
end_at:
  - finish
nodes:
  - id: translate
    handler: llm
    params:
      prompt_ref: "prompts/translate_prompts.yaml#translate"
      output_key: translation
      model:
        provider: openai
        name: gpt-4.1-mini
  - id: finish
    handler: passthrough  # user-defined no-op handler (see handlers.py below)
edges:
  - source: translate
    target: finish

Create prompts/translate_prompts.yaml:

translate:
  system: "You are a translator."
  user: "Translate the following text to English:\n\n{text}"

Create handlers.py:

from typing import TypedDict
from yagra import Yagra
from yagra.handlers import create_llm_handler


class TranslateState(TypedDict, total=False):
    text: str
    translation: str


def passthrough(state: TranslateState, params: dict) -> dict:
    return {}


registry = {
    "llm": create_llm_handler(),
    "passthrough": passthrough,
}

Validate:

yagra validate --workflow workflow.yaml
# ✓ ワークフローは valid です。

Run & Capture Traces

Create run.py:

from handlers import registry, TranslateState
from yagra import Yagra

app = Yagra.from_workflow(
    workflow_path="workflow.yaml",
    registry=registry,
    state_schema=TranslateState,
    observability=True,
)

test_inputs = [
    {"text": "こんにちは、世界!"},
    {"text": "今日は天気がいいですね。"},
    {"text": ""},  # edge case: empty input
]

for inp in test_inputs:
    app.invoke(inp, trace=True)

print("Traces saved to .yagra/traces/")
python run.py

Analyze Traces

Start MCP server in a separate terminal:

yagra mcp

Call analyze_traces from your agent:

{
  "tool": "analyze_traces",
  "arguments": {"workflow_name": "workflow", "limit": 10}
}

The agent finds that the translate node returns an empty string for the empty-input case. Suggestion: add input validation instructions to the system prompt.

Save a Golden Case

yagra golden save \
  --trace .yagra/traces/workflow/workflow_20260222T120000_a1b2c3d4.json \
  --name normal-translation \
  --strategy translate:structural

Propose an Update

The agent first edits prompts/translate_prompts.yaml to improve the system prompt:

translate:
  system: |
    You are a professional translator.
    If the input text is empty, return an empty string without any additional message.
  user: "Translate the following text to English:\n\n{text}"

Then submits the updated workflow.yaml (which references the prompt file) to propose_update for preview:

{
  "tool": "propose_update",
  "arguments": {
    "workflow_path": "workflow.yaml",
    "candidate_yaml": "version: \"1.0\"\nstart_at: translate\nend_at:\n  - finish\nnodes:\n  - id: translate\n    handler: llm\n    params:\n      prompt_ref: \"prompts/translate_prompts.yaml#translate\"\n      output_key: translation\n      model:\n        provider: openai\n        name: gpt-4.1-mini\n  - id: finish\n    handler: passthrough\nedges:\n  - source: translate\n    target: finish\n",
    "reason": "Update translate node's system prompt to handle empty input gracefully"
  }
}

Review the diff in the response and confirm is_valid: true. Note the candidate_yaml — you’ll pass it to apply_update after golden tests pass.

Run Golden Tests

yagra golden test --workflow workflow.yaml
# ✓ normal-translation  (1/1 nodes passed)

All tests pass.

Apply the Change

{
  "tool": "apply_update",
  "arguments": {
    "workflow_path": "workflow.yaml",
    "candidate_yaml": "version: \"1.0\"\nstart_at: translate\n..."
  }
}

The improved workflow is now live. The response includes a backup_id you can use with rollback_update if needed. Run more test cases and repeat the cycle as needed.


Agent Automation

For AI coding agents (Claude Code, Cursor, Copilot, etc.), you can automate the entire cycle using Yagra’s MCP tools. The recommended tool call sequence is:

analyze_traces        → identify problems in recent executions
propose_update        → generate a YAML diff
run_golden_tests      → verify existing behavior is preserved
apply_update          → commit the improvement
  (or rollback_update if tests failed)

For system prompt examples and a full automation guide, see: Agent Integration Guide

Next Steps