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
28 changes: 28 additions & 0 deletions .devcontainer/pi-config/models.json
Original file line number Diff line number Diff line change
@@ -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" }
]
}
}
}
5 changes: 5 additions & 0 deletions .devcontainer/pi-config/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"defaultProvider": "omniroute",
"defaultModel": "auto-fastest",
"lastChangelogVersion": "0.84.2"
}
13 changes: 12 additions & 1 deletion .devcontainer/post-create-cmd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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]}"
Expand Down Expand Up @@ -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


Expand Down
28 changes: 27 additions & 1 deletion .devcontainer/skills/codespace-persistent-symlinks/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<name>/`
(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:
Expand Down Expand Up @@ -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.
Expand Down
24 changes: 23 additions & 1 deletion .devcontainer/start-hermes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/devcontainer-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
bash .devcontainer/skills/ci-lint-check/scripts/ci_lint_check.sh
Loading