|
| 1 | +# OpenClaw Macro System — Agent Harness SOP |
| 2 | + |
| 3 | +## What Is This? |
| 4 | + |
| 5 | +**OpenClaw Macro System** is a layered CLI that turns valuable GUI workflows into |
| 6 | +parameterized, agent-callable macros. The agent sends one command: |
| 7 | + |
| 8 | +```bash |
| 9 | +cli-anything-openclaw macro run export_png --param output=/tmp/out.png --json |
| 10 | +``` |
| 11 | + |
| 12 | +The system handles everything else: parameter validation, precondition checks, |
| 13 | +backend selection, step execution, postcondition verification, and structured |
| 14 | +result output. The agent never touches the GUI directly. |
| 15 | + |
| 16 | +## Architecture |
| 17 | + |
| 18 | +``` |
| 19 | +Agent |
| 20 | + └─▶ cli-anything-openclaw macro run <name> --param k=v --json (L6: CLI) |
| 21 | + │ |
| 22 | + MacroRuntime (L5) |
| 23 | + │ 1. Validate params against MacroDefinition schema |
| 24 | + │ 2. Check preconditions (file_exists, process_running, …) |
| 25 | + │ 3. For each step: |
| 26 | + │ RoutingEngine → select backend by priority (L3) |
| 27 | + │ Backend.execute(step, resolved_params) (L2) |
| 28 | + │ 4. Check postconditions |
| 29 | + │ 5. Collect declared outputs |
| 30 | + │ 6. Record telemetry in ExecutionSession |
| 31 | + └─▶ { success, output, error, telemetry } |
| 32 | +``` |
| 33 | + |
| 34 | +## Layer Mapping |
| 35 | + |
| 36 | +| Layer | Name | Implementation | |
| 37 | +|-------|------|---------------| |
| 38 | +| L7 | Agent Task Interface | Caller (OpenClaw or any agent) | |
| 39 | +| L6 | Unified CLI Entry | `openclaw_cli.py` — Click CLI | |
| 40 | +| L5 | Macro Execution Runtime | `core/runtime.py` | |
| 41 | +| L4 | Parameterized Macro Model | `core/macro_model.py` + `macro_definitions/*.yaml` | |
| 42 | +| L3 | Backend Routing Engine | `core/routing.py` | |
| 43 | +| L2 | Execution Backends | `backends/` (5 backends) | |
| 44 | +| L1 | Target Application | Any GUI-first or closed-source app | |
| 45 | + |
| 46 | +## Execution Backends |
| 47 | + |
| 48 | +| Backend | Priority | Trigger | Use case | |
| 49 | +|---------|----------|---------|----------| |
| 50 | +| `native_api` | 100 | `backend: native_api` | subprocess / shell commands | |
| 51 | +| `gui_macro` | 80 | `backend: gui_macro` | precompiled coordinate replay (pyautogui) | |
| 52 | +| `file_transform` | 70 | `backend: file_transform` | XML, JSON, text file editing | |
| 53 | +| `semantic_ui` | 50 | `backend: semantic_ui` | accessibility API + keyboard (xdotool) | |
| 54 | +| `recovery` | 10 | `backend: recovery` | retry + fallback orchestration | |
| 55 | + |
| 56 | +The RoutingEngine respects the step's explicit `backend:` field; if that backend |
| 57 | +is unavailable it walks down the priority list. |
| 58 | + |
| 59 | +## Macro Definition Format |
| 60 | + |
| 61 | +Macros live in `cli_anything/openclaw/macro_definitions/` as YAML files: |
| 62 | + |
| 63 | +```yaml |
| 64 | +name: export_png |
| 65 | +version: "1.0" |
| 66 | +description: Export the active diagram to PNG. |
| 67 | + |
| 68 | +parameters: |
| 69 | + output: |
| 70 | + type: string |
| 71 | + required: true |
| 72 | + example: /tmp/diagram.png |
| 73 | + |
| 74 | +preconditions: |
| 75 | + - process_running: draw.io |
| 76 | + - file_exists: /path/to/input.drawio |
| 77 | + |
| 78 | +steps: |
| 79 | + - id: export |
| 80 | + backend: native_api |
| 81 | + action: run_command |
| 82 | + params: |
| 83 | + command: [draw.io, --export, --output, "${output}", input.drawio] |
| 84 | + timeout_ms: 30000 |
| 85 | + on_failure: fail # or: skip | continue |
| 86 | + |
| 87 | +postconditions: |
| 88 | + - file_exists: ${output} |
| 89 | + - file_size_gt: |
| 90 | + - ${output} |
| 91 | + - 100 |
| 92 | + |
| 93 | +outputs: |
| 94 | + - name: exported_file |
| 95 | + path: ${output} |
| 96 | + |
| 97 | +agent_hints: |
| 98 | + danger_level: safe |
| 99 | + side_effects: [creates_file] |
| 100 | + reversible: true |
| 101 | +``` |
| 102 | +
|
| 103 | +### Supported Condition Types |
| 104 | +
|
| 105 | +| Type | Args | Checks | |
| 106 | +|------|------|--------| |
| 107 | +| `file_exists` | path | `os.path.exists(path)` | |
| 108 | +| `file_size_gt` | [path, min_bytes] | `os.stat(path).st_size > min_bytes` | |
| 109 | +| `process_running` | name | `pgrep -x name` or psutil | |
| 110 | +| `env_var` | name | `name in os.environ` | |
| 111 | +| `always` | true/false | constant pass/fail | |
| 112 | + |
| 113 | +## Package Layout |
| 114 | + |
| 115 | +``` |
| 116 | +openclaw-skill/ |
| 117 | +└── agent-harness/ |
| 118 | + ├── setup.py entry_point: cli-anything-openclaw |
| 119 | + └── cli_anything/openclaw/ |
| 120 | + ├── openclaw_cli.py Main Click CLI |
| 121 | + ├── macro_definitions/ YAML macro registry |
| 122 | + │ ├── manifest.yaml |
| 123 | + │ └── examples/ |
| 124 | + │ ├── export_file.yaml |
| 125 | + │ ├── transform_json.yaml |
| 126 | + │ └── undo_last.yaml |
| 127 | + ├── core/ |
| 128 | + │ ├── macro_model.py MacroDefinition + YAML loader |
| 129 | + │ ├── registry.py MacroRegistry |
| 130 | + │ ├── routing.py RoutingEngine |
| 131 | + │ ├── runtime.py MacroRuntime (full lifecycle) |
| 132 | + │ └── session.py ExecutionSession + telemetry |
| 133 | + ├── backends/ |
| 134 | + │ ├── base.py Backend ABC + StepResult |
| 135 | + │ ├── native_api.py subprocess backend |
| 136 | + │ ├── file_transform.py XML/JSON/text backend |
| 137 | + │ ├── semantic_ui.py accessibility backend |
| 138 | + │ ├── gui_macro.py compiled replay backend |
| 139 | + │ └── recovery.py retry/fallback backend |
| 140 | + ├── skills/SKILL.md Agent-readable skill definition |
| 141 | + ├── utils/repl_skin.py Unified REPL skin (cli-anything standard) |
| 142 | + └── tests/ |
| 143 | + ├── test_core.py Unit tests (49 tests, no external deps) |
| 144 | + └── test_full_e2e.py E2E + CLI subprocess tests (15 tests) |
| 145 | +``` |
| 146 | +
|
| 147 | +## Installation |
| 148 | +
|
| 149 | +```bash |
| 150 | +cd openclaw-skill/agent-harness |
| 151 | +pip install -e . |
| 152 | +``` |
| 153 | + |
| 154 | +**Runtime dependencies:** Python 3.10+, PyYAML, click, prompt-toolkit. |
| 155 | + |
| 156 | +**Optional (for specific backends):** |
| 157 | +- `xdotool` — semantic_ui backend on Linux |
| 158 | +- `pyautogui` — gui_macro backend |
| 159 | +- `psutil` — richer process_running checks |
| 160 | + |
| 161 | +## Running Tests |
| 162 | + |
| 163 | +```bash |
| 164 | +cd openclaw-skill/agent-harness |
| 165 | +python3 -m pytest cli_anything/openclaw/tests/ -v -s |
| 166 | +# 64 passed |
| 167 | +``` |
| 168 | + |
| 169 | +## Key Design Decisions |
| 170 | + |
| 171 | +**Why YAML macros, not Python?** YAML macros are readable by agents without |
| 172 | +running code, inspectable via `macro info`, and editable without touching the |
| 173 | +harness source. |
| 174 | + |
| 175 | +**Why 5 backends?** Real GUI applications expose many different control |
| 176 | +surfaces. The routing engine picks the most reliable one available — the agent |
| 177 | +doesn't need to know which one ran. |
| 178 | + |
| 179 | +**Why preconditions and postconditions?** Agents operate in environments where |
| 180 | +state is uncertain. Failing loudly before execution (preconditions) and |
| 181 | +verifying after (postconditions) catches problems the agent can act on. |
| 182 | + |
| 183 | +**Why `on_failure: skip | continue`?** Some macro steps are best-effort (e.g., |
| 184 | +confirming a dialog that may or may not appear). Skipping lets the macro |
| 185 | +continue to the real work. |
0 commit comments