5.8 KiB
namespace, tags, last_updated, confidence
| namespace | tags | last_updated | confidence | ||||
|---|---|---|---|---|---|---|---|
| work |
|
2026-05-14 | 1.0 |
Personal OS — Self-Modification Guide
How Eagle (and Alex via Claude Code) can safely evolve the system without breaking it.
Rule: Always show a diff/draft first, confirm, then apply. Commit the vault after.
What Eagle Can Change (no approval needed — show draft first)
1. Prompt files (~/Developer/personal-os/agent/prompts/*.md)
Eagle reads and writes its own prompts. When Alex says "add X to the morning brief" or "change how inbox triage handles Y":
- Read the current prompt:
~/Developer/personal-os/agent/prompts/<name>.md - Draft the change, show the diff in Zulip
- On confirmation: write the file
- Commit:
cd ~/Developer/personal-os && git add agent/prompts/<name>.md && git commit -m "[YYYY-MM-DD] prompt: <description>"
Do not change executor-bug-fix.md without explicit confirmation — it controls autonomous code changes.
2. SOUL.md (~/.hermes/SOUL.md)
Eagle's identity, rules, and operating context. Eagle can propose changes to:
- Allowed vault write paths
- Focus gating rules
- Channel routing
Same flow: draft → confirm → write → no vault commit needed (SOUL.md is outside vault).
3. ActivityWatch project rules (~/Developer/personal-os/aw-projects.json)
To classify a new project as work or personal:
- Read current
aw-projects.json - Add entry with
project,namespace(omit for work,"personal"for pet projects), andrules - Show draft, confirm, write
- Commit:
cd ~/Developer/personal-os && git add aw-projects.json && git commit -m "[YYYY-MM-DD] aw: classify <project>"
The namespace: "personal" field excludes a project from work activity in status.md and activity_daily.
4. Vault knowledge pages (~/obsidian/wiki/, ~/obsidian/work/wiki/)
Eagle does not write to wiki/ — that's the wiki-ingest job (22:00 launchd). But Eagle can write to:
~/obsidian/work/projects/SLUG.md— project notes~/obsidian/work/decisions/YYYY-MM-TOPIC.md— decisions~/obsidian/personal/— personal notes~/obsidian/family/— family docs
After writing: cd ~/obsidian && git add -A && git commit -m "[YYYY-MM-DD] <description>"
5. Correction logging (corrections_log table)
Eagle silently logs a row whenever Alex pushes back on a suggestion. No approval needed — this is continuous background instrumentation, not a visible change.
INSERT INTO corrections_log (date, week_number, source, original_plan, correction, deferred_gids, reason_tag)
VALUES (current_date, EXTRACT(WEEK FROM current_date)::int,
'<source>', '<original>', '<user_message>',
ARRAY[<gids>]::text[], '<reason_tag>');
What Requires Approval (always confirm explicitly)
Schema changes (PostgreSQL)
New tables, columns, or indexes require a SQL migration. Pattern:
- Draft the SQL in Zulip (or Mattermost, post-migration), explain the purpose
- On "go": run via
psql personal_os -c "..." - Update
~/obsidian/wiki/personal-os-schema.md(this is auto-generated at 22:00, but a manual update is fine)
Current tables: tasks, stories, task_edges, sync_state, task_annotations, activity_daily, corrections_log, executor_runs.
New Hermes cron jobs
Add to ~/.hermes/config.yaml. Format:
crons:
- id: my-new-job
schedule: "30 9 * * 1-5"
prompt: |
Run: ~/Developer/personal-os/agent/prompts/my-prompt.md
channel: "#channel-name"
Must confirm before writing — a broken cron syntax silently prevents Hermes from starting.
New launchd agents (~/Library/LaunchAgents/)
New plist files. Rarely needed — only if a job requires Mac filesystem access and can't run inside Hermes (e.g., wiki-ingest). Confirm before writing and before loading with launchctl.
Executor prompt (executor-bug-fix.md)
Controls autonomous code changes. Changes here need explicit approval because a mistake could cause the Executor to behave incorrectly on real PRs.
Hermes model or MCP configuration (~/.hermes/config.yaml)
Model upgrades, new MCP servers. Show the diff; confirm before writing.
How generate-status.js Gets Extended
When a new data signal should appear in status.md:
- Add a SQL query function (e.g.,
getNewSignal()) - Add a render function (e.g.,
renderNewSignal(data)) - Add both to
main(): Promise.all for the query, the render call inparts - Test:
node ~/Developer/personal-os/generate-status.jsand read~/context/status.md - Commit
Never edit generate-status.js to change how Asana sync works — that's sync.js territory.
Debugging the System
| Symptom | Check |
|---|---|
| status.md is stale (> 40 min old) | stat -f "%Sm" ~/context/status.md — if old, run bash ~/scripts/run-pipeline.sh |
| Hermes cron didn't fire | Check heartbeat: tail -20 ~/Developer/personal-os/logs/heartbeat.log |
| Eagle gave wrong Asana data | The DB may be stale — run node ~/Developer/personal-os/sync.js manually |
| activity_daily missing today | generate-status.js runs upsert — check select * from activity_daily where bucket_day = current_date |
| corrections_log empty | Eagle only inserts on pushback — expected to be sparse |
| Executor in stuck state | SELECT id, state, updated_at FROM executor_runs WHERE state NOT IN ('complete','abandoned'); |
Safe Operations Checklist
Before making any system change:
- Read the current file/config before proposing a change
- Show the diff, not just a description
- Wait for explicit confirmation
- Apply the change
- Verify: run the component or check output
- Commit if in a git repo (
personal-osorobsidian)
See Also
- personal-os-architecture — full system map
- personal-os-agent-rules — Eagle's rules and channel routing
- personal-os-purpose — why this system exists