Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 72 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,82 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4
with:
submodules: false
- uses: actions/setup-python@v5

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -e ".[dev,server,car]"
- run: pytest tests/ -x -q --timeout=30
enable-cache: true

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Sync dependencies (latest, editable + dev extras)
run: uv sync --upgrade --extra dev --extra server --extra car --python ${{ matrix.python-version }}

- name: Run tests
run: uv run pytest tests/ -x -q --timeout=30
env:
PYTHONDONTWRITEBYTECODE: "1"

wheel-smoke:
runs-on: ubuntu-latest
needs: test
strategy:
matrix:
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v4
with:
submodules: false

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Build wheel and sdist
run: uv build --python ${{ matrix.python-version }}

- name: Audit built artifacts — no unwanted paths
run: |
WHL=$(ls dist/*.whl | head -1)
SDIST=$(ls dist/*.tar.gz | head -1)
echo "=== Wheel contents ==="
unzip -l "$WHL"
echo "=== Sdist contents ==="
tar -tzf "$SDIST"
for forbidden in benchmarks/ .codeboarding/ skyrl/ runs/; do
if unzip -l "$WHL" | grep -q "$forbidden"; then
echo "ERROR: wheel contains forbidden path: $forbidden" && exit 1
fi
if tar -tzf "$SDIST" | grep -q "$forbidden"; then
echo "ERROR: sdist contains forbidden path: $forbidden" && exit 1
fi
done
echo "Artifact audit passed."

- name: Smoke test — install from wheel into clean venv
run: |
uv venv .venv-wheel --python ${{ matrix.python-version }}
uv pip install --python .venv-wheel/bin/python dist/*.whl
.venv-wheel/bin/python -c "import clawloop; print('version:', clawloop.__version__)"
.venv-wheel/bin/clawloop --help
.venv-wheel/bin/clawloop demo math --dry-run
.venv-wheel/bin/python -m clawloop.demo_math --dry-run

- name: Smoke test — install from sdist into clean venv
run: |
uv venv .venv-sdist --python ${{ matrix.python-version }}
uv pip install --python .venv-sdist/bin/python dist/*.tar.gz
.venv-sdist/bin/python -c "import clawloop; print('version:', clawloop.__version__)"
.venv-sdist/bin/clawloop --help
.venv-sdist/bin/clawloop demo math --dry-run
.venv-sdist/bin/python -m clawloop.demo_math --dry-run
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
.idea/
.pytest_cache/
.vscode/
*.iml
*.~undo-tree~
*.backup

# Secrets
.env
Expand All @@ -27,4 +30,4 @@ examples/openclaw_runner/package-lock.json

# Runtime artifacts
playbook.json
runs/
runs/
42 changes: 29 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

[![License: BSL 1.1](https://img.shields.io/badge/License-BSL%201.1-blue.svg)](LICENSE)
[![CI](https://github.com/aganthos/clawloop/actions/workflows/ci.yml/badge.svg)](https://github.com/aganthos/clawloop/actions/workflows/ci.yml)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
[![Python 3.12+](https://img.shields.io/badge/python-3.12%20|%203.13-blue.svg)](https://www.python.org/downloads/)

Your AI agents run, fail, and forget. ClawLoop closes the loop: it observes
agent-environment interactions, learns from them, and feeds improvements back
Expand All @@ -19,24 +19,38 @@ all following the same protocol.

## Install

Requires Python 3.11+.
Requires Python 3.12 or 3.13.

```bash
pip install -e .
git clone https://github.com/aganthos/clawloop
cd clawloop
uv sync # installs all deps from uv.lock, creates .venv automatically
```

For weight training (GPU):
```bash
git submodule update --init clawloop/skyrl
pip install -e clawloop/skyrl[fsdp]
uv sync --extra taubench
```

## Try It in 10 Seconds

No API keys. No setup. Just run:

```bash
python examples/demo_math.py --dry-run
uv run clawloop demo math --dry-run
```

or as a module:

```bash
uv run python -m clawloop.demo_math --dry-run
```

or via the examples shim (also works from a clone):

```bash
uv run python examples/demo_math.py --dry-run
```

```
Expand Down Expand Up @@ -81,19 +95,21 @@ results = agent.learn(MathEnvironment(), iterations=10, episodes_per_iter=5)
**Config-driven training (no code):**

```bash
python examples/train_runner.py examples/configs/math_harness.json
uv run python examples/train_runner.py examples/configs/math_harness.json
```

## Choose Your Integration Path

| You have... | Start here | What it shows |
| Example type | Start here | What it shows |
|---|---|---|
| A Python agent | [`examples/demo_math.py`](examples/demo_math.py) | Full learning loop with `ClawLoopAgent` |
| An n8n or workflow platform | [`examples/n8n/`](examples/n8n/) | Webhook integration, zero Python needed |
| An OpenAI-compatible agent | [`examples/train_runner.py`](examples/train_runner.py) with configs | CRMArena, Harbor BFCL via litellm |
| Want zero-code-change learning | [`examples/openclaw_demo.py`](examples/openclaw_demo.py) | Transparent proxy captures traces + injects skills |
| A running OpenClaw instance | [`examples/openclaw_demo_remote.py`](examples/openclaw_demo_remote.py) | SSH into your OpenClaw, learn from traces, show improvement |
| GPU resources for weight training | [`examples/recipes/`](examples/recipes/) | SkyRL/Tinker GRPO, PPO, full finetune |
| Harness: no-key math learning loop | `uv run clawloop demo math --dry-run` | ClawLoopAgent learns from math episodes without API keys |
| Harness: package/module demo entry points | `uv run python -m clawloop.demo_math --dry-run` or [`examples/demo_math.py`](examples/demo_math.py) | Same math demo from an installed package or source clone |
| Playbook internals walkthrough | `uv run python examples/playbook_demo.py --dry-run` | `forward_backward`, `optim_step`, entry scoring, structured skills |
| Workflow: n8n webhook integration | [`examples/n8n/`](examples/n8n/) | Workflow platform sends traces to clawloop-server; no Python in the workflow |
| Harness benchmarks: config-driven runner | `uv run python examples/train_runner.py examples/configs/math_harness.json` | Math, CRMArena, Harbor BFCL via JSON configs and litellm |
| Proxy harness: zero-code-change OpenClaw | `uv run python examples/openclaw_demo.py` | Transparent proxy captures traces and injects learned skills |
| Remote OpenClaw: SSH-connected proxy harness | `uv run python examples/openclaw_demo_remote.py --host YOUR_HOST ...` | Learn from a remote OpenClaw instance and compare before/after |
| Weights: SkyRL/Tinker training recipes | [`examples/recipes/`](examples/recipes/) | GRPO, PPO, and fine-tuning recipes for GPU training |

See [`examples/README.md`](examples/README.md) for details on each path.

Expand Down
31 changes: 31 additions & 0 deletions clawloop/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ def _build_parser() -> argparse.ArgumentParser:
setup_p.add_argument("-v", "--verbose", action="store_true", help="Enable debug logging")
setup_p.add_argument("--bench", required=True, help="Benchmark name")

# -- demo --
demo_p = sub.add_parser("demo", help="Run built-in demos")
demo_sub = demo_p.add_subparsers(dest="demo_name", required=True)

math_p = demo_sub.add_parser("math", help="Math learning loop demo")
math_p.add_argument("--dry-run", action="store_true", help="Use mock LLMs (no API calls)")
math_p.add_argument("--iterations", type=int, default=None, help="Number of learning iterations")
math_p.add_argument("--episodes", type=int, default=None, help="Episodes per iteration")
math_p.add_argument("--output", type=str, default="playbook.json", help="Playbook output path")

return parser


Expand Down Expand Up @@ -265,6 +275,26 @@ def cmd_setup_bench(args: argparse.Namespace) -> None:
print(f"Setup complete for {bench}")


def cmd_demo(args: argparse.Namespace) -> None:
"""Dispatch to the requested built-in demo."""
if args.demo_name == "math":
from clawloop.demo_math import main as demo_math_main

argv: list[str] = []
if getattr(args, "dry_run", False):
argv.append("--dry-run")
if getattr(args, "iterations", None) is not None:
argv += ["--iterations", str(args.iterations)]
if getattr(args, "episodes", None) is not None:
argv += ["--episodes", str(args.episodes)]
if getattr(args, "output", None):
argv += ["--output", args.output]
demo_math_main(argv)
else:
print(f"Unknown demo: {args.demo_name}", file=sys.stderr)
sys.exit(1)


def main() -> None:
parser = _build_parser()
args = parser.parse_args()
Expand All @@ -280,6 +310,7 @@ def main() -> None:
"run": cmd_run,
"eval": cmd_eval,
"setup-bench": cmd_setup_bench,
"demo": cmd_demo,
}
handlers[args.command](args)

Expand Down
Loading
Loading