agent-yaml 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_yaml-0.1.0/LICENSE +21 -0
- agent_yaml-0.1.0/PKG-INFO +311 -0
- agent_yaml-0.1.0/README.md +271 -0
- agent_yaml-0.1.0/agent_yaml/__init__.py +8 -0
- agent_yaml-0.1.0/agent_yaml/adapters/__init__.py +95 -0
- agent_yaml-0.1.0/agent_yaml/adapters/base.py +55 -0
- agent_yaml-0.1.0/agent_yaml/adapters/claude.py +144 -0
- agent_yaml-0.1.0/agent_yaml/adapters/codex.py +112 -0
- agent_yaml-0.1.0/agent_yaml/adapters/gemini.py +141 -0
- agent_yaml-0.1.0/agent_yaml/adapters/opencode.py +147 -0
- agent_yaml-0.1.0/agent_yaml/adapters/qwen.py +160 -0
- agent_yaml-0.1.0/agent_yaml/cli.py +973 -0
- agent_yaml-0.1.0/agent_yaml/ensure.py +115 -0
- agent_yaml-0.1.0/agent_yaml/events.py +26 -0
- agent_yaml-0.1.0/agent_yaml/executor.py +868 -0
- agent_yaml-0.1.0/agent_yaml/graph.py +292 -0
- agent_yaml-0.1.0/agent_yaml/includes.py +222 -0
- agent_yaml-0.1.0/agent_yaml/loops.py +3252 -0
- agent_yaml-0.1.0/agent_yaml/parser.py +52 -0
- agent_yaml-0.1.0/agent_yaml/skip_conditions.py +287 -0
- agent_yaml-0.1.0/agent_yaml/task_validators.py +571 -0
- agent_yaml-0.1.0/agent_yaml/utils.py +338 -0
- agent_yaml-0.1.0/agent_yaml/validator.py +827 -0
- agent_yaml-0.1.0/agent_yaml/variables.py +330 -0
- agent_yaml-0.1.0/agent_yaml/web/__init__.py +1 -0
- agent_yaml-0.1.0/agent_yaml/web/app.py +281 -0
- agent_yaml-0.1.0/agent_yaml/web/db.py +413 -0
- agent_yaml-0.1.0/agent_yaml/web/runtime.py +207 -0
- agent_yaml-0.1.0/agent_yaml/web/scheduler.py +81 -0
- agent_yaml-0.1.0/agent_yaml/web/static/app.js +565 -0
- agent_yaml-0.1.0/agent_yaml/web/static/index.html +138 -0
- agent_yaml-0.1.0/agent_yaml/web/static/styles.css +814 -0
- agent_yaml-0.1.0/agent_yaml/web/static/webgl.js +636 -0
- agent_yaml-0.1.0/agent_yaml/when_conditions.py +127 -0
- agent_yaml-0.1.0/agent_yaml.egg-info/PKG-INFO +311 -0
- agent_yaml-0.1.0/agent_yaml.egg-info/SOURCES.txt +40 -0
- agent_yaml-0.1.0/agent_yaml.egg-info/dependency_links.txt +1 -0
- agent_yaml-0.1.0/agent_yaml.egg-info/entry_points.txt +2 -0
- agent_yaml-0.1.0/agent_yaml.egg-info/requires.txt +19 -0
- agent_yaml-0.1.0/agent_yaml.egg-info/top_level.txt +1 -0
- agent_yaml-0.1.0/pyproject.toml +146 -0
- agent_yaml-0.1.0/setup.cfg +4 -0
agent_yaml-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joe Schlesinger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-yaml
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Declarative YAML workflows for AI coding-agent CLIs
|
|
5
|
+
Author: Joe Schlesinger
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/roboalchemist/agent-yaml
|
|
8
|
+
Project-URL: Repository, https://github.com/roboalchemist/agent-yaml
|
|
9
|
+
Project-URL: Issues, https://github.com/roboalchemist/agent-yaml/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/roboalchemist/agent-yaml/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: ai,agents,orchestration,workflow,yaml,cli,llm
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: pyyaml>=6.0
|
|
25
|
+
Requires-Dist: jinja2>=3.0
|
|
26
|
+
Requires-Dist: fastapi>=0.110
|
|
27
|
+
Requires-Dist: uvicorn>=0.27
|
|
28
|
+
Requires-Dist: pydantic>=2.0
|
|
29
|
+
Requires-Dist: backports.zoneinfo>=0.2.1; python_version < "3.9"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-mock>=3.10; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
35
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
36
|
+
Provides-Extra: docs
|
|
37
|
+
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
38
|
+
Requires-Dist: mkdocs-material<10,>=9.7; extra == "docs"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# agent-yaml
|
|
42
|
+
|
|
43
|
+
Write out your workflows in yaml and then run them under a harness of your choice.
|
|
44
|
+
|
|
45
|
+
## Why it exists
|
|
46
|
+
|
|
47
|
+
Each model and framework provider is trying to provide a way to interact with their harness, without providing what is usually needed: a definable, repeatable flow-chart style workflow to accomplish some repeated task.
|
|
48
|
+
|
|
49
|
+
The yaml contains the prompts, where each loop gets its items, and the checks a step has to pass
|
|
50
|
+
before the next one starts. Run it again next month in the latest harness/model of the day and run the same workflow.
|
|
51
|
+
|
|
52
|
+
agent-yaml lets one define their workflows irregardless of the harness or model of the day, by breaking down what most boring workflows tend to be (or could most easily be) comprised of:
|
|
53
|
+
- a working directory
|
|
54
|
+
- prompts made with templates
|
|
55
|
+
- looping over something
|
|
56
|
+
- validating checks
|
|
57
|
+
- composable templating of the above
|
|
58
|
+
|
|
59
|
+
Each step is one subprocess call, with contracts between steps being files on the disk.
|
|
60
|
+
|
|
61
|
+
Model output stays opaque and is never parsed.
|
|
62
|
+
If you want to parse it, write it to a file and parse that.
|
|
63
|
+
Validators run against written files to form checkpoints.
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
No PyPI release yet. Install from the repository:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install "git+https://github.com/roboalchemist/agent-yaml.git"
|
|
71
|
+
agent-yaml --help
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Python 3.8+, tested on 3.9, 3.11 and 3.12, plus at least one supported agent CLI on `PATH`
|
|
75
|
+
(`opencode`, `claude`, `gemini`, `qwen`, or `codex`).
|
|
76
|
+
|
|
77
|
+
## Quick start
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
# hello.yaml
|
|
81
|
+
directory: /tmp/hello
|
|
82
|
+
cli-agent: opencode
|
|
83
|
+
cli-agent-config:
|
|
84
|
+
skip-confirmations: true
|
|
85
|
+
model: provider/model-name
|
|
86
|
+
|
|
87
|
+
task:
|
|
88
|
+
- write-file:
|
|
89
|
+
prompt: "Create hello.txt containing exactly: hello from agent-yaml"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
mkdir -p /tmp/hello
|
|
94
|
+
agent-yaml validate hello.yaml # parse and schema check, no agent calls
|
|
95
|
+
agent-yaml run hello.yaml --dry-run # print the exact command that would run
|
|
96
|
+
agent-yaml run hello.yaml # run it
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
A dry run for that file prints the command it would execute, including the translated flags, so you
|
|
100
|
+
can spot a wrong model or a rejected option before spending tokens:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
[DRY RUN] Task 'write-file' would execute:
|
|
104
|
+
Command: opencode run --standalone --auto -- Create hello.txt containing exactly: ...
|
|
105
|
+
Working directory: /tmp/hello
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## A real workflow
|
|
109
|
+
|
|
110
|
+
This is the shape of the workflows in `examples/` and `tasks/`: a CSV loop, a validator per item,
|
|
111
|
+
retries on validation failure, and a checkpoint so an interrupted run can continue.
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
directory: ~/projects/reports
|
|
115
|
+
cli-agent-config:
|
|
116
|
+
model: openai/gpt-5
|
|
117
|
+
|
|
118
|
+
task:
|
|
119
|
+
- summarize-rows:
|
|
120
|
+
loop:
|
|
121
|
+
type: csv
|
|
122
|
+
source: "{{directory}}/rows.csv"
|
|
123
|
+
workers: 4
|
|
124
|
+
retry: {max-attempts: 3, on-failure: retry}
|
|
125
|
+
checkpoint-file: "{{directory}}/state/summaries.json"
|
|
126
|
+
resume: true
|
|
127
|
+
prompt: |
|
|
128
|
+
Summarize row {{loop.index}}/{{loop.total}} ({{loop.item.name}}) and write
|
|
129
|
+
{{directory}}/out/{{loop.item.name}}.md
|
|
130
|
+
validate:
|
|
131
|
+
- file-not-empty: "{{directory}}/out/{{loop.item.name}}.md"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Run it, then rerun the same command after an interruption. Finished items are skipped.
|
|
135
|
+
|
|
136
|
+
## Primitives
|
|
137
|
+
|
|
138
|
+
A workflow is built from a fixed vocabulary. The forms are the same on every adapter, and only the
|
|
139
|
+
CLI-specific options change.
|
|
140
|
+
|
|
141
|
+
**Workflow fields**
|
|
142
|
+
|
|
143
|
+
| Field | Type | Notes |
|
|
144
|
+
|-------|------|-------|
|
|
145
|
+
| `directory` | string | Required. Working directory for every task |
|
|
146
|
+
| `cli-agent` | string | `opencode`, `claude`, `gemini`, `qwen`, `codex` (default `claude`) |
|
|
147
|
+
| `environment` | map | Env vars inherited by every task |
|
|
148
|
+
| `template-variables` | map | User variables, resolved in dependency order |
|
|
149
|
+
| `includes` | map | Reusable prompt fragments, inline or from a file |
|
|
150
|
+
| `ensure` | map | Pre-flight `tools` and `mcps` checks |
|
|
151
|
+
| `task` | list | The task tree (required) |
|
|
152
|
+
|
|
153
|
+
`cli-agent-config` holds adapter options: `model`, `timeout`, `skip-confirmations`,
|
|
154
|
+
`additional-dirs`, `mcp-config-file`, `raw-args`, and the per-adapter equivalents.
|
|
155
|
+
|
|
156
|
+
**Task fields**
|
|
157
|
+
|
|
158
|
+
| Field | Type | Notes |
|
|
159
|
+
|-------|------|-------|
|
|
160
|
+
| `prompt` | string | Text sent to the agent CLI |
|
|
161
|
+
| `task` | list or map | Nested tasks: a list runs in order, a map runs its keys concurrently |
|
|
162
|
+
| `include` | list | Names of top-level includes appended to the prompt |
|
|
163
|
+
| `loop` | map | Runs the task once per item |
|
|
164
|
+
| `validate` | list | Validators checked after the task exits 0 |
|
|
165
|
+
| `retry` | map | `max-attempts`, `on-validation-failure` (`retry`, `fail`, `skip`) |
|
|
166
|
+
| `skip-if` | map | `file-exists` or `file-contains`, evaluated before the task |
|
|
167
|
+
| `when` | string | Expression gate. An empty string skips the task |
|
|
168
|
+
| `env` | map | Task-level environment variables |
|
|
169
|
+
| `directory` | string | Task working directory, relative to the workflow `directory` |
|
|
170
|
+
| `cli-agent-config` | map | Per-task adapter options |
|
|
171
|
+
| `on-error` | string | `fail`, `continue`, `skip` |
|
|
172
|
+
|
|
173
|
+
**Loops**
|
|
174
|
+
|
|
175
|
+
| `type` | Iterates over | Required |
|
|
176
|
+
|--------|---------------|----------|
|
|
177
|
+
| `todo-list` | pending `- [ ]` items in a markdown file | `source` |
|
|
178
|
+
| `csv` | rows of a CSV | `source` |
|
|
179
|
+
| `json` | array in a JSON file, optionally nested | `source` |
|
|
180
|
+
| `jsonl` | one JSON object per line | `source` |
|
|
181
|
+
| `glob` | files matching a pattern | `pattern` |
|
|
182
|
+
|
|
183
|
+
All loops share these options:
|
|
184
|
+
|
|
185
|
+
| Option | Notes |
|
|
186
|
+
|--------|-------|
|
|
187
|
+
| `checkpoint-file` | JSON progress file, template variables expanded |
|
|
188
|
+
| `resume` | Continue after the last completed item |
|
|
189
|
+
| `retry` | `{max-attempts, on-failure}` with `skip`, `retry`, or `abort` |
|
|
190
|
+
| `workers` | Items run concurrently. Applies to `csv`, `json`, and `jsonl` only |
|
|
191
|
+
| `directory-pool` | Existing directories assigned round-robin to workers |
|
|
192
|
+
| `git-worktrees` | One worktree per worker: `base`, `branch-field`, `name-template`, `cleanup` |
|
|
193
|
+
|
|
194
|
+
Type-specific filters: `columns` and `filter` for `csv`, `path` and `filter` for `json`.
|
|
195
|
+
|
|
196
|
+
**Validators**
|
|
197
|
+
|
|
198
|
+
| Validator | Config | Passes when |
|
|
199
|
+
|-----------|--------|-------------|
|
|
200
|
+
| `file-exists` | string path | The path exists |
|
|
201
|
+
| `file-not-empty` | string path | The path exists and is not empty |
|
|
202
|
+
| `contains` | `{file, text}` | The file contains the text |
|
|
203
|
+
| `json-valid` | string path | The file parses as JSON |
|
|
204
|
+
| `yaml-valid` | string path | The file parses as YAML |
|
|
205
|
+
| `command` | `{cmd, file?, timeout?}` | The command exits 0 |
|
|
206
|
+
|
|
207
|
+
Validators run after a task exits 0. A failure follows `retry.on-validation-failure`.
|
|
208
|
+
|
|
209
|
+
**Variables**
|
|
210
|
+
|
|
211
|
+
| Variable | Value |
|
|
212
|
+
|----------|-------|
|
|
213
|
+
| `{{directory}}` | Absolute workflow directory |
|
|
214
|
+
| `{{gitroot}}` | Nearest ancestor with `.git/`, else `{{directory}}` |
|
|
215
|
+
| `{{datetime}}` | ISO-8601 timestamp, fixed for the run |
|
|
216
|
+
| `{{user}}` | `USER`, `USERNAME`, or `LOGNAME`, else `unknown` |
|
|
217
|
+
| `{{env.NAME}}` | Environment variable from the merged environment |
|
|
218
|
+
| `{{loop.index}}` | 0-based item index |
|
|
219
|
+
| `{{loop.total}}` | Number of items |
|
|
220
|
+
| `{{loop.remaining}}` | Items left |
|
|
221
|
+
| `{{loop.item}}` | Current item. Fields depend on the loop type |
|
|
222
|
+
| `{{loop.worker.id}}` | Worker id for parallel loops |
|
|
223
|
+
| `{{loop.worker.directory}}` | Worker directory with `directory-pool` or `git-worktrees` |
|
|
224
|
+
|
|
225
|
+
User variables are defined in `template-variables` and can reference each other. `--var key=value`
|
|
226
|
+
overrides them, and `{{env.NAME}}` reads the merged environment.
|
|
227
|
+
|
|
228
|
+
## How a run works
|
|
229
|
+
|
|
230
|
+
1. **Parse** (`agent_yaml/parser.py`) the YAML with `yaml.safe_load`.
|
|
231
|
+
2. **Validate** (`agent_yaml/validator.py`) required fields, task-tree shape, loop, validator, skip-if,
|
|
232
|
+
when, and retry configuration, plus adapter option names.
|
|
233
|
+
3. **Resolve includes** (`agent_yaml/includes.py`) and **variables** (`agent_yaml/variables.py`,
|
|
234
|
+
`agent_yaml/utils.py`). Reserved variables are injected, user variables resolve in dependency order,
|
|
235
|
+
and cycles are rejected.
|
|
236
|
+
4. **Check prerequisites** (`agent_yaml/ensure.py`) with `shutil.which` before anything runs.
|
|
237
|
+
5. **Execute** (`agent_yaml/executor.py`) the task tree. Array items run in order, mapping keys run
|
|
238
|
+
concurrently in a `ThreadPoolExecutor`, and each task is one `subprocess.run` with a timeout.
|
|
239
|
+
6. **Validate output**, then run the loop's next item or checkpoint its progress.
|
|
240
|
+
|
|
241
|
+
## Visual runner
|
|
242
|
+
|
|
243
|
+
`agent-yaml serve` starts a local console for browsing, running, and watching workflows:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
agent-yaml serve --host 127.0.0.1 --port 8765
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
It discovers `*.yaml` under `examples/` and `tasks/`, draws the stage graph on a WebGL2 canvas,
|
|
250
|
+
streams execution events over SSE, and stores runs plus interval/cron schedules in SQLite. Details
|
|
251
|
+
and the HTTP API are in [docs/INTERFACE_RUNNER.md](docs/INTERFACE_RUNNER.md).
|
|
252
|
+
|
|
253
|
+
## Pick your agent
|
|
254
|
+
|
|
255
|
+
| Adapter | Skip confirmations | Model flag |
|
|
256
|
+
|---------|--------------------|------------|
|
|
257
|
+
| `opencode` | `--auto` | `--model provider/model` |
|
|
258
|
+
| `claude` | `--dangerously-skip-permissions` | `--model` (aliases translated) |
|
|
259
|
+
| `gemini` | `--yolo` | `--model` |
|
|
260
|
+
| `qwen` | `--yolo` | `--model` |
|
|
261
|
+
| `codex` | `--full-auto` | `-m` |
|
|
262
|
+
|
|
263
|
+
Common options (`model`, `timeout`, `skip-confirmations`, `additional-dirs`, `mcp-config-file`,
|
|
264
|
+
`debug`, `raw-args`, `output-format`) map to each CLI. See
|
|
265
|
+
[docs/ADAPTER_TROUBLESHOOTING.md](docs/ADAPTER_TROUBLESHOOTING.md) for the full per-adapter table
|
|
266
|
+
and [spec/ADAPTER_SPEC.md](spec/ADAPTER_SPEC.md) for the contract.
|
|
267
|
+
|
|
268
|
+
## Docs
|
|
269
|
+
|
|
270
|
+
- [docs/](docs/README.md) is the field-level reference: authoring, templating, loops, parallel
|
|
271
|
+
loops, validators, interface runner, adapters.
|
|
272
|
+
- [docs/CLI.md](docs/CLI.md) covers every flag, exit code, environment precedence, and retry
|
|
273
|
+
behaviour.
|
|
274
|
+
- [examples/](examples/README.md) has 40 runnable workflows, all dry-run by the test suite.
|
|
275
|
+
- [spec/](spec/) holds the authoritative schema and adapter specs.
|
|
276
|
+
|
|
277
|
+
## Development
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
pip install -e ".[dev]" # pytest, ruff, coverage
|
|
281
|
+
pytest -q # hermetic, no model calls
|
|
282
|
+
ruff check agent_yaml tests
|
|
283
|
+
pip install -e ".[docs]" # mkdocs
|
|
284
|
+
mkdocs serve # preview docs/ at http://127.0.0.1:8000
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The test suite is three tiers: unit (agent mocked), integration (a fake `opencode` that runs the
|
|
288
|
+
prompt's `bash` block), and live (`pytest --live`, a real model behind an OpenAI-compatible
|
|
289
|
+
gateway). See [tests/README.md](tests/README.md).
|
|
290
|
+
|
|
291
|
+
## What it doesn't do
|
|
292
|
+
|
|
293
|
+
- It does not parse or judge model output. If a step needs to prove something, give it a validator.
|
|
294
|
+
- MCP detection in `ensure.mcps` is a warning-only stub.
|
|
295
|
+
- Nested list tasks (`task: [...]` inside a task) are rejected by the validator even though
|
|
296
|
+
`spec/YAML_SPEC.md` documents them. Use the mapping form.
|
|
297
|
+
- `agent-yaml run --resume` is a no-op. Resumption happens through each loop's `resume: true` plus
|
|
298
|
+
`checkpoint-file`.
|
|
299
|
+
- The Codex adapter emits flags that recent `codex-cli` releases reject. The Claude adapter sends
|
|
300
|
+
`--allowed-mcp-server-names`, which current `claude` builds reject. Both are listed with
|
|
301
|
+
workarounds in [docs/ADAPTER_TROUBLESHOOTING.md](docs/ADAPTER_TROUBLESHOOTING.md).
|
|
302
|
+
- Cancellation is best effort: in a parallel group with `on-error: fail`, tasks already scheduled
|
|
303
|
+
still run before the workflow aborts.
|
|
304
|
+
|
|
305
|
+
## Contributing
|
|
306
|
+
|
|
307
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and pull requests are welcome.
|
|
308
|
+
|
|
309
|
+
## License
|
|
310
|
+
|
|
311
|
+
[MIT](LICENSE) © 2026 Joe Schlesinger
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# agent-yaml
|
|
2
|
+
|
|
3
|
+
Write out your workflows in yaml and then run them under a harness of your choice.
|
|
4
|
+
|
|
5
|
+
## Why it exists
|
|
6
|
+
|
|
7
|
+
Each model and framework provider is trying to provide a way to interact with their harness, without providing what is usually needed: a definable, repeatable flow-chart style workflow to accomplish some repeated task.
|
|
8
|
+
|
|
9
|
+
The yaml contains the prompts, where each loop gets its items, and the checks a step has to pass
|
|
10
|
+
before the next one starts. Run it again next month in the latest harness/model of the day and run the same workflow.
|
|
11
|
+
|
|
12
|
+
agent-yaml lets one define their workflows irregardless of the harness or model of the day, by breaking down what most boring workflows tend to be (or could most easily be) comprised of:
|
|
13
|
+
- a working directory
|
|
14
|
+
- prompts made with templates
|
|
15
|
+
- looping over something
|
|
16
|
+
- validating checks
|
|
17
|
+
- composable templating of the above
|
|
18
|
+
|
|
19
|
+
Each step is one subprocess call, with contracts between steps being files on the disk.
|
|
20
|
+
|
|
21
|
+
Model output stays opaque and is never parsed.
|
|
22
|
+
If you want to parse it, write it to a file and parse that.
|
|
23
|
+
Validators run against written files to form checkpoints.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
No PyPI release yet. Install from the repository:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install "git+https://github.com/roboalchemist/agent-yaml.git"
|
|
31
|
+
agent-yaml --help
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Python 3.8+, tested on 3.9, 3.11 and 3.12, plus at least one supported agent CLI on `PATH`
|
|
35
|
+
(`opencode`, `claude`, `gemini`, `qwen`, or `codex`).
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
# hello.yaml
|
|
41
|
+
directory: /tmp/hello
|
|
42
|
+
cli-agent: opencode
|
|
43
|
+
cli-agent-config:
|
|
44
|
+
skip-confirmations: true
|
|
45
|
+
model: provider/model-name
|
|
46
|
+
|
|
47
|
+
task:
|
|
48
|
+
- write-file:
|
|
49
|
+
prompt: "Create hello.txt containing exactly: hello from agent-yaml"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
mkdir -p /tmp/hello
|
|
54
|
+
agent-yaml validate hello.yaml # parse and schema check, no agent calls
|
|
55
|
+
agent-yaml run hello.yaml --dry-run # print the exact command that would run
|
|
56
|
+
agent-yaml run hello.yaml # run it
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
A dry run for that file prints the command it would execute, including the translated flags, so you
|
|
60
|
+
can spot a wrong model or a rejected option before spending tokens:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
[DRY RUN] Task 'write-file' would execute:
|
|
64
|
+
Command: opencode run --standalone --auto -- Create hello.txt containing exactly: ...
|
|
65
|
+
Working directory: /tmp/hello
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## A real workflow
|
|
69
|
+
|
|
70
|
+
This is the shape of the workflows in `examples/` and `tasks/`: a CSV loop, a validator per item,
|
|
71
|
+
retries on validation failure, and a checkpoint so an interrupted run can continue.
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
directory: ~/projects/reports
|
|
75
|
+
cli-agent-config:
|
|
76
|
+
model: openai/gpt-5
|
|
77
|
+
|
|
78
|
+
task:
|
|
79
|
+
- summarize-rows:
|
|
80
|
+
loop:
|
|
81
|
+
type: csv
|
|
82
|
+
source: "{{directory}}/rows.csv"
|
|
83
|
+
workers: 4
|
|
84
|
+
retry: {max-attempts: 3, on-failure: retry}
|
|
85
|
+
checkpoint-file: "{{directory}}/state/summaries.json"
|
|
86
|
+
resume: true
|
|
87
|
+
prompt: |
|
|
88
|
+
Summarize row {{loop.index}}/{{loop.total}} ({{loop.item.name}}) and write
|
|
89
|
+
{{directory}}/out/{{loop.item.name}}.md
|
|
90
|
+
validate:
|
|
91
|
+
- file-not-empty: "{{directory}}/out/{{loop.item.name}}.md"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Run it, then rerun the same command after an interruption. Finished items are skipped.
|
|
95
|
+
|
|
96
|
+
## Primitives
|
|
97
|
+
|
|
98
|
+
A workflow is built from a fixed vocabulary. The forms are the same on every adapter, and only the
|
|
99
|
+
CLI-specific options change.
|
|
100
|
+
|
|
101
|
+
**Workflow fields**
|
|
102
|
+
|
|
103
|
+
| Field | Type | Notes |
|
|
104
|
+
|-------|------|-------|
|
|
105
|
+
| `directory` | string | Required. Working directory for every task |
|
|
106
|
+
| `cli-agent` | string | `opencode`, `claude`, `gemini`, `qwen`, `codex` (default `claude`) |
|
|
107
|
+
| `environment` | map | Env vars inherited by every task |
|
|
108
|
+
| `template-variables` | map | User variables, resolved in dependency order |
|
|
109
|
+
| `includes` | map | Reusable prompt fragments, inline or from a file |
|
|
110
|
+
| `ensure` | map | Pre-flight `tools` and `mcps` checks |
|
|
111
|
+
| `task` | list | The task tree (required) |
|
|
112
|
+
|
|
113
|
+
`cli-agent-config` holds adapter options: `model`, `timeout`, `skip-confirmations`,
|
|
114
|
+
`additional-dirs`, `mcp-config-file`, `raw-args`, and the per-adapter equivalents.
|
|
115
|
+
|
|
116
|
+
**Task fields**
|
|
117
|
+
|
|
118
|
+
| Field | Type | Notes |
|
|
119
|
+
|-------|------|-------|
|
|
120
|
+
| `prompt` | string | Text sent to the agent CLI |
|
|
121
|
+
| `task` | list or map | Nested tasks: a list runs in order, a map runs its keys concurrently |
|
|
122
|
+
| `include` | list | Names of top-level includes appended to the prompt |
|
|
123
|
+
| `loop` | map | Runs the task once per item |
|
|
124
|
+
| `validate` | list | Validators checked after the task exits 0 |
|
|
125
|
+
| `retry` | map | `max-attempts`, `on-validation-failure` (`retry`, `fail`, `skip`) |
|
|
126
|
+
| `skip-if` | map | `file-exists` or `file-contains`, evaluated before the task |
|
|
127
|
+
| `when` | string | Expression gate. An empty string skips the task |
|
|
128
|
+
| `env` | map | Task-level environment variables |
|
|
129
|
+
| `directory` | string | Task working directory, relative to the workflow `directory` |
|
|
130
|
+
| `cli-agent-config` | map | Per-task adapter options |
|
|
131
|
+
| `on-error` | string | `fail`, `continue`, `skip` |
|
|
132
|
+
|
|
133
|
+
**Loops**
|
|
134
|
+
|
|
135
|
+
| `type` | Iterates over | Required |
|
|
136
|
+
|--------|---------------|----------|
|
|
137
|
+
| `todo-list` | pending `- [ ]` items in a markdown file | `source` |
|
|
138
|
+
| `csv` | rows of a CSV | `source` |
|
|
139
|
+
| `json` | array in a JSON file, optionally nested | `source` |
|
|
140
|
+
| `jsonl` | one JSON object per line | `source` |
|
|
141
|
+
| `glob` | files matching a pattern | `pattern` |
|
|
142
|
+
|
|
143
|
+
All loops share these options:
|
|
144
|
+
|
|
145
|
+
| Option | Notes |
|
|
146
|
+
|--------|-------|
|
|
147
|
+
| `checkpoint-file` | JSON progress file, template variables expanded |
|
|
148
|
+
| `resume` | Continue after the last completed item |
|
|
149
|
+
| `retry` | `{max-attempts, on-failure}` with `skip`, `retry`, or `abort` |
|
|
150
|
+
| `workers` | Items run concurrently. Applies to `csv`, `json`, and `jsonl` only |
|
|
151
|
+
| `directory-pool` | Existing directories assigned round-robin to workers |
|
|
152
|
+
| `git-worktrees` | One worktree per worker: `base`, `branch-field`, `name-template`, `cleanup` |
|
|
153
|
+
|
|
154
|
+
Type-specific filters: `columns` and `filter` for `csv`, `path` and `filter` for `json`.
|
|
155
|
+
|
|
156
|
+
**Validators**
|
|
157
|
+
|
|
158
|
+
| Validator | Config | Passes when |
|
|
159
|
+
|-----------|--------|-------------|
|
|
160
|
+
| `file-exists` | string path | The path exists |
|
|
161
|
+
| `file-not-empty` | string path | The path exists and is not empty |
|
|
162
|
+
| `contains` | `{file, text}` | The file contains the text |
|
|
163
|
+
| `json-valid` | string path | The file parses as JSON |
|
|
164
|
+
| `yaml-valid` | string path | The file parses as YAML |
|
|
165
|
+
| `command` | `{cmd, file?, timeout?}` | The command exits 0 |
|
|
166
|
+
|
|
167
|
+
Validators run after a task exits 0. A failure follows `retry.on-validation-failure`.
|
|
168
|
+
|
|
169
|
+
**Variables**
|
|
170
|
+
|
|
171
|
+
| Variable | Value |
|
|
172
|
+
|----------|-------|
|
|
173
|
+
| `{{directory}}` | Absolute workflow directory |
|
|
174
|
+
| `{{gitroot}}` | Nearest ancestor with `.git/`, else `{{directory}}` |
|
|
175
|
+
| `{{datetime}}` | ISO-8601 timestamp, fixed for the run |
|
|
176
|
+
| `{{user}}` | `USER`, `USERNAME`, or `LOGNAME`, else `unknown` |
|
|
177
|
+
| `{{env.NAME}}` | Environment variable from the merged environment |
|
|
178
|
+
| `{{loop.index}}` | 0-based item index |
|
|
179
|
+
| `{{loop.total}}` | Number of items |
|
|
180
|
+
| `{{loop.remaining}}` | Items left |
|
|
181
|
+
| `{{loop.item}}` | Current item. Fields depend on the loop type |
|
|
182
|
+
| `{{loop.worker.id}}` | Worker id for parallel loops |
|
|
183
|
+
| `{{loop.worker.directory}}` | Worker directory with `directory-pool` or `git-worktrees` |
|
|
184
|
+
|
|
185
|
+
User variables are defined in `template-variables` and can reference each other. `--var key=value`
|
|
186
|
+
overrides them, and `{{env.NAME}}` reads the merged environment.
|
|
187
|
+
|
|
188
|
+
## How a run works
|
|
189
|
+
|
|
190
|
+
1. **Parse** (`agent_yaml/parser.py`) the YAML with `yaml.safe_load`.
|
|
191
|
+
2. **Validate** (`agent_yaml/validator.py`) required fields, task-tree shape, loop, validator, skip-if,
|
|
192
|
+
when, and retry configuration, plus adapter option names.
|
|
193
|
+
3. **Resolve includes** (`agent_yaml/includes.py`) and **variables** (`agent_yaml/variables.py`,
|
|
194
|
+
`agent_yaml/utils.py`). Reserved variables are injected, user variables resolve in dependency order,
|
|
195
|
+
and cycles are rejected.
|
|
196
|
+
4. **Check prerequisites** (`agent_yaml/ensure.py`) with `shutil.which` before anything runs.
|
|
197
|
+
5. **Execute** (`agent_yaml/executor.py`) the task tree. Array items run in order, mapping keys run
|
|
198
|
+
concurrently in a `ThreadPoolExecutor`, and each task is one `subprocess.run` with a timeout.
|
|
199
|
+
6. **Validate output**, then run the loop's next item or checkpoint its progress.
|
|
200
|
+
|
|
201
|
+
## Visual runner
|
|
202
|
+
|
|
203
|
+
`agent-yaml serve` starts a local console for browsing, running, and watching workflows:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
agent-yaml serve --host 127.0.0.1 --port 8765
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
It discovers `*.yaml` under `examples/` and `tasks/`, draws the stage graph on a WebGL2 canvas,
|
|
210
|
+
streams execution events over SSE, and stores runs plus interval/cron schedules in SQLite. Details
|
|
211
|
+
and the HTTP API are in [docs/INTERFACE_RUNNER.md](docs/INTERFACE_RUNNER.md).
|
|
212
|
+
|
|
213
|
+
## Pick your agent
|
|
214
|
+
|
|
215
|
+
| Adapter | Skip confirmations | Model flag |
|
|
216
|
+
|---------|--------------------|------------|
|
|
217
|
+
| `opencode` | `--auto` | `--model provider/model` |
|
|
218
|
+
| `claude` | `--dangerously-skip-permissions` | `--model` (aliases translated) |
|
|
219
|
+
| `gemini` | `--yolo` | `--model` |
|
|
220
|
+
| `qwen` | `--yolo` | `--model` |
|
|
221
|
+
| `codex` | `--full-auto` | `-m` |
|
|
222
|
+
|
|
223
|
+
Common options (`model`, `timeout`, `skip-confirmations`, `additional-dirs`, `mcp-config-file`,
|
|
224
|
+
`debug`, `raw-args`, `output-format`) map to each CLI. See
|
|
225
|
+
[docs/ADAPTER_TROUBLESHOOTING.md](docs/ADAPTER_TROUBLESHOOTING.md) for the full per-adapter table
|
|
226
|
+
and [spec/ADAPTER_SPEC.md](spec/ADAPTER_SPEC.md) for the contract.
|
|
227
|
+
|
|
228
|
+
## Docs
|
|
229
|
+
|
|
230
|
+
- [docs/](docs/README.md) is the field-level reference: authoring, templating, loops, parallel
|
|
231
|
+
loops, validators, interface runner, adapters.
|
|
232
|
+
- [docs/CLI.md](docs/CLI.md) covers every flag, exit code, environment precedence, and retry
|
|
233
|
+
behaviour.
|
|
234
|
+
- [examples/](examples/README.md) has 40 runnable workflows, all dry-run by the test suite.
|
|
235
|
+
- [spec/](spec/) holds the authoritative schema and adapter specs.
|
|
236
|
+
|
|
237
|
+
## Development
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
pip install -e ".[dev]" # pytest, ruff, coverage
|
|
241
|
+
pytest -q # hermetic, no model calls
|
|
242
|
+
ruff check agent_yaml tests
|
|
243
|
+
pip install -e ".[docs]" # mkdocs
|
|
244
|
+
mkdocs serve # preview docs/ at http://127.0.0.1:8000
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The test suite is three tiers: unit (agent mocked), integration (a fake `opencode` that runs the
|
|
248
|
+
prompt's `bash` block), and live (`pytest --live`, a real model behind an OpenAI-compatible
|
|
249
|
+
gateway). See [tests/README.md](tests/README.md).
|
|
250
|
+
|
|
251
|
+
## What it doesn't do
|
|
252
|
+
|
|
253
|
+
- It does not parse or judge model output. If a step needs to prove something, give it a validator.
|
|
254
|
+
- MCP detection in `ensure.mcps` is a warning-only stub.
|
|
255
|
+
- Nested list tasks (`task: [...]` inside a task) are rejected by the validator even though
|
|
256
|
+
`spec/YAML_SPEC.md` documents them. Use the mapping form.
|
|
257
|
+
- `agent-yaml run --resume` is a no-op. Resumption happens through each loop's `resume: true` plus
|
|
258
|
+
`checkpoint-file`.
|
|
259
|
+
- The Codex adapter emits flags that recent `codex-cli` releases reject. The Claude adapter sends
|
|
260
|
+
`--allowed-mcp-server-names`, which current `claude` builds reject. Both are listed with
|
|
261
|
+
workarounds in [docs/ADAPTER_TROUBLESHOOTING.md](docs/ADAPTER_TROUBLESHOOTING.md).
|
|
262
|
+
- Cancellation is best effort: in a parallel group with `on-error: fail`, tasks already scheduled
|
|
263
|
+
still run before the workflow aborts.
|
|
264
|
+
|
|
265
|
+
## Contributing
|
|
266
|
+
|
|
267
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and pull requests are welcome.
|
|
268
|
+
|
|
269
|
+
## License
|
|
270
|
+
|
|
271
|
+
[MIT](LICENSE) © 2026 Joe Schlesinger
|