From 4148d9bb92005438881cd511ef31bd9c802ed7d1 Mon Sep 17 00:00:00 2001 From: pokemanricko Date: Fri, 21 Aug 2026 07:39:09 +0000 Subject: [PATCH] refactor: persist pi-agent LM config across rebuilds + pre-install pi-agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Track pi-agent LM config under .devcontainer/pi-config/{models,settings}.json (renamed from firstmate/; it is pi's config, not the skill's) pointed at the local OmniRoute relay, symlinked into ~/.pi/agent/ - start-hermes.sh §5.6 relinks those config files on every Codespace start (pi writes its own stub on first launch) so they survive rebuilds - post-create-cmd.sh: pre-install pi-agent (PyAgent) via npm so it's available on every fresh Codespace (like omniroute/modelrelay) for firstmate-bridge - codespace-persistent-symlinks/SKILL.md documents the per-file symlink exception this relies on (mixed-state runtime dirs: ~/.pi/agent/) Note: the firstmate-bridge skill itself is NOT vendored here — installed live via its one-shot install.sh and discovered from ~/.hermes/skills, keeping this repo free of upstream third-party skill/CI noise. Verified: ci-lint-check ALL CHECKS PASSED; Greptile P1 fixed (settings.json defaultProvider x -> omniroute); live scout dispatch proved the pipeline E2E. --- .devcontainer/pi-config/models.json | 28 +++++++++++++++++++ .devcontainer/pi-config/settings.json | 5 ++++ .devcontainer/post-create-cmd.sh | 13 ++++++++- .../codespace-persistent-symlinks/SKILL.md | 28 ++++++++++++++++++- .devcontainer/start-hermes.sh | 24 +++++++++++++++- .github/workflows/devcontainer-ci.yml | 12 +++++++- 6 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 .devcontainer/pi-config/models.json create mode 100644 .devcontainer/pi-config/settings.json diff --git a/.devcontainer/pi-config/models.json b/.devcontainer/pi-config/models.json new file mode 100644 index 0000000..2aed60d --- /dev/null +++ b/.devcontainer/pi-config/models.json @@ -0,0 +1,28 @@ +{ + "providers": { + "omniroute": { + "baseUrl": "http://localhost:20128/v1", + "api": "openai-completions", + "apiKey": "***", + "compat": { + "supportsDeveloperRole": false, + "supportsReasoningEffort": false + }, + "models": [ + { "id": "auto-fastest" } + ] + }, + "modelrelay": { + "baseUrl": "http://localhost:7352/v1", + "api": "openai-completions", + "apiKey": "no-key-needed", + "compat": { + "supportsDeveloperRole": false, + "supportsReasoningEffort": false + }, + "models": [ + { "id": "auto-fastest" } + ] + } + } +} \ No newline at end of file diff --git a/.devcontainer/pi-config/settings.json b/.devcontainer/pi-config/settings.json new file mode 100644 index 0000000..4a8d19c --- /dev/null +++ b/.devcontainer/pi-config/settings.json @@ -0,0 +1,5 @@ +{ + "defaultProvider": "omniroute", + "defaultModel": "auto-fastest", + "lastChangelogVersion": "0.84.2" +} \ No newline at end of file diff --git a/.devcontainer/post-create-cmd.sh b/.devcontainer/post-create-cmd.sh index aa9c293..5c62b44 100755 --- a/.devcontainer/post-create-cmd.sh +++ b/.devcontainer/post-create-cmd.sh @@ -6,6 +6,7 @@ MODELRELAY_VERSION=1.18.0 OLLAMA_VERSION=0.32.9 NODE_VERSION=24.18.0 MNEMON_VERSION=0.1.17 +PI_AGENT_VERSION=0.84.2 SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_PATH="${BASH_SOURCE[0]}" @@ -240,7 +241,17 @@ if command -v omniroute &>/dev/null; then setsid /usr/local/bin/omniroute >> /tmp/omniroute.log 2>&1 & fi else - echo "[$SCRIPT_NAME] omniroute not found, skipping start" + echo "[$SCRIPT_NAME] omniroute not found, skipping start" +fi + + +# Install Pi-agent for firstmate-bridge crewmate harness +echo "[$SCRIPT_NAME] Installing Pi-agent..." +if command -v pi &>/dev/null; then + echo "[$SCRIPT_NAME] pi already installed: $(pi --version 2>&1 | head -1)" +else + sudo npm install -g --ignore-scripts @earendil-works/pi-coding-agent@${PI_AGENT_VERSION} + echo "[$SCRIPT_NAME] pi-agent installed" fi diff --git a/.devcontainer/skills/codespace-persistent-symlinks/SKILL.md b/.devcontainer/skills/codespace-persistent-symlinks/SKILL.md index e55153d..a29a2e6 100644 --- a/.devcontainer/skills/codespace-persistent-symlinks/SKILL.md +++ b/.devcontainer/skills/codespace-persistent-symlinks/SKILL.md @@ -17,12 +17,33 @@ target: .devcontainer/memories/ (git-tracked: MEMORY.md, USER.md, .gitig .gitignore: *.lock *.log (Hermes writes lock/log beside the memories) ``` -- Whole-folder symlink (skills-style), NOT per-file links: single point of truth, +- Whole-folder symlink (skills-style) is the default: single point of truth, no copy-back between `~/.hermes` and the repo, edits flow both ways instantly. + Use per-file symlinks only for mixed-state runtime dirs (see below) — never as a + general substitute for the whole-folder pattern. - Hermes memory writes are symlink-safe: `atomic_replace` (utils.py) re-solves the symlink and writes the real git file — no Hermes change needed. - Memory path is `~/.hermes/memories/`, NOT `profiles/default/memories/`. +## Per-file symlink exception (mixed-state runtime dirs) + +The whole-folder rule assumes the runtime dir holds ONLY durable, persistable +state. Some tools keep durable config in a dir that also holds private/runtime +state (e.g. `~/.pi/agent/` mixes `models.json`/`settings.json` with `auth.json`, +`sessions/`, lock files). Symlinking the whole folder would persist secrets and +ephemeral state into the repo — wrong. Instead: + +1. Track ONLY the durable config files under `.devcontainer//` + (e.g. `.devcontainer/pi-config/models.json`, `settings.json`). +2. Replace each runtime file with a symlink to the tracked copy: + `ln -sf "$TRACKED/$f" "$RUNTIME/$f"` — replace a plain file, but never clobber + an existing symlink that already resolves to the target. +3. Add an idempotent guard in `start-hermes.sh` (repair-guard style) that re-links + them on every boot, because the tool writes its own stub on first launch: + `if [ "$(readlink -f "$runtime")" != "$(readlink -f "$tracked")" ]; then rm -f "$runtime"; ln -s "$tracked" "$runtime"; fi` + +This keeps secrets/ephemeral state out of git while surviving rebuilds. + ## Placement decision (Option A — validated 2026-08) Two layers, keep `start-hermes.sh` trivial: @@ -64,6 +85,11 @@ self-check.sh). Run locally: ONLY the injected marker line (perl/grep), never tail-trim — you can truncate a real directive and the file shrinks unexpectedly (observed 1297B → 504B). - Verify the guard against ALL THREE cases, not just the happy path. +- **Mixed-state runtime dirs need per-file symlinks, not whole-folder.** `~/.pi/agent/` + is the canonical case: symlink only `models.json`/`settings.json` into + `.devcontainer/pi-config/`; never symlink the whole dir (it drags in `auth.json`, + `sessions/`, locks). Add a `start-hermes.sh` guard to re-link past the tool's own + first-launch stub, and verify the guard replaces a plain stub with the symlink. ## Sync note (wiki) - `persistent-memory-proposal.md` — reference/proposal doc vs this procedural skill. diff --git a/.devcontainer/start-hermes.sh b/.devcontainer/start-hermes.sh index 97f7d34..b51dc5e 100755 --- a/.devcontainer/start-hermes.sh +++ b/.devcontainer/start-hermes.sh @@ -153,7 +153,29 @@ else echo "[$SCRIPT_NAME] Memories folder symlink already correct" fi -# 5.6. Starting keepalive (idempotent) — keeps codespace from idle-shutting-down +# 5.6. Pi-agent LM config persistence — REPAIR GUARD ONLY. +# The pi crewmate (firstmate-bridge skill) needs ~/.pi/agent/{models,settings}.json +# pointed at the local OmniRoute relay. Those files are tracked under +# .devcontainer/pi-config/ so they survive rebuilds; this guard (re)links them. +# pi writes its own stub on first launch, so we replace a plain file but never +# clobber an existing symlink that already resolves to the tracked target. +PI_CONF_TRACKED="$WORKSPACE_ROOT/.devcontainer/pi-config" +PI_AGENT_DIR="$HOME/.pi/agent" +if [ -d "$PI_CONF_TRACKED" ]; then + mkdir -p "$PI_AGENT_DIR" + for f in models.json settings.json; do + tracked="$PI_CONF_TRACKED/$f" + [ -f "$tracked" ] || continue + runtime="$PI_AGENT_DIR/$f" + if [ "$(readlink -f "$runtime" 2>/dev/null)" != "$(readlink -f "$tracked")" ]; then + rm -f "$runtime" + ln -s "$tracked" "$runtime" + echo "[$SCRIPT_NAME] Linked pi config: $runtime -> $tracked" + fi + done +fi + +# 5.7. Starting keepalive (idempotent) — keeps codespace from idle-shutting-down if ! pgrep -f "keepalive.sh" > /dev/null; then echo "[$SCRIPT_NAME] Starting keepalive..." setsid nohup "${SCRIPT_DIR}/keepalive.sh" >> /tmp/keepalive.log 2>&1 & diff --git a/.github/workflows/devcontainer-ci.yml b/.github/workflows/devcontainer-ci.yml index 9f81a0f..b4d986c 100644 --- a/.github/workflows/devcontainer-ci.yml +++ b/.github/workflows/devcontainer-ci.yml @@ -64,6 +64,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + - name: run post-create-cmd.sh run: bash ./.devcontainer/post-create-cmd.sh @@ -145,9 +150,14 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + - name: Run local CI lint check script run: | # Install markdownlint-cli (required by script) npm install -g markdownlint-cli # Run the canonical validation script - bash .devcontainer/skills/ci-lint-check/scripts/ci_lint_check.sh \ No newline at end of file + bash .devcontainer/skills/ci-lint-check/scripts/ci_lint_check.sh