diff --git a/.github/workflows/zeroclaw_dual_orchestrator.yml b/.github/workflows/zeroclaw_dual_orchestrator.yml new file mode 100644 index 0000000..26a0225 --- /dev/null +++ b/.github/workflows/zeroclaw_dual_orchestrator.yml @@ -0,0 +1,42 @@ +name: ZeroClaw Dual Orchestrator + +on: + push: + branches: [main] + paths: + - "tools/ai/zeroclaw_dual_*.sh" + - "specs/zeroclaw_dual_hw_*.md" + - ".github/workflows/zeroclaw_dual_orchestrator.yml" + pull_request: + branches: [main] + paths: + - "tools/ai/zeroclaw_dual_*.sh" + - "specs/zeroclaw_dual_hw_*.md" + - ".github/workflows/zeroclaw_dual_orchestrator.yml" + workflow_dispatch: + +concurrency: + group: zeroclaw-dual-orchestrator-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-and-contract: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install shellcheck + run: | + sudo apt-get update + sudo apt-get install -y shellcheck + + - name: Shellcheck orchestrator scripts + run: | + shellcheck tools/ai/zeroclaw_dual_bootstrap.sh + shellcheck tools/ai/zeroclaw_dual_chat.sh + + - name: Verify spec files + run: | + test -s specs/zeroclaw_dual_hw_orchestration_spec.md + test -s specs/zeroclaw_dual_hw_todo.md diff --git a/.gitignore b/.gitignore index 60dd6ea..f412458 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .venv # Fichiers et dossiers à ignorer pour Kill_LIFE +zeroclaw/ # Python __pycache__/ diff --git a/specs/README.md b/specs/README.md index 79637dc..bd8b438 100644 --- a/specs/README.md +++ b/specs/README.md @@ -9,3 +9,8 @@ Flux conseillé (itératif) : 6) Implémentation (firmware/hardware) + tests + doc Le fichier `constraints.yaml` est la **source de vérité** des contraintes non-fonctionnelles et règles repo. + +Specs complémentaires: + +- `zeroclaw_dual_hw_orchestration_spec.md`: architecture d'orchestration ZeroClaw multi-repo + double matériel. +- `zeroclaw_dual_hw_todo.md`: backlog opérationnel court terme pour autonomie contrôlée. diff --git a/specs/zeroclaw_dual_hw_orchestration_spec.md b/specs/zeroclaw_dual_hw_orchestration_spec.md new file mode 100644 index 0000000..95bd1a7 --- /dev/null +++ b/specs/zeroclaw_dual_hw_orchestration_spec.md @@ -0,0 +1,114 @@ +# Spec: ZeroClaw Dual-Repo + Dual-Hardware Orchestration + +Last updated: 2026-02-21 + +## 1) Goal + +Run one orchestration layer that can: + +- converse against `RTC_BL_PHONE` and `le-mystere-professeur-zacus` independently, +- keep workspace boundaries strict per repo, +- run low-cost autonomous loops with guarded command allowlists, +- stay ready for connected hardware checks before any upload/flash action. + +## 2) Scope + +In scope: + +- local ZeroClaw profile bootstrap for both repos, +- deterministic workspace switch (`rtc` vs `zacus`) from one CLI entrypoint, +- hardware discovery/introspection preflight, +- lightweight CI validation for orchestration scripts/spec. + +Out of scope: + +- storing provider secrets in git, +- hard-coding serial ports in committed config, +- forcing hardware jobs on GitHub-hosted runners. + +## 3) Current Hardware Snapshot (local) + +Detected at bootstrap time: + +- `CP2102 USB to UART Bridge Controller` (`10c4:ea60`) +- `USB Single Serial` (`1a86:55d3`) + +Known candidate ports: + +- `/dev/tty.SLAB_USBtoUART` +- `/dev/tty.usbserial-0001` +- `/dev/tty.usbmodem5AB90753301` + +## 4) Architecture + +### 4.1 Repo-local ZeroClaw profile + +Each repo gets: + +- `/.zeroclaw/config.toml` + +Runtime workspace selection is done via: + +- `ZEROCLAW_WORKSPACE=` + +This keeps `autonomy.workspace_only = true` effective on a per-repo boundary. + +### 4.2 Orchestrator scripts + +- `tools/ai/zeroclaw_dual_bootstrap.sh` + - writes/refreshes both repo profiles, + - archives legacy root `config.toml` + `workspace/` if they match old ZeroClaw layout, + - validates ZeroClaw binary availability, + - runs `zeroclaw status` for both workspaces, + - runs `zeroclaw hardware discover`. +- `tools/ai/zeroclaw_dual_chat.sh` + - target switch by alias (`rtc`, `zacus`) or absolute path, + - message mode (`-m`) or interactive mode, + - provider auto-fallback (`copilot` -> `openai-codex` -> `openrouter`), + - token sourcing from `gh auth token` at runtime only when `copilot` is selected. + +### 4.3 Provider/cost strategy + +Auto provider selection order in `zeroclaw_dual_chat.sh`: + +1. explicit `ZEROCLAW_PROVIDER` override, +2. `copilot` when `gh` auth is valid and Copilot billing endpoint is accessible, +3. `openai-codex` when a ZeroClaw auth profile exists, +4. `openrouter` when `OPENROUTER_API_KEY` is present. + +Observed on current machine: + +- GitHub API returned `404` for Copilot billing endpoint (no active Copilot subscription on this token), so fallback path is required for autonomous chat. + +## 5) Operations Loop + +1. Bootstrap configs and hardware probe. +2. Run one focused prompt in `rtc`. +3. Run one focused prompt in `zacus`. +4. Apply patches/tests per repo. +5. Open small PRs + issue links (one concern per PR). +6. Repeat. + +## 6) CI Workflow + +Workflow: + +- `.github/workflows/zeroclaw_dual_orchestrator.yml` + +Behavior: + +- path-filtered on orchestration scripts/spec files, +- shellcheck scripts, +- verify spec/todo files exist, +- concurrency enabled to cancel stale runs on same ref. + +## 7) External References (optimization decisions) + +- GitHub Actions path filters and trigger controls: + https://docs.github.com/actions/reference/workflows-and-actions/workflow-syntax +- GitHub Actions concurrency control (cancel stale runs): + https://docs.github.com/actions/using-jobs/using-concurrency +- PlatformIO remote unit test runner and local/remote split strategy: + https://docs.platformio.org/en/latest/plus/remote/unit-testing.html +- PySerial port metadata usage (`serial.tools.list_ports`) for stable device targeting: + https://pyserial.readthedocs.io/en/latest/tools.html diff --git a/specs/zeroclaw_dual_hw_todo.md b/specs/zeroclaw_dual_hw_todo.md new file mode 100644 index 0000000..95aed68 --- /dev/null +++ b/specs/zeroclaw_dual_hw_todo.md @@ -0,0 +1,36 @@ +# TODO: Dual Hardware Autonomy (RTC + Zacus) + +Last updated: 2026-02-21 + +## Phase A: Local Baseline + +- [ ] Run `tools/ai/zeroclaw_dual_bootstrap.sh` and capture hardware snapshot. +- [ ] Validate `tools/ai/zeroclaw_dual_chat.sh rtc -m ""`. +- [ ] Validate `tools/ai/zeroclaw_dual_chat.sh zacus -m ""`. +- [ ] Record detected ports and map preferred role per board. + +## Phase B: Repo Specs and PR Cadence + +- [ ] Create/refresh one issue in `RTC_BL_PHONE` for ZeroClaw-assisted hardware loop. +- [ ] Create/refresh one issue in `le-mystere-professeur-zacus` for ZeroClaw-assisted hardware loop. +- [ ] Open one small PR per repo focused on one gate (build, tests, hardware smoke, docs). +- [ ] Require code review pass before merge (`gh pr review --approve` only after checks). + +## Phase C: Autonomy + Cost Optimization + +- [ ] Keep prompts short, target one repo at a time. +- [ ] Use provider auto-fallback (`copilot` -> `openai-codex` -> `openrouter`) to avoid dead sessions. +- [ ] Add repo-level path filters in workflows to avoid expensive irrelevant runs. +- [ ] Use `workflow_dispatch` for hardware-required jobs to avoid noisy CI failures. + +## Phase D: Hardware Robustness + +- [ ] Add serial-port resolver step before every upload/flash action. +- [ ] Fail fast if no expected USB device is detected. +- [ ] Archive logs per run for replayability (`artifacts//...`). + +## Exit Criteria + +- [ ] Both repos can be targeted with one command (`rtc` or `zacus`) without workspace leakage. +- [ ] Hardware discovery passes before action on connected boards. +- [ ] At least one successful PR cycle completed per repo with this orchestration path. diff --git a/tools/ai/zeroclaw_dual_bootstrap.sh b/tools/ai/zeroclaw_dual_bootstrap.sh new file mode 100755 index 0000000..0f059f0 --- /dev/null +++ b/tools/ai/zeroclaw_dual_bootstrap.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +set -euo pipefail + +ZEROCLAW_BIN="${ZEROCLAW_BIN:-/Users/cils/Documents/Lelectron_rare/Kill_LIFE/zeroclaw/target/release/zeroclaw}" +RTC_REPO="/Users/cils/Documents/Lelectron_rare/RTC_BL_PHONE" +ZACUS_REPO="/Users/cils/Documents/Lelectron_rare/le-mystere-professeur-zacus" +HARDWARE_ONLY=0 + +usage() { + cat <] [--zacus ] [--zeroclaw-bin ] [--hardware-only] + +Bootstrap local ZeroClaw workspace profiles for both repos and run hardware discovery. +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --rtc) + RTC_REPO="${2:-}"; shift 2 ;; + --zacus) + ZACUS_REPO="${2:-}"; shift 2 ;; + --zeroclaw-bin) + ZEROCLAW_BIN="${2:-}"; shift 2 ;; + --hardware-only) + HARDWARE_ONLY=1; shift ;; + -h|--help) + usage; exit 0 ;; + *) + echo "Unknown argument: $1" >&2 + usage + exit 1 ;; + esac +done + +if [[ ! -x "$ZEROCLAW_BIN" ]]; then + if command -v zeroclaw >/dev/null 2>&1; then + ZEROCLAW_BIN="$(command -v zeroclaw)" + else + echo "zeroclaw binary not found." >&2 + exit 1 + fi +fi + +if [[ "$HARDWARE_ONLY" == "1" ]]; then + "$ZEROCLAW_BIN" hardware discover + exit 0 +fi + +write_repo_config() { + local repo="$1" + mkdir -p "$repo/.zeroclaw" + mkdir -p "$repo/.zeroclaw/legacy" + + # If a previous run created a root ZeroClaw profile, archive it so + # ZEROCLAW_WORKSPACE resolves to repo-local .zeroclaw/config.toml. + if [[ -f "$repo/config.toml" ]] && rg -q '^default_provider =|^\[autonomy\]' "$repo/config.toml"; then + mv "$repo/config.toml" "$repo/.zeroclaw/legacy/config.root.auto.toml" + fi + if [[ -d "$repo/workspace" && -d "$repo/workspace/memory" && -d "$repo/workspace/state" ]]; then + mv "$repo/workspace" "$repo/.zeroclaw/legacy/workspace.root.auto" + fi + + cat > "$repo/.zeroclaw/config.toml" <<'TOML' +default_provider = "openai-codex" +default_temperature = 0.2 + +[memory] +backend = "sqlite" +auto_save = true +embedding_provider = "none" + +[autonomy] +level = "supervised" +workspace_only = true +allowed_commands = ["git", "gh", "ls", "cat", "rg", "sed", "awk", "find", "make", "python3", "bash", "sh", "pio"] +forbidden_paths = ["/etc", "/root", "/home", "/usr", "/bin", "/sbin", "/lib", "/opt", "/boot", "/dev", "/proc", "/sys", "/var", "/tmp", "~/.ssh", "~/.gnupg", "~/.aws", "~/.config"] +max_actions_per_hour = 40 +max_cost_per_day_cents = 150 +require_approval_for_medium_risk = true +block_high_risk_commands = true +TOML +} + +write_repo_config "$RTC_REPO" +write_repo_config "$ZACUS_REPO" + +ZEROCLAW_WORKSPACE="$RTC_REPO" "$ZEROCLAW_BIN" status >/dev/null +ZEROCLAW_WORKSPACE="$ZACUS_REPO" "$ZEROCLAW_BIN" status >/dev/null + +"$ZEROCLAW_BIN" hardware discover +echo "Bootstrap complete." +echo "- RTC workspace: $RTC_REPO" +echo "- Zacus workspace: $ZACUS_REPO" +echo "Provider strategy:" +echo "1) export ZEROCLAW_PROVIDER=copilot (requires GitHub Copilot subscription)" +echo "2) zeroclaw auth login --provider openai-codex --device-code" +echo "3) export OPENROUTER_API_KEY=... and use ZEROCLAW_PROVIDER=openrouter" diff --git a/tools/ai/zeroclaw_dual_chat.sh b/tools/ai/zeroclaw_dual_chat.sh new file mode 100755 index 0000000..c2e6eaa --- /dev/null +++ b/tools/ai/zeroclaw_dual_chat.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -euo pipefail + +ZEROCLAW_BIN="${ZEROCLAW_BIN:-/Users/cils/Documents/Lelectron_rare/Kill_LIFE/zeroclaw/target/release/zeroclaw}" +RTC_REPO="/Users/cils/Documents/Lelectron_rare/RTC_BL_PHONE" +ZACUS_REPO="/Users/cils/Documents/Lelectron_rare/le-mystere-professeur-zacus" + +usage() { + cat < -m "message" + $(basename "$0") --interactive + $(basename "$0") --hardware +USAGE +} + +if [[ $# -lt 2 ]]; then + usage + exit 1 +fi + +target="$1" +shift + +case "$target" in + rtc) workspace="$RTC_REPO" ;; + zacus) workspace="$ZACUS_REPO" ;; + *) workspace="$target" ;; +esac + +if [[ ! -d "$workspace" ]]; then + echo "Workspace not found: $workspace" >&2 + exit 1 +fi + +if [[ ! -x "$ZEROCLAW_BIN" ]]; then + if command -v zeroclaw >/dev/null 2>&1; then + ZEROCLAW_BIN="$(command -v zeroclaw)" + else + echo "zeroclaw binary not found." >&2 + exit 1 + fi +fi + +if [[ "${1:-}" == "--hardware" ]]; then + ZEROCLAW_WORKSPACE="$workspace" "$ZEROCLAW_BIN" hardware discover + exit 0 +fi + +resolve_provider() { + if [[ -n "${ZEROCLAW_PROVIDER:-}" ]]; then + echo "$ZEROCLAW_PROVIDER" + return 0 + fi + + if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then + if gh api /user/copilot/billing --silent >/dev/null 2>&1; then + local gh_token + gh_token="$(gh auth token)" + export API_KEY="$gh_token" + echo "copilot" + return 0 + fi + fi + + if "$ZEROCLAW_BIN" auth list 2>/dev/null | rg -q "openai-codex:"; then + echo "openai-codex" + return 0 + fi + + if [[ -n "${OPENROUTER_API_KEY:-}" ]]; then + export API_KEY="$OPENROUTER_API_KEY" + echo "openrouter" + return 0 + fi + + cat <<'ERR' >&2 +No provider credentials detected. +- Option 1: ZEROCLAW_PROVIDER=copilot with GitHub Copilot subscription + gh auth login +- Option 2: zeroclaw auth login --provider openai-codex --device-code +- Option 3: export OPENROUTER_API_KEY=... and ZEROCLAW_PROVIDER=openrouter +ERR + return 1 +} + +export ZEROCLAW_WORKSPACE="$workspace" +provider="$(resolve_provider)" +echo "[zeroclaw-dual-chat] workspace=$workspace provider=$provider" + +if [[ "${1:-}" == "--interactive" ]]; then + exec "$ZEROCLAW_BIN" agent -p "$provider" +fi + +if [[ "${1:-}" != "-m" || -z "${2:-}" ]]; then + usage + exit 1 +fi + +exec "$ZEROCLAW_BIN" agent -p "$provider" -m "$2"