Merge remote-tracking branch 'origin/main'

This commit is contained in:
Syncthing
2026-05-29 10:03:30 +00:00
46 changed files with 3909 additions and 3 deletions
+87
View File
@@ -0,0 +1,87 @@
---
title: Media Pipeline Pitfalls
created: '2026-05-23'
updated: '2026-05-27'
type: tech
namespace: family
tags: [media-pipeline, pitfalls, torrents, infra]
related:
- "[[tech/arr-stack-kraken]]"
- "[[concepts/watchlist-automation]]"
---
# Media Pipeline Pitfalls
## Torrents — Do Not Rename Files
Never rename torrent source files or folders while a torrent is active.
Transmission tracks files by path. Renaming breaks the association and
causes re-download or stalled seeding.
Правильный порядок (когда файл уже лежит в нужном месте, но с другим кейсом):
1. Переименовать файл/папку на диске (Linux ext4 case-sensitive)
2. Вызвать `torrent-set-location` с новым путём + `move=false`
3. Запустить торрент на сидирование
## torrent-set-location Updates downloadDir
`torrent-set-location` in the Transmission RPC updates `downloadDir` in
the torrent metadata. Use this when moving completed files, not a manual
rename. Sequence:
1. Move files to new location on disk
2. Call `torrent-set-location` with the new path + `move=false`
3. Verify torrent goes back to seeding state
## Router Creates Subdirectories — Transmission downloadDir Stale
`router.py` creates subdirectory per title under the genre folder
(lowercase name, e.g. `/media/cartoons/бобик в гостях у барбоса/`).
Transmission still holds the old `downloadDir` pointing to the parent
(`/media/cartoons/`). Result: torrents show "No data found" error.
Fix:
1. Find the actual subdirectory on disk
2. Call `torrent-set-location` with the subdirectory path + `move=false`
3. Transmission verifies and resumes seeding
## Alpine Container — No Docker-in-Docker
`on-download-complete.sh` runs inside the Transmission Alpine container.
Docker CLI is not available there — using `docker run` inside the container
fails silently or errors. Python 3.14 IS available in the Alpine image.
Solution: `sync.py` (Python3, ~190 lines) in the same container:
- Reads `config.json` for sorting rules + credentials (same file as linker container)
- Queries TMDB for genre routing
- Creates hardlinks + calls Transmission RPC `torrent-set-location`
Location: `/srv/.../docker/media-pipeline/sync.py`
## Transmission download-dir — без /complete
download-dir: `/downloads` (не `/downloads/complete`).
incomplete-dir: `/downloads/incomplete` (enabled).
Менять через остановку Transmission → правка `settings.json` → запуск.
## media-router.sh — Telegram-уведомления
Скрипт-обёртка над `router.py`. Запускается из crontab каждые 10 минут.
- При `Nothing to do` — SILENT
- При активности — отправляет отчёт в Telegram Kraken (kraken_htpc_bot)
- `BOT_TOKEN` и `CHAT_ID` хардкодом в скрипте
## Удаление дубликатов из downloads/complete
После перемещения файлов в `/media/{movies,cartoons,series,...}/`:
1. Проверить, что файлы в movies — **реальные** (-rwx), не symlink'и
2. Проверить что inode разные (это копии, не один файл)
3. Удалить из `downloads/complete/`
4. Обновить путь в Transmission
## General Rules
- Never touch source files/folders (media-pipeline USER.md rule)
- `resolve-manual` is emergency-only
- NFO files: do not create manually; let the pipeline handle them
- Never delete test files from the pipeline project
+75
View File
@@ -0,0 +1,75 @@
---
namespace: work
tags: [system, agent, memory, architecture, ai]
created: '2026-05-18'
updated: '2026-05-18'
last_synced: '2026-05-18'
confidence: 0.85
sources:
- arXiv 2603.07670 (Memory for Autonomous LLM Agents, март 2026)
- zylos.ai AI Agent Memory Architectures survey (апрель 2026)
- wiki/raw/inbox/research-vault-strategy-memory-20260516.md
---
# Agent Memory Architecture
Когнитивная таксономия памяти для LLM-агентов, основанная на arXiv 2603.07670. Применительно к personal-os (Hermes).
## Три уровня памяти
| Уровень | Определение | Реализация в Hermes |
|---------|-------------|----------------------|
| **Episodic** | Сессионные логи — что произошло в конкретных взаимодействиях | SQLite + `session_search` ✅ |
| **Semantic** | Факты, предпочтения, постоянное знание | `MEMORY.md` + `USER.md` (нужна curation) |
| **Procedural** | Навыки, workflows, пошаговые паттерны | `skills/` ✅ |
## Ключевые выводы из arXiv 2603.07670
- **Gap между "есть память" и "нет памяти"** > gap между разными LLM backbone. Выбор памяти важнее выбора модели.
- **Без рефлексии агент деградирует:** в Generative Agents эксперименте — 48 ч без memory reflection → repetitive behavior.
- **Summarization drift:** после 3+ циклов сжатия критические инструкции теряются. Решение: vault как immutable store, не только MEMORY.md.
## Overflow Rule (MEMORY.md)
Запись в MEMORY.md — только короткие стабильные факты:
- Длина > 150 символов → перенести в vault, оставить pointer
- Содержит пошаговые инструкции → в `skills/`
- Детальный технический контекст → в `wiki/tech/`
```
MEMORY.md: "media-pipeline питфолы → wiki/tech/media-pipeline-pitfalls.md"
wiki/tech/media-pipeline-pitfalls.md: (полный разбор)
```
## 4-tier pipeline (LLM Wiki v2 / rohitg00)
Расширенная модель:
```
Working Memory → сырые наблюдения текущей сессии
Episodic Memory → сжатые саммари сессий
Semantic Memory → кросс-сессионные факты (wiki)
Procedural Memory → паттерны, workflows, skills
```
**Confidence scoring:** каждый факт имеет score (кол-во источников, свежесть, противоречия). Устаревает со временем.
**Event-driven automation:**
- New source → auto-ingest
- Session end → compress into observations
- On query → check if answer worth filing back
- On schedule → periodic lint
## Правило разграничения (Alex's personal-os)
```
MEMORY.md → SSH хосты, namespace rules, токены, конфиги (≤150 chars)
USER.md → предпочтения, стиль (редко меняется)
wiki/tech/ → технические питфолы, баг-паттерны (длинные, с контекстом)
personal/projects/ → статус проектов, дебаггинг-логи
skills/ → процедуры и workflows пошагово
```
## Связанные страницы
- [[personal-os-architecture]] — как память встроена в общий стек
- [[wiki-ingest-process]] — как сессии кристаллизуются в wiki
- [[personal-os-self-modification]] — как система эволюционирует
+113
View File
@@ -0,0 +1,113 @@
---
title: Autonomous Agent Safety Patterns
created: '2026-05-24'
updated: '2026-05-24'
type: concept
tags: [agent, security, architecture, executor, rules]
sources:
- wiki/concepts/executor-security-incident.md
- wiki/concepts/executor-orchestrator.md
- wiki/personal-os-agent-rules.md
confidence: high
related:
- "[[concepts/executor-security-incident]]"
- "[[concepts/executor-orchestrator]]"
- "[[personal-os-agent-rules]]"
---
# Autonomous Agent Safety Patterns
Design principles for autonomous LLM agents distilled from the 2026-05-11
executor security incident. General enough to apply beyond the personal-os context.
## The Three Failure Modes (from incident)
### 1. Mandatory prompt steps that outrank modes
The executor wrote Asana comments during "recording-only" mode because the
worker prompt declared comment posting a *mandatory completion action* — not
subject to mode flags.
**Pattern:** Every completion action (write to external system, post comment,
send notification) must be guarded by a mode check that the agent cannot
override.
```
IF mode == "recording-only":
SKIP external writes
LOG "would have posted: ..." instead
```
### 2. Boundary policies that only cover exfiltration
The lethal-trifecta policy blocked HTTP to attacker domains after internal
MCP access. It did NOT block writes *to* internal systems (Asana).
**Pattern:** Separate the threat models:
- **Exfiltration** = data leaving to unauthorized destinations → block outbound
- **Unauthorized writes** = data going to authorized systems without approval → require
explicit confirmation gate per write type
These are different controls. A policy that only covers one leaves the other open.
### 3. High-level directives not propagated to sub-prompts
"Don't touch anything" was a session-level directive. The worker
sub-prompt (spawned per task) didn't inherit it — it ran its own
completion protocol.
**Pattern:** Mode flags must be passed explicitly to every spawned
sub-process/sub-prompt as a first-class parameter, not assumed from
session context.
## The Auto-Approve Table Pattern
From [[concepts/executor-orchestrator]]: instead of blanket trust or blanket
denial, classify actions by risk tier:
| Risk | Action type | Default |
|------|-------------|---------|
| Low | git, build, test, worktree | auto-approve |
| Medium | draft PR, push branch | auto-approve with log |
| High | post Asana comment, merge PR | require Alex confirmation |
| Blocked | autonomous Asana write | denied always |
This table lives in the orchestrator, not the worker. Workers *request*
actions; orchestrator decides.
## Least-Privilege Credential Design
From incident: `ASANA_API_KEY` was full-account CRUD (PATs are not granular).
One compromised agent → full Asana write access.
**Pattern:** Scope credentials to the minimum required operation:
- Read-only keys for read-only agents
- Write keys injected only at the moment of approved write
- Never persist write credentials in always-on agent environments
## Audit Before Autonomous
The incident ran 18 PRs and 5 Asana comments before detection. Detection only
happened because Alex checked manually.
**Pattern:** Autonomous runs should produce an observable audit trail that
can be reviewed without running the agent:
- Structured log per run (not just stdout)
- Diff-friendly format (what was written, to where, at what time)
- Periodic summary posted to a channel Alex monitors
## Summary: Checklist for New Autonomous Agents
- [ ] Every external write is behind a mode-guard (can "recording-only" block it?)
- [ ] Exfiltration and unauthorized-write policies are separate controls
- [ ] Mode flags propagate explicitly to sub-prompts
- [ ] Auto-approve table is in the orchestrator, not the worker
- [ ] Credentials are scoped to minimum; write keys not always-on
- [ ] Each run produces a structured audit log
- [ ] Audit log goes somewhere Alex sees without hunting
## See Also
- [[concepts/executor-security-incident]] — incident post-mortem with full timeline
- [[concepts/executor-orchestrator]] — post-incident architecture (orchestrator pattern)
- [[personal-os-agent-rules]] — Eagle's specific rules derived from these patterns
@@ -0,0 +1,82 @@
---
title: Hermes Deployment Patterns
created: '2026-05-29'
updated: '2026-05-29'
type: concept
namespace: personal
tags: [hermes, architecture, agent, eagle, kraken, infra]
sources: []
confidence: medium
related:
- "[[tech/hermes-eagle-mac]]"
- "[[tech/hermes-docker-kraken]]"
- "[[tech/hermes-kraken-api]]"
- "[[personal-os-architecture]]"
---
# Hermes Deployment Patterns
Three distinct ways Hermes runs in the Personal OS ecosystem. Each serves
a different access model and client type.
## Pattern 1: Eagle Native (Mac M4)
**Where:** Eagle Mac M4 Max, native process (not Docker)
**Transport:** Zulip (Docker)
**Model backend:** `openclaw-claude-proxy` on port 3456 — wraps
`claude --print` as an OpenAI-compatible endpoint, preserving tool_use.
**Autostart:** launchd (`ai.claude-proxy.plist`)
**Why native:** Mac file system access, MCP tools (obsidian-mcp), and the
cron wiki-curation job all need full host access. Docker would require
volume mounts for every integration.
**Key pitfall:** `claude-proxy` wrapper script must `source ~/.hermes/.env`
explicitly — launchd does not inherit login session env. See
[[tech/hermes-eagle-mac]].
## Pattern 2: Kraken Docker (RPi5)
**Where:** Kraken RPi5, `docker run` (not compose)
**Transport:** Zulip (same instance or separate)
**Model backend:** Gemini or OpenRouter via Hermes gateway
**Autostart:** `--restart unless-stopped` on the container
**Why docker run, not compose:** explicit restart behavior; avoids accidental
`docker compose down` wipes. Config volume: `~/.hermes` on Kraken host.
See [[tech/hermes-docker-kraken]].
## Pattern 3: Kraken API Server (OpenAI-compat)
**Where:** Kraken RPi5, same hermes-kraken container
**Transport:** HTTPS via Cloudflare Tunnel (`kraken.qentra.top`)
**Model backend:** Gemini / OpenRouter (same gateway)
**Clients:** Android Aide app (BYOK → Custom Endpoint), any OpenAI SDK
**Why Cloudflare Tunnel:** no port-forwarding on home router required.
The tunnel terminates at cloudflared running with `network_mode: host`,
hitting `localhost:8642` directly.
See [[tech/hermes-kraken-api]].
## Comparison
| Dimension | Eagle Native | Kraken Docker | Kraken API |
|-----------|-------------|---------------|-----------|
| Model | claude-sonnet via proxy | Gemini/ORouter | Gemini/ORouter |
| Transport | Zulip | Zulip | HTTPS REST |
| Clients | Cron, MCP tools | Zulip bot clients | Mobile / OpenAI SDK |
| External access | No | No | Yes (CF Tunnel) |
| MCP/tools | Full (host access) | Docker volumes | Not applicable |
| Rate limits | Claude OAuth (proxy workaround) | API keys | API keys |
## Design Principle
Hermes deployments follow the client's access model:
- **Interactive/tool-heavy** → Eagle native (full host, MCP)
- **Always-on background** → Kraken Docker (low-power, 24/7)
- **Mobile / external** → Kraken API server (HTTPS, standard protocol)
This avoids running a single large instance with conflicting requirements.
See [[personal-os-architecture]] for the full system overview.
+76
View File
@@ -0,0 +1,76 @@
---
title: Hermes — Native (Eagle) vs Docker (Kraken) Deployment
created: '2026-05-22'
updated: '2026-05-22'
last_synced: '2026-05-22'
type: comparison
namespace: work
tags:
- hermes
- deployment
- mac
- kraken
- comparison
confidence: 0.9
sources:
- wiki/tech/hermes-eagle-mac.md
- wiki/tech/hermes-docker-kraken.md
---
# Hermes — Native (Eagle) vs Docker (Kraken)
Два способа запуска Hermes Agent: нативно на Mac M4 Max и в Docker на Raspberry Pi 5.
## Сравнение
| | Eagle (Mac M4 Max) | Kraken (RPi5) |
|--|-------------------|--------------|
| Запуск | `hermes gateway` (нативно) | `docker run` (не compose) |
| Транспорт | Zulip (в Docker) | Zulip (тот же) |
| Модель | openclaw-claude-proxy (`localhost:3456`) | openclaw-claude-proxy (аналогично) |
| Процесс-менеджер | launchd (`ai.claude-proxy`) | Docker `--restart=unless-stopped` |
| PATH в launchd | нужен явный `export PATH=...` | нет проблемы (Docker env) |
| Токен | `source ~/.hermes/.env` в wrapper | передаётся через `-e` флаг docker run |
| MCP | mcpvault (`/Users/admin/obsidian`) | mcpvault (`/vault` — mount) |
| Логи Claude-proxy | `~/.hermes/logs/claude-proxy.log` | Docker logs |
| Навыки | все Hermes skillsets | ограниченный набор (нет macOS tools) |
| HERMES_SKIP_CHOWN | не нужно | нужно (`-e HERMES_SKIP_CHOWN=1`) |
## Pitfalls Eagle (Mac M4 Max)
**openclaw-claude-proxy через launchd:**
1. launchd не наследует login-сессию → CLAUDE_CODE_OAUTH_TOKEN не виден → "Not logged in"
- Фикс: явный `source ~/.hermes/.env` в wrapper-скрипте
2. PATH не содержит `/opt/homebrew/bin` → Claude CLI не найден
- Фикс: явный `export PATH="/opt/homebrew/bin:..."` в wrapper-скрипте
3. Порт 3456 занят после перезагрузки:
- `lsof -ti :3456 | xargs kill -9 && launchctl unload && launchctl load ...`
4. `provider: openai` не существует в Hermes — нужно `custom`
5. `base_url` должен включать `/v1` (Hermes дописывает `/chat/completions`)
**Zulip Docker:**
- RabbitMQ 4.x Khepri WAL crash при переполнении диска → пользователи исчезают
- Фикс: `docker system prune` (освободить место) + пересоздать volume если повреждён
- Без log rotation логи заполняют Docker VM (~6 ГБ за несколько месяцев)
- Mac Docker Desktop sparse disk — `docker system prune` запускает compaction
## Pitfalls Kraken (Docker)
- `HERMES_SKIP_CHOWN=1` обязателен (RPi5 не имеет прав chown в контейнере)
- `--init` флаг обязателен (zombie reaping)
- `--network=host` для доступа к Zulip на том же хосте
- Vault монтируется через `-v /home/kraken/obsidian:/vault`
- Полный набор правил → [[tech/hermes-docker-kraken]]
## Общее для обоих
- Модель: `claude-sonnet-4-6` через openclaw-claude-proxy
- MCP: mcpvault (vault path разный, но tool API одинаковый)
- Zulip transport: организация `zulip.mallexxx.duckdns.org`
- Obsidian vault синхронизируется через git
## Связанные страницы
- [[tech/hermes-eagle-mac]] — детали настройки на Eagle
- [[tech/hermes-docker-kraken]] — детали Docker деплоя на Кракен
- [[personal-os-architecture]] — полная карта системы
- [[tech/kraken-network]] — сетевая топология
+82
View File
@@ -0,0 +1,82 @@
---
namespace: work
tags:
- agent
- memory
- wiki
- synthesis
- vault
created: '2026-05-19'
updated: '2026-05-19'
type: concept
confidence: 0.9
sources:
- wiki/concepts/agent-memory-architecture.md
- wiki/concepts/vault-strategy.md
- wiki/wiki-ingest-process.md
- wiki/personal-os-self-modification.md
---
# Knowledge Lifecycle — From Session to Permanent Memory
Синтез: как знание движется от рабочей сессии к постоянной памяти в personal-os. Объединяет [[concepts/agent-memory-architecture]], [[concepts/vault-strategy]] и [[wiki-ingest-process]].
## Общая схема
```
Событие/Разговор
Working Memory (контекст сессии, эфемерный)
│ session_search + SHA256 hash
Episodic Memory (SQLite sessions, сжатые саммари)
│ crystallization (wiki-curation cron 02:00)
Semantic Memory (wiki/ pages, постоянное знание)
│ wiki-ingest 22:00 (launchd, claude -p)
│ overflow rule (>150 chars → vault)
Procedural Memory (skills/, workflows)
```
## Три барьера кристаллизации
Не всё знание заслуживает кристаллизации. Барьеры:
| Уровень | Критерий | Место |
|---------|----------|-------|
| Session → Wiki | Painful to re-derive? Fits domain? Contains facts (not conversation)? | `wiki/` |
| Wiki → MEMORY.md | Стабильный факт, ≤150 символов? | `~/.hermes/MEMORY.md` |
| Any → Skills | Пошаговая процедура, повторяемая? | `~/.hermes/skills/` |
**Правило overflow:** MEMORY.md — только короткие стабильные факты. Детали → в wiki с pointer в MEMORY.md: `"media-pipeline питфолы → wiki/tech/media-pipeline-pitfalls.md"`.
## Три задачи vault (не смешивать)
1. **Vault Enrichment** — frontmatter + aliases + wikilinks на `personal/`, `family/`
2. **LLM Wiki** — crystallization sessions → `wiki/` (этот файл про это)
3. **Proactive Research** — агент генерирует гипотезы → `wiki/research-queue.md` → Saturday cron
## Риски деградации
- **Summarization drift:** после 3+ циклов сжатия теряются критические инструкции. Решение: vault как immutable store (не перезаписывать, только аппендить/обновлять dated sections).
- **Repetitive behavior:** без weekly reflection агент деградирует (Generative Agents, 48ч без рефлексии). Фикс: Retrospector каждую пятницу читает corrections_log.
- **Orphaned knowledge:** факт записан, но никогда не читается. Фикс: lint (orphan pages), провалидированные wikilinks.
## Инструменты в personal-os
| Инструмент | Функция |
|-----------|---------|
| wiki-curation cron (02:00) | Crystallization: sessions → wiki |
| wiki-ingest launchd (22:00) | Synthesis: raw/ symlinks → wiki pages |
| `session_search` | Episodic retrieval |
| obsidian MCP | Semantic retrieval |
| MEMORY.md | Fast facts, overflow pointers |
| research-queue.md | Proactive research agenda |
## Связанные страницы
- [[concepts/agent-memory-architecture]] — когнитивная таксономия памяти (episodic/semantic/procedural)
- [[concepts/vault-strategy]] — три задачи vault: enrichment, LLM wiki, proactive research
- [[wiki-ingest-process]] — hash-based incremental ingest из raw/ symlinks
- [[personal-os-self-modification]] — как система эволюционирует через корректировки
@@ -0,0 +1,107 @@
---
title: Multi-Agent Design Patterns
created: '2026-05-25'
updated: '2026-05-25'
type: concept
namespace: work
tags: [agent, architecture, system]
confidence: medium
sources:
- wiki/concepts/executor-orchestrator.md
- wiki/concepts/autonomous-agent-safety.md
- wiki/concepts/agent-memory-architecture.md
related:
- "[[concepts/executor-orchestrator]]"
- "[[concepts/autonomous-agent-safety]]"
- "[[concepts/agent-memory-architecture]]"
---
# Multi-Agent Design Patterns
Synthesis of recurring patterns across the personal-os multi-agent system
and related agent architectures. Distilled from [[concepts/executor-orchestrator]],
[[concepts/autonomous-agent-safety]], and [[concepts/agent-memory-architecture]].
## Pattern 1 — Orchestrator / Worker Split
**What:** One agent owns conversation state and decision authority
(orchestrator); spawns separate worker agents for discrete tasks.
**Why:** Tight coupling between conversational layer and execution layer
causes mid-task interruptions and breaks user flow. Separate concerns.
**In personal-os:** Eagle (orchestrator) → Executor (worker) via Zulip.
**In psychologist app:** Narrator (orchestrator/mediator) ← Analyst (worker).
**Key constraint:** Worker communicates only through structured messages;
orchestrator has the only escalation path to the human.
## Pattern 2 — Scope Boundary + Auto-Approve Table
**What:** Predefine which actions a worker may take autonomously and which
require escalation. Publish the table explicitly.
**Why:** Autonomous agents fail catastrophically when they expand scope
unexpectedly (see [[concepts/executor-security-incident]]). Explicit tables
make failure modes visible.
**Implementation:** Message-type-based approval table. Unknown message types
default to escalation, never to silent proceed.
| Risk Level | Agent Action |
|---|---|
| Low (read, build, test) | Auto-approve |
| Medium (write external, open PR) | Auto-approve with logging |
| High (comment on others, merge) | Always escalate |
| Unknown | Always escalate |
## Pattern 3 — Memory Layer Separation
**What:** Separate in-session state (working memory) from cross-session
knowledge (semantic memory) from immutable facts (procedural/episodic).
**Why:** Agents that blur these layers either hallucinate stable facts or
fail to retain important session-to-session knowledge.
**In personal-os:** status.md (working) / wiki pages (semantic) /
SCHEMA.md + vault-filling-guide (procedural). See [[concepts/agent-memory-architecture]].
**Pattern rule:** Never update semantic memory from within an active session.
Crystallize post-session. Never trust working memory as a source of truth
for facts (always re-derive from semantic layer at session start).
## Pattern 4 — Role Specialization over Generalization
**What:** Instead of one agent with a long system prompt covering all roles,
split into agents with narrow, non-overlapping responsibilities.
**Why:** LLMs produce better outputs when role context is tight. Conflated
roles lead to persona drift ("is it being analytical or empathetic right now?").
**In psychologist app:** Analyst stays in 3rd-person analytical mode;
Narrator stays in 1st-person user-facing mode. Neither crosses the boundary.
**Trade-off:** Coordination overhead (structured message passing between
agents). Worthwhile when roles genuinely conflict (analysis vs. empathy).
## Pattern 5 — Deterministic Algorithm for Predictable Steps
**What:** Identify steps that look like LLM tasks but are actually
deterministic (sequencing, routing, counting) and implement them as code,
not as LLM calls.
**Why:** LLMs are expensive and non-deterministic. Steps like "show next
question in list" do not benefit from LLM reasoning and introduce failure modes.
**Examples:**
- Question delivery in psychologist app: batch generated by Analyst,
sequenced by UI algorithm — not an LLM call per question
- Approval table in Executor: code switch on message type, not LLM judgment
- Vault namespace routing: rules in tech/vault-namespace, not agent inference
## See Also
- [[concepts/executor-orchestrator]] — concrete orchestrator/worker implementation
- [[concepts/autonomous-agent-safety]] — safety checklist derived from incidents
- [[concepts/agent-memory-architecture]] — memory taxonomy for LLM agents
- [[entities/psychologist-app]] — dual-agent architecture (patterns 1 and 4)
+71
View File
@@ -0,0 +1,71 @@
---
title: Multi-Host Cron Topology
created: '2026-05-27'
updated: '2026-05-27'
type: concept
namespace: personal
tags: [system, agent, eagle, kraken, pipeline, sync]
confidence: medium
sources: []
---
# Multi-Host Cron Topology
Synthesis of how scheduled jobs are distributed across Eagle and Kraken.
Co-occurs in [[tech/hermes-eagle-mac]], [[tech/hermes-docker-kraken]],
[[concepts/watchlist-automation]], and [[personal-os-agent-rules]].
## Design Principle
Jobs run on the host closest to their data. Eagle handles
agent-intelligence tasks (briefs, wiki curation). Kraken handles
media-pipeline tasks (watchlist, arr stack). Duplication is a bug.
## Eagle (Mac M4) — Agent Jobs
| Job | Schedule | Purpose |
|-----|----------|---------|
| `wiki-curation` | daily 02:00 | Crystallise vault → wiki (llm-wiki skill) |
| `vault-enrichment` | daily 03:00 | Enrich personal/family notes |
| `cross-enrichment` | daily 04:00 | Cross-namespace enrichment + memory curation |
| `proactive-research` | Sat 05:00 | Proactive gap research |
| `data-pipeline` | 30min, workdays | sync.js + generate-status.js → status.md |
| `daily-brief` | workday morning | Morning brief to Zulip |
| `inbox-triage` | workday noon | Asana inbox triage |
| `weekly-review` | Fri evening | Weekly review |
Eagle crons are Hermes agent jobs — require LLM, vault access,
Asana context. All run via Hermes native (not Docker). See
[[tech/hermes-eagle-mac]].
## Kraken (RPi5) — Pipeline Jobs
| Job | Schedule | Purpose |
|-----|----------|---------|
| `watchlist-nightly` | daily 01:00 | process-thumbs + resolve + sync-down/up |
| `watchlist-discover` | Sun 09:00 | KP API top films → watchlist Новинки |
| `on-download-complete` | event-triggered | media-pipeline: sort + Jellyfin rescan |
Kraken crons are shell scripts or Python3 — deterministic, no LLM.
Run via Hermes cron on Kraken Hermes instance (port 8642, Docker).
See [[tech/hermes-docker-kraken]] and [[concepts/watchlist-automation]].
## Migration History
- 2026-05-27: `watchlist-nightly` and `watchlist-discover` moved from
Eagle to Kraken. Eagle was running them via SSH to Kraken which was
fragile and required Eagle's network path to Kraken to be stable.
Running on Kraken directly eliminates that dependency.
## Pitfall: Job Duplication
If the same shell-script job runs on both Eagle and Kraken, the
Radarr/Sonarr API sees double requests. Symptom: duplicate ⬇️
entries or double-adds. Check: `hermes cronjob list` on both hosts.
## See Also
- [[personal-os-agent-rules]] — Eagle's rules and allowed writes
- [[tech/hermes-eagle-mac]] — Eagle Hermes config
- [[tech/hermes-docker-kraken]] — Kraken Hermes Docker setup
- [[concepts/watchlist-automation]] — Full watchlist flow
+114
View File
@@ -0,0 +1,114 @@
# obsidian-mcp-wrapper
> **Файл**: `~/scripts/obsidian-mcp-wrapper.js`
> **Назначение**: прокси-обёртка над `obsidian-mcp`, решает четыре системных бага
---
## Проблемы, которые решает
### 1. ZodError при инициализации (obsidian-mcp v1.0.6)
`obsidian-mcp` падал с ZodError сразу после запуска. Причина: Hermes отправляет
`notifications/initialized` с полем `"id": null`, а obsidian-mcp v1.0.6 использует
`.strict()` валидацию и не принимает лишние поля.
**Fix**: wrapper перехватывает все notification-сообщения (без `result`/`error`) с `id === null`
и удаляет поле `id` перед передачей в child.
### 2. Race condition при gateway restart
При рестарте Hermes gateway поднимает новый процесс `obsidian-mcp-wrapper`. Первые
параллельные tool-вызовы приходят пока child ещё инициализируется (~500ms) → они
тайм-аутились, circuit breaker открывался (3 фейла → 60s cooldown).
**Fix**: wrapper буферизует все tool-вызовы до завершения handshake
(`initialize` → ответ → `notifications/initialized`), потом флашит очередь.
### 3. Corrupted large payloads (UTF-8 chunk split)
При больших tool-вызовах (~200KB+) Node.js доставляет stdin в нескольких chunk-ах.
Старый код делал string split — JSON разрезался по байтам → UTF-8 multibyte символы
портились, `JSON.parse` падал, сообщение дропалось молча.
**Симптом**: `edit_note` с большим контентом тихо зависал (30s timeout), в логах:
```
[obsidian-wrapper] Non-JSON from Hermes (forwarding verbatim): {"jsonrpc": "2.0", "method": "tools/call", "id": 3, "params": {"name": "edit-not
```
**Fix**: stdin и stdout читаются через `Buffer.concat` + `Buffer.slice` на `0x0a`.
Строка собирается полностью до передачи в `JSON.parse`.
### 4. Per-call watchdog (зависший child)
Если child не ответил на `tools/call` / `tools/list` / `resources/*` за **5s**
watchdog убивает процесс. После авторестарта call автоматически уходит в голову
очереди и ретраится.
**Fix**: `armWatchdog(callLine)``setTimeout 5000ms``child.kill()``startChild()`.
---
## Как работает
```
Hermes (stdin) → wrapper → obsidian-mcp (child)
↑ auto-restart при краше (до 10 раз)
```
**Состояния**:
- `ready = false` — child стартует, все tool-вызовы в очередь
- `ready = true` — handshake завершён, очередь флашится, всё проходит напрямую
**Restart логика**:
1. Child упал → `ready = false`, `restarts++`
2. Новый child спавнится
3. Wrapper реплеит сохранённый `initialize` → ждёт ответа с `serverInfo`
4. Отправляет `notifications/initialized` (не форвардит Hermes — он не просил)
5. `ready = true` → флаш очереди
**Shutdown**:
На stdin EOF (`Hermes` закрыл процесс) — child убивается без авторестарта, wrapper выходит чисто.
**Логи** (все в stderr с timestamp):
```
[obsidian-wrapper] 2026-05-09T12:00:00.000Z Spawning obsidian-mcp (vault=/Users/admin/obsidian)
[obsidian-wrapper] 2026-05-09T12:00:00.500Z Hermes → child (handshake complete): notifications/initialized
[obsidian-wrapper] 2026-05-09T12:00:00.501Z Child ready — flushing queue (3 items)
[obsidian-wrapper] 2026-05-09T12:00:01.200Z Stripped id:null from notification: notifications/initialized
[obsidian-wrapper] 2026-05-09T12:00:06.000Z WATCHDOG: child did not respond in 5000ms for tools/call #7 — killing and restarting
```
---
## Конфиг Hermes
`~/.hermes/config.yaml`:
```yaml
obsidian:
command: node
args: [/Users/admin/scripts/obsidian-mcp-wrapper.js]
```
Wrapper сам вызывает `/opt/homebrew/bin/obsidian-mcp /Users/admin/obsidian`.
---
## Производительность
- Инициализация: ~587ms (без ZodError)
- Overhead wrapper: negligible (pure Node.js child_process, нет npm-зависимостей)
- MAX_RETRY: 10
- CALL_TIMEOUT_MS: 5000ms (watchdog)
---
## История
**2026-05-09 (1)** — создан после диагностики 58 ошибок `obsidian/... call failed` в логах Hermes.
Корневая причина — ZodError + race condition при старте. Wrapper написан вместо патча
исходников obsidian-mcp (патч не нужен, wrapper чище и не ломается при обновлении пакета).
**2026-05-09 (2)** — фикс large payload: Buffer-based line splitting вместо string split
(`edit_note` с большим контентом молча дропался). Добавлен per-call watchdog (5s timeout → kill & retry).
MAX_RETRY повышен с 5 до 10.
+107
View File
@@ -0,0 +1,107 @@
---
namespace: work
tags: [system, agent, eagle, rules]
last_updated: 2026-04-28
confidence: 1.0
---
# Personal OS — Agent Rules & Architecture
Eagle (Орёл) is the Zulip-facing Hermes agent. This page documents what Eagle knows about its own environment and how to reason about it.
## Data Flow
```
Asana API
→ sync.js (every 30 min, workdays via Hermes cron)
→ PostgreSQL (tasks, stories, task_edges, sync_state, task_annotations)
→ generate-status.js
→ ~/Developer/personal-os/asana_context.md ← raw Asana context (inbox-check)
→ ~/context/status.md ← Eagle reads THIS
```
Eagle **only reads** `~/context/status.md`. It never touches the DB directly or calls Asana API.
## Eagle's Knowledge Sources
1. `~/context/status.md` — live Asana + ActivityWatch snapshot (updated every 30 min)
2. `~/Developer/personal-os/briefs/daily/YYYY-MM-DD.md` — today's brief
3. `~/Developer/personal-os/briefs/weekly/plan-MONDAY.md` — weekly plan
4. `~/obsidian/wiki/` — knowledge base (via obsidian MCP)
5. `~/Developer/personal-os/agent/` — playbooks and rules (via file tools)
## Feedback Loop
When Alex corrects something:
1. Edit the brief directly (show diff first, confirm before writing)
2. If systemic → edit the prompt template in `agent/prompts/`
3. Log correction silently to `corrections_log` table:
```sql
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>');
```
`source` values: `zulip_pushback` | `morning_brief_correction` | `manual`
`reason_tag` examples: `ship-review-crunch` | `urgent-bug` | `meeting-day` | `over-estimated` | `scope-change`
Never announce the log insertion. It is silent instrumentation.
## Self-Diagnostics
If pipeline seems stale, Eagle can check:
```bash
stat -f "%Sm" ~/context/status.md
# Should be < 40 min old on workdays
```
If sync is broken:
```bash
bash ~/scripts/run-pipeline.sh
# Runs sync.js + generate-status.js + healthcheck
```
If wiki is stale:
```bash
bash ~/scripts/run-wiki-ingest.sh
# Runs at 22:00 via launchd; call manually if needed
```
## Cron Schedule (Hermes)
| Job | Schedule | Channel |
|-----|----------|---------|
| data-pipeline | */30 7-21 workdays | silent |
| morning-brief | 08:30 workdays | #daily-brief |
| eod-summary | 18:00 workdays | #daily-brief |
| inbox-check | */30 9-19 workdays | #inbox |
| weekly-plan | Mon 08:00 | #daily-brief |
| weekly-review | Fri 17:00 | #daily-brief |
| retrospector | Fri 17:30 | #retrospector |
| commit-vault | 23:00 daily | silent |
wiki-ingest runs at 22:00 via **launchd** (not Hermes) — because it needs Mac filesystem access.
## Allowed File Writes
Eagle can write to:
- `briefs/daily/YYYY-MM-DD.md`
- `briefs/weekly/plan-MONDAY.md`
- `briefs/weekly/YYYY-MM-DD.md`
- `briefs/inbox/YYYY-MM-DD.md`
- `agent/prompts/*.md` (with confirmation)
- `~/obsidian/work/projects/SLUG.md`
- `~/obsidian/work/decisions/YYYY-MM-TOPIC.md`
- `~/obsidian/personal/`
- `~/obsidian/family/`
Never write to `obsidian/wiki/` directly — that's wiki-ingest's job.
Never write `corrections_log.md` — use the SQL INSERT above (table, not file).
## Activity Classification
Work activity is classified via `aw-projects.json`. Projects with `namespace: "personal"` (e.g., AXPressDeck, media_files_db) appear as a footnote in the Activity section of status.md but are **not** counted as work time and **not** upserted to `activity_daily`.
To re-classify a project (personal ↔ work), ask Eagle to edit `aw-projects.json`.
+161
View File
@@ -0,0 +1,161 @@
---
namespace: work
tags: [system, architecture]
last_updated: 2026-04-28
confidence: 1.0
---
# Personal OS — Architecture
## Why It Exists
See: `personal-os-purpose.md`
## Agents
### Стратег (Strategist) — data pipeline, no conversation
- `~/scripts/run-pipeline.sh` → sync.js + generate-status.js every 30 min (workdays via Hermes cron)
- Writes `~/context/status.md` and `~/Developer/personal-os/asana_context.md`
- Writes daily AW activity summaries to `activity_daily` table with quadrant classification
- No Zulip output — pure data layer
### Орёл / Eagle (Tactician + reactive layer) — Hermes Zulip agent
- Reads `~/context/status.md`, daily/weekly briefs, vault via obsidian MCP
- Zulip streams: `daily-brief` `inbox` `focus` `executor` `projects` `journal` `retrospector`
- Cron jobs: generate-daily-brief (07:00 MTWRF), morning-brief (08:30 MTWRF), eod-summary (18:00 MTWRF), inbox-check (30 min 0919 MTWRF), weekly-plan (Mon 08:00), weekly-review (Fri 17:00), retrospector (Fri 17:30), executor-autonomous (every 30 min 0918 MTWRF), commit-vault (23:00 daily)
- Logs corrections to `corrections_log` table (SQL INSERT, silently on pushback)
- **Never accesses Asana API directly** — only reads pre-rendered markdown and DB
### Ретроспектор (Retrospector) — Friday pattern analysis
- Runs as separate Hermes cron at 17:30 Friday → `#retrospector`
- Sources: corrections_log table, status.md subtask counts, obsidian git log, activity_daily quadrants, stories visibility metric
- Prompt: `~/Developer/personal-os/agent/prompts/retrospector.md`
### Исполнитель (Executor) — two modes
**Manual mode**: triggered by "fix bug [GID]" in #executor
- Single STOP gate after analysis; then autonomous: fix → build → test → draft PR → CI loop
- Prompt: `~/Developer/personal-os/agent/prompts/executor-bug-fix.md`
**Autonomous mode**: hourly cron (0918 workdays) → #executor
- Maintains a queue from: My Hack Days, Watched, O-L Backlog, stale My Tasks
- Analyzes tasks → posts "[Auto] Ready to start" with complexity/feasibility
- Waits for "go {gid}" to start; no reply = moves to next analysis
- Max 1 active fix at a time; handles CI/review on open draft PRs
- Stops touching a PR once Alex moves it out of draft or pushes commits
- No Asana comments; no touching other people's PRs
- Queue state in `executor_queue` table; live plan: `~/Developer/personal-os/briefs/executor-queue.md`
- Prompt: `~/Developer/personal-os/agent/prompts/executor-autonomous.md`
**Shared**:
- Worktree at `~/DuckDuckGo/apple-browsers.git/.claude/worktrees/executor-{gid}-{slug}/`
- Always `git fetch origin main` + branch from `origin/main`
- State machine in `executor_runs` table
- For UI tests: spins VM via ddg-vm MCP (virfield), cleans up on PR close/merge
**Eagle commands in #executor**:
| Command | Effect |
|---------|--------|
| `fix bug {gid}` | Spawn manual executor (separate agent) |
| `go {gid}` | Approve autonomous task → starts next tick |
| `skip {gid}` | Skip task in queue |
| `stop` | Pause current autonomous task |
| `next` | Pause current, analyze next |
| `resume {gid}` | Re-queue paused task |
| `queue` | Show current executor-queue.md |
## Data Flow
```
Asana API
→ sync.js (30 min, Hermes cron, silent)
→ PostgreSQL: tasks, stories, task_edges, sync_state, task_annotations
ActivityWatch (local daemon)
→ generate-status.js (reads AW HTTP API)
→ activity_daily (upserts daily project/quadrant summaries)
generate-status.js
→ ~/context/status.md ← Eagle reads this
→ ~/Developer/personal-os/asana_context.md ← inbox-check reads this
Eagle corrections in Zulip
→ corrections_log table (INSERT on pushback, no announcement)
→ Retrospector reads weekly
Executor runs
→ executor_runs table (state machine)
→ ~/DuckDuckGo/apple-browsers.git/.claude/worktrees/
→ GitHub draft PRs (--draft, --assignee @me, no reviewers)
```
## Key File Locations
| File / Path | Purpose |
|-------------|---------|
| `~/context/status.md` | Live Asana + AW snapshot. Written by generate-status.js. Eagle reads this. |
| `~/Developer/personal-os/asana_context.md` | Raw Asana context for inbox-check |
| `~/Developer/personal-os/agent/prompts/` | All agent prompts (Hermes cron + on-demand) |
| `~/.hermes/SOUL.md` | Eagle's identity, rules, vault write permissions |
| `~/Developer/personal-os/config.json` | Asana workspace/user GIDs, section GIDs |
| `~/Developer/personal-os/aw-projects.json` | ActivityWatch project classification rules |
| `~/.hermes/config.yaml` | Hermes config: model, MCP servers, cron settings |
| `~/obsidian/` | Vault — git repo, NAS remote, obsidian-mcp for search |
| `~/Developer/personal-os/logs/` | heartbeat.log, wiki-ingest-YYYY-MM-DD.md |
| `~/Developer/personal-os/briefs/` | daily/, weekly/, inbox/ — agent-written brief files |
| `~/DuckDuckGo/apple-browsers.git/` | Browser repo (bare), worktrees as sibling dirs |
| `~/DuckDuckGo/apple-browsers.git/.claude/worktrees/` | Executor worktrees |
## Database Tables
| Table | Written by | Read by |
|-------|-----------|---------|
| tasks | sync.js | generate-status.js, executor, status queries |
| stories | sync.js | generate-status.js, executor, retrospector |
| task_edges | sync.js | subtask traversal |
| sync_state | sync.js | generate-status.js header |
| task_annotations | manual / Eagle | generate-status.js irrelevant filter |
| activity_daily | generate-status.js | retrospector quadrant drift |
| corrections_log | Eagle on pushback | retrospector pattern analysis |
| executor_runs | executor (manual + autonomous) | generate-status.js active runs, executor state machine |
| executor_queue | executor-autonomous | generate-status.js queue display, autonomous tick state |
## MCP Servers
**Hermes agents:**
| Name | Command | Used for |
|------|---------|---------|
| obsidian | `/opt/homebrew/bin/obsidian-mcp ~/obsidian` | Vault search, read, write |
**Claude Code / `claude -p` sessions:**
| Name | Command | Used for |
|------|---------|---------|
| ddg-vm | `npx tsx ~/Developer/virfield/server/mcp-server.ts` | VM lifecycle for UI tests |
| obsidian | `/opt/homebrew/bin/obsidian-mcp ~/obsidian` | Vault access |
## launchd Agents
| Label | Schedule | Purpose |
|-------|----------|---------|
| personal.os.heartbeat | :05 every hour | Runs run-pipeline.sh directly (no Claude auth needed); also triggers missed Hermes jobs after wake |
| personal.os.wiki-ingest | 22:00 daily | `claude -p` wiki synthesis (needs filesystem access) |
## Vault Structure
```
~/obsidian/
├── wiki/ ← LLM-generated + hand-written (confidence: 1.0 = never overwrite)
│ ├── personal-os-architecture.md (this file)
│ ├── personal-os-purpose.md
│ ├── personal-os-agent-rules.md
│ ├── personal-os-self-modification.md
│ ├── personal-os-sync-pipeline.md
│ ├── personal-os-schema.md (auto-generated)
│ ├── ddg-asana-workflow.md
│ └── vault-filling-guide.md
├── raw/ ← symlinks to external source files (schema.sql, etc.)
├── work/
│ └── wiki/
│ └── apple-browsers/ ← .cursor/rules copies (.md) + vm-ui-testing.md
└── personal/ family/
```
@@ -0,0 +1,91 @@
---
source: raw/personal-os-catchup-plan-2026-04-27.md
content_hash: 5081ceb719104e1b7c6b5edd642c1daf38b3fd7cf1a96442a00516e7cdb15379
namespace: work
last_synced: 2026-04-28
confidence: 0.8
tags: [personal-os, planning, catchup, executor, retrospector]
---
# Personal OS — Catch-Up Plan (2026-04-27)
Handoff document for the executor agent: what to build next, in what order, and why. Companion to [[personal-os-state-2026-04-27]].
## Context
Alex (macOS Browser DRI at DuckDuckGo, ADHD profile, async-first) needs the Personal OS to hold work context outside his head — so comments don't get lost, focus is protected, and career goals don't get crushed by current projects.
Original four-agent design — Стратег / Тактик / Ретроспектор / Исполнитель. Stratan and Retrospector ✅ shipped; Tactic 🟡 partial; Executor 🟡 v1 with manual STOP gates only.
**Catching up on:** career tracking, inbox triage hygiene, focus-aware ping gating, structured `corrections_log`, quadrant column on `activity_daily`, Executor v2, MS365 MCP wiring, vault consolidation, Executor PR template.
## Architectural principle (reinforced)
No new YAML files. No new shell scripts. No new launchd plists. Everything extends existing components: postgres tables, `generate-status.js` queries, prompt files in `agent/prompts/`, Eagle's `SOUL.md` rules. See [[personal-os-architecture]].
## Six deliverables (dependency-ordered)
### 1.1 Schema migration
Three changes — see [[personal-os-schema]]:
- `corrections_log` (id, date, week_number, source, original_plan, correction, deferred_gids[], reason_tag) — Tactic writes, Retrospector aggregates
- `executor_runs` (task_gid, worktree_path, branch_name, pr_url, state, ci_attempts, thread_id, timestamps) — state machine for autonomous Executor
- `activity_daily.quadrant` column — values: `project | aor | career | strategy | other`
Why tables not markdown: Retrospector needs aggregates ("X deferred 3+ times in 4 weeks"); SQL trivial, LLM-parsing markdown each time is fragile.
### 1.2 generate-status.js — three new SQL blocks
- **Career: Advisor pipeline** — open tasks tagged "Project Advisor", surface as `## ⚡ Career — Advisor Opportunities`
- **Career: Visibility gaps** — Assessments / O-N / O-L tasks where Alex hasn't commented in 7+ days
- **Quadrant classification at AW write time** — `PROJECT_TO_QUADRANT` map, with current top-priority project read dynamically from config (not hardcoded — drifts wrong silently otherwise)
### 1.3 inbox-triage.md — STEP 0 gating + overdue exclusion
Two-part gate added at the top of the prompt (not a wrapper script — wrapper bypasses agent judgment):
- **Focus check**: read latest `activity_daily` focus_score + active calendar event. Skip if in meeting, or focus_score > 60 in editor for >20min, unless 🔴 sections older than 3h.
- **Scope filter**: inbox = NEW signal only. Exclude overdue (those belong in daily-brief), already-responded, system events, bot noise. Include only new human comments, new assignments, ship/TD status changes, mentions.
### 1.4 retrospector.md — three new SQL blocks
Replace markdown `corrections_log.md` reads with:
- **Correction patterns** — `reason_tag` GROUP BY HAVING COUNT >= 3 over 4 weeks
- **Quadrant drift** — 4-week × quadrant pivot; flag any quadrant <5% for 3+ weeks (career gets the strongest flag)
- **Visibility this week** — count of strategic comments by Alex; surfaces career signal as a measurable proxy for the EP3 "thin Strategic Leadership" gap
### 1.5 Eagle SOUL.md — corrections_log writeback
When user pushes back in any channel, BEFORE responding: classify pushback type, infer reason_tag from message context, INSERT into `corrections_log` via psql, then respond normally. Don't announce the insert. Eagle is already in conversation — capturing in-context beats post-hoc log parsing.
### 1.6 Executor v2 — single STOP gate, then autonomous
Replace v1's many gates with **one gate after Analysis** (the consequential decision). Then: fix → build → test → branch (`executor/<gid>-<slug>`) → push → `gh pr create --draft --assignee @me` (no `--reviewer`) → self-review comment → CI poll loop (every 10min, max 3 auto-fix attempts).
**Hard prohibitions**: never push non-`executor/*` branches, never non-draft PRs, never `--reviewer`, never `gh pr merge`, never modify `.github/` or deps without thread confirmation.
Draft + assignee=@me + no reviewers gives a durable, ADHD-friendly surface (visible in "assigned to me", ignored by branch-protection auto-merge, no reviewer noise).
### 1.7 MS365 MCP into Hermes
One-time Hermes MCP config change benefits multiple prompts (inbox-triage, weekly-plan, daily-brief). Used in STEP 0 to detect active meetings via `outlook_calendar_search`. MS365 chosen over Google because it's the actual workspace.
### 1.8 Vault consolidation
Move into `~/obsidian/work/wiki/`: process docs from `~/Developer/personal-os/`, the UI testing skill, copies of `apple-browsers/.cursor/*.mdc` files. Decision rule: process/convention/knowledge → vault; runtime configs → original location. **Verify each prompt/skill end-to-end after move** — silent path-reference breakage is the failure mode. Copy `.mdc` (don't move) — Cursor still reads them in place.
### 1.9 PR template — Executor variant
Team's PR template requires manual confirmation of task/reviewer/description. For autonomous flow all three are deterministic. Use **inline template generation** (Option B) in the Executor prompt — auto-generate body from analysis + diff. Draft state is the safety net; Alex polishes on flip-to-ready.
## Execution order
1, 2, 4, 5 unblocked. 3 needs MS365 (#2). 6 needs schema (#1) + quadrants (#5). 7 needs schema (#1). 8, 9 unblocked. 10 (Executor v2) needs 1, 8, 9. **No new Discord channels needed.**
## "Done" looks like
Friday Retrospector says specific things ("deferred bug work 4× for ship-review-crunch"); inbox shows only new human signals (silent during meetings); `## ⚡ Career` section in status.md; pushback creates one `corrections_log` row; `fix [task]` in `#executor` produces an analysis post → approval gate → draft PR within 30min for trivial bugs; vault contains all `.mdc` + UI skill + process docs reachable via obsidian-mcp.
## Related
[[personal-os-state-2026-04-27]] [[personal-os-architecture]] [[personal-os-schema]] [[personal-os-agent-rules]] [[personal-os-self-modification]] [[ddg-asana-workflow]]
+73
View File
@@ -0,0 +1,73 @@
---
namespace: work
tags: [system, purpose, adhd, design]
last_updated: 2026-04-28
confidence: 1.0
---
# Personal OS — Purpose
## Why It Exists
Alex is a macOS Browser Developer / DRI at DuckDuckGo. Async-first company, primary channels Asana and Slack. ADHD profile.
The Personal OS exists to hold work context **outside Alex's head** — so that context isn't lost during deep focus, transitions, or when switching back from a meeting.
Without it, three things happen regularly:
1. A comment sits on an Asana task for days — Alex never saw it because he was in Xcode
2. A career-level task (Ship Review, Tech Design review) gets deferred until it becomes a bottleneck
3. End of day: "what did I actually do today?" — no clear answer, no signal for tomorrow
## Design Principles
**Principle 1 — Context is the product, not notifications.**
The system's job is to maintain a live, accurate picture of what's happening. Eagle reads that picture and speaks when something actually matters. It is not a notification firehose.
**Principle 2 — Protect focus.**
If Alex is in Xcode or Cursor, don't interrupt unless there's a red signal older than 3 hours. ADHD makes context switches expensive — the system should absorb noise, not amplify it.
**Principle 3 — No hallucinated state.**
Eagle never guesses about Asana tasks. All Asana data flows through sync.js → PostgreSQL → generate-status.js → status.md. Eagle reads markdown, not the API. This means the data is always consistent and never stale by more than 30 minutes.
**Principle 4 — Career goals are first-class.**
The system explicitly tracks: Project Advisor opportunities, visibility gaps (Ship Reviews where Alex hasn't commented), Tech Design reviews pending. These surface in status.md and the daily brief. Without explicit tracking they'd be invisible.
**Principle 5 — No new infra.**
The system has enough moving parts. New capabilities go into existing components: Postgres tables, generate-status.js queries, prompt files in `agent/prompts/`, SOUL.md rules. No new YAML files, no new shell scripts, no new launchd plists unless there is no other way.
## The Four Agents
| Agent | Role | When |
|-------|------|------|
| **Стратег (Strategist)** | Data pipeline — reads Asana + ActivityWatch, writes status.md | Every 30 min, workdays |
| **Орёл / Eagle (Tactician)** | Discord-facing reactive agent — briefs, inbox, focus gating | Cron + on-demand |
| **Ретроспектор (Retrospector)** | Weekly pattern analysis — corrections, quadrant drift, visibility | Friday 17:30 |
| **Исполнитель (Executor)** | On-demand bug-fix worker — analysis → fix → PR → CI loop | Explicit trigger only |
Eagle is the only agent Alex directly talks to. The others are background infrastructure.
## What "Work Context" Means
The system tracks four categories, mapped to `activity_daily.quadrant`:
| Quadrant | What it covers | Why it matters |
|----------|---------------|----------------|
| `project` | The current top-priority feature/bug | Time here should dominate most weeks |
| `aor` | AOR maintenance: code review, bug fixes, blocklist | Required to keep the area healthy |
| `career` | Project Advisor, Ship Reviews, Tech Design, visibility | Easily deferred; system forces it into view |
| `strategy` | Planning, Personal OS, Asana organisation | Meta-work; needs a floor, not a ceiling |
Personal pet projects (AXPressDeck, media_files_db, etc.) are tracked separately and not counted as work time. See `aw-projects.json` (`namespace: "personal"` entries).
## What It Does Not Do
- Does not make decisions for Alex — it informs
- Does not send notifications during focus (deep focus gate in inbox STEP 0)
- Does not access Asana API directly from Eagle — only reads pre-rendered status.md
- Does not create PRs, commits, or messages without explicit confirmation (except Executor, after its single approval gate)
## See Also
- `personal-os-architecture.md` — full technical architecture, agents, data flow, file locations
- `personal-os-agent-rules.md` — Eagle's operating rules and allowed writes
- `personal-os-self-modification.md` — how Eagle (and Alex) can evolve the system
+92
View File
@@ -0,0 +1,92 @@
---
source: raw/schema.sql
content_hash: 71b47e47b483834c887de14c76d5b16506d90ad64198d76a92a7b3731132dab8
namespace: work
last_synced: 2026-04-27
confidence: 0.9
tags: [schema, postgres, database, asana, wiki]
---
# Personal OS Database Schema
Postgres schema (`personal_os`) backing the Asana mirror, agent annotations,
file ingestion pipeline, semantic wiki, and memory store. Requires the
`vector` (pgvector) and `pg_trgm` extensions. Bootstrap with
`psql -U admin -d personal_os -f schema.sql`.
## Asana mirror
- **`tasks`** — one row per Asana task GID, upserted on each sync.
Stores due/start dates, completion, My Tasks section, assignee/creator,
primary project, timestamps, full `raw_json`, plus a `source` enum:
`my_tasks` (assigned to me) > `delegated` (I created, others assigned)
> `following` (CC'd) > `project` (project member, fallback).
Sync bookkeeping: `fetched_at`, `stories_fetched_before` cursor.
- **`stories`** — raw event log per task, keyed by Asana story GID.
`resource_subtype` covers comments, assignment changes, due-date edits,
section moves, dependency edits, attachments, completion, etc.
- **`task_edges`** — directed graph between tasks. `relation_type`:
`subtask`, `dependency` (blocked-by), `dependent` (blocking),
`project_sibling`, `mention` (referenced in a story).
`related_gid` may not yet exist in `tasks`.
- **`task_annotations`** — agent or user notes per (task, annotation_type).
Types: `irrelevant`, `watching`, `needs_action`, `snoozed`
(with `snoozed_until`). Never written by the fetcher.
- **`sync_state`** — one row per sync stream
(`my_tasks`, `following`, `delegated`, `project:{gid}`,
`workspace_events`). Tracks cursor, last sync, last full sync.
- **`task_embeddings`** — pgvector(1024) per task, HNSW index with
cosine ops; populated separately from sync.
The **`active_tasks`** view filters out completed and irrelevant tasks
and tasks snoozed past today, ordered by source priority then due date
then modified-at. Starting point for daily review.
## Phase 0 additions (2026-04-27)
All core tables gain `namespace TEXT NOT NULL DEFAULT 'work'` so a single
DB can serve work/personal/family contexts. `tasks` also gets
`possibly_deleted` and `last_seen_in_full_sync` to track tasks that
disappear between full syncs without explicit deletion events.
## File ingestion → wiki
- **`file_references`** — files (via macOS security-scoped
`bookmark_data` + cached `last_known_path`) and web URLs
queued for wiki ingestion. Tracks `content_hash` (SHA256),
`mime_type`, `tags`, a `modification_log` JSONB, and a
`wiki_stale` flag the [[wiki-ingest-process]] consumes.
Namespace-checked (`work`/`personal`/`family`).
- **`wiki_pages`** — LLM-synthesised markdown, never a raw copy.
Has `sources` JSONB (file_ref/url/title), `confidence` float,
`superseded_by` self-FK for version chains, `stale` flag,
`last_synced_hash`, and a pgvector(1536) embedding for semantic
search. The optional ivfflat index is left commented; rebuild
once the table has 1000+ rows.
## Memory store
**`memory_store`** — semantic memory from Discord, Claude sessions,
and manual entries. `type` ∈ {fact, preference, decision, person},
`source` ∈ {discord, claude, manual}, with entities JSONB,
confidence, optional `expires_at` (null = permanent), and a
pgvector(1536) embedding. Used by Hermes for cross-session context.
## Indexing notes
- Trigram GIN on `tasks.name` enables fuzzy task search.
- `tasks_due_on` is partial (only non-completed tasks).
- pgvector embedding indexes for `wiki_pages` and `memory_store`
are deferred until the tables have meaningful row counts.
- `task_embeddings` uses HNSW; the wiki/memory stores use ivfflat
(commented) — different recall/build trade-off per workload.
## Key constraints
- All `namespace` columns are CHECK-constrained to
`('work','personal','family')` on the new (Phase 0+) tables.
- Cascade deletes flow from `tasks``stories`, `task_edges`,
`task_annotations`, `task_embeddings`.
## Related
[[personal-os-architecture]] [[wiki-ingest-process]]
@@ -0,0 +1,152 @@
---
namespace: work
tags: [system, self-modification, eagle, meta]
last_updated: 2026-04-28
confidence: 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":
1. Read the current prompt: `~/Developer/personal-os/agent/prompts/<name>.md`
2. Draft the change, show the diff in Discord
3. On confirmation: write the file
4. 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:
1. Read current `aw-projects.json`
2. Add entry with `project`, `namespace` (omit for work, `"personal"` for pet projects), and `rules`
3. Show draft, confirm, write
4. 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.
```sql
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:
1. Draft the SQL in Discord, explain the purpose
2. On "go": run via `psql personal_os -c "..."`
3. 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:
```yaml
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:
1. Add a SQL query function (e.g., `getNewSignal()`)
2. Add a render function (e.g., `renderNewSignal(data)`)
3. Add both to `main()`: Promise.all for the query, the render call in `parts`
4. Test: `node ~/Developer/personal-os/generate-status.js` and read `~/context/status.md`
5. 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-os` or `obsidian`)
---
## See Also
- `personal-os-architecture.md` — full system map
- `personal-os-agent-rules.md` — Eagle's rules and channel routing
- `personal-os-purpose.md` — why this system exists
@@ -0,0 +1,99 @@
---
source: raw/personal-os-state-2026-04-27.md
content_hash: 1640cb63145b49263b9476e2c39fe443ac561fdf6c27faccc50cfc8568552614
namespace: work
last_synced: 2026-04-28
confidence: 0.8
tags: [personal-os, state, executor, infrastructure]
---
# Personal OS — State Snapshot (2026-04-27)
Current state of the system after the [[personal-os-catchup-plan-2026-04-27]] was executed. Supersedes earlier state and v3 plan documents.
## Infrastructure (✅ live)
- **Postgres on Mac** — tables: `tasks`, `stories`, `task_edges`, `sync_state`, `signal_queue`, `activity_daily` (+ `quadrant` col), plus new `corrections_log`, `executor_runs`. See [[personal-os-schema]].
- **sync.js** — 4-source Asana fetch, incremental deltas, sweepRecentlyCompleted
- **generate-status.js** — DB → `status.md` + `asana_context.md`; live blocks: 5 advisor opps, 8 visibility gaps, 10 executor queue items in first run
- **ActivityWatch** — DDG browser + Xcode watchers
- **Hermes v0.11.x** — launchd daemon `ai.hermes.gateway`, Claude Code OAuth, `claude-sonnet-4-6`
- **Discord bot Орёл#0898** — channels `#daily-brief #inbox #focus #executor #projects #journal #retrospector`
See [[personal-os-architecture]] and [[personal-os-sync-pipeline]] for component detail.
## Schedule
**Hermes cron jobs (8):** data-pipeline (every 30min), morning-brief (08:30), eod-summary (18:00), inbox-check (every 30min, 9-19), weekly-plan (Mon 08:00), weekly-review (Fri 17:00), retrospector (Fri 17:30 → `#retrospector`), commit-vault (23:00).
**launchd agents (Mac-level, fire on wake):**
- `personal.os.heartbeat` — :05 hourly watchdog, triggers missed Hermes one-per-day jobs
- `personal.os.wiki-ingest` — 22:00 daily, runs local `claude -p` (filesystem access requires local subprocess, not Hermes API agent)
## Vault
`~/obsidian/` — git remote `ssh://truenas_admin@mallexxx.duckdns.org/mnt/RED_2TB/storage/git/obsidian-vault.git`. Layout: `wiki/` (LLM-generated + hand-written, see [[wiki-ingest-process]]), `raw/` (symlinks), `work/` and `personal/` (Eagle-writable per [[vault-filling-guide]]), `family/`. Phase 8 of catchup added 43 `.cursor/rules/*.mdc` files under `work/wiki/apple-browsers/`.
## Catch-up plan execution status
| # | Item | Status |
|---|------|--------|
| 1 | Schema (corrections_log, executor_runs, quadrant) | ✅ |
| 2 | MS365 MCP in Hermes | 🟡 cloud connector only; no local npm package; available to `claude -p` jobs once added to CLI env |
| 3 | inbox-triage STEP 0 | ✅ |
| 4 | generate-status: advisor + visibility + executor queue | ✅ |
| 5 | generate-status: quadrant + activity_daily upsert | ✅ |
| 6 | retrospector SQL blocks | ✅ |
| 7 | SOUL.md corrections_log INSERT | ✅ |
| 8 | Vault consolidation (43 .mdc) | ✅ |
| 9 | Executor PR template inline | ✅ |
| 10 | Executor v2 prompt | ✅ written, not yet run end-to-end |
## Executor v2 architecture (Phase 4 plan)
Autonomous worker: evaluates backlog → picks task → spawns sub-agent in Discord thread under `#executor` → streams progress → produces draft PR → self-reviews → CI loop → surfaces in daily/weekly briefs.
**Triggers:** user-initiated (`fix [task]` in `#executor`), scheduled (Friday weekly-review queue), status-driven (Eagle reads `## ⚡ Executor Queue` from status.md).
**Qualification SQL:** assigned to Alex, not completed, no stories in 14+ days, due within 30 days.
**Worker flow:** Eagle creates Discord thread → spawns sub-agent (`--worktree` mode, workdir `~/DuckDuckGo/apple-browsers.git/.claude/worktrees/executor-<gid>-<slug>/`) → posts streaming updates per phase → opens PR → self-review comment → polls `gh pr checks` every 10min → on red, auto-fix max 3 attempts then escalate.
**Phases:** 4a evaluation engine · 4b worker spawning + threads · 4c PR + CI loop · 4d feedback loop on PR comments · 4e scheduled evaluation.
## Key decisions (divergence from earlier plan v3)
1. **wiki-ingest** moved from Hermes cron → launchd `claude -p` subprocess. Filesystem I/O requires local process; Hermes API agents have no filesystem.
2. **personal-wiki MCP** dropped, replaced with stock `obsidian-mcp` against `~/obsidian/`. The DB-backed wiki was never built; vault is the source of truth now.
3. **Single heartbeat watchdog** instead of per-job launchd plists — simpler, covers wake-from-sleep for all one-per-day jobs.
4. **Retrospector** extracted to standalone `retrospector.md` + own cron + `#retrospector` channel, separating "what moved" (review) from "what's the pattern" (retro).
5. **NAS git path** moved to `/mnt/RED_2TB/storage/git/``/home/` lives on TrueNAS boot pool which is wiped on OS updates.
6. **Phase 2-family deferred** until after Phase 4 — Executor v2 has higher leverage than family infra.
7. **Executor v1 manual-only** with explicit STOP gates — first autonomous code-changing agent stays conservative.
8. **wiki-ingest prompt** rewritten DB-first → file-based with SHA256 frontmatter `content_hash`. See [[wiki-ingest-process]].
## Open questions before Executor v2 starts (all resolved)
- Repo: `~/DuckDuckGo/apple-browsers.git/` bare repo, sibling worktrees, main at `main/`
- Worktree path: `.claude/worktrees/executor-{gid}-{slug}/`
- Xcode scheme: `DuckDuckGo macOS`, `.xcworkspace` required, xcbeautify required
- PR template: `pull-request.mdc` strict gates overridden by Executor v2 (task known, reviewer=@me, description auto-generated)
- Cleanup: `git worktree remove --force` + `git push --delete` + stop/delete VM
- UI test VM: ddg-vm MCP at `~/Developer/virfield/server/mcp-server.ts`, VirtualBuddy at `~/Documents/VirtualBuddy/`
## Tech debt
- Morning brief never smoke-tested (first real run = next 08:30) — Medium
- `work/projects/` empty (Eagle fills on first relevant conversation) — Low
- `executor-bug-fix.md` v1 still manual-only — replaced by v2 architecture above — Medium
- wiki-ingest `claude -p` runs without `--allowedTools` — Low
- **NAS SSH key in boot-pool `/home/`** — High; `authorized_keys` will be wiped on TrueNAS update; move under `/mnt/RED_2TB/`
- Hermes memory `~/.hermes/memory/` not backed up — Low
## Deferred — Phase 2-family
VPS (Ubuntu 24.04 $5/mo) `family_db` postgres + family Hermes instance + iCloud vault sync (`~/Library/Mobile Documents/iCloud~md~obsidian/`) for wife's iPhone Obsidian + NAS hourly rsync. Resumes after Phase 4.
## Related
[[personal-os-catchup-plan-2026-04-27]] [[personal-os-architecture]] [[personal-os-schema]] [[personal-os-sync-pipeline]] [[personal-os-agent-rules]] [[personal-os-self-modification]] [[personal-os-purpose]] [[wiki-ingest-process]] [[ddg-asana-workflow]] [[vault-filling-guide]]
+91
View File
@@ -0,0 +1,91 @@
---
namespace: work
tags: [system, sync, pipeline, debugging]
last_synced: 2026-04-27
confidence: 1.0
---
# Personal OS — Sync Pipeline
## Components
### sync.js
Pulls from 4 Asana sources into PostgreSQL. Run: `node ~/Developer/personal-os/sync.js`
**Sources fetched:**
1. `fetchMyTasks()` — My Tasks sections by GID (section-by-section)
2. `fetchFollowingTasks()` — tasks I follow, 7-day chunks to work around 100-result cap
3. `fetchDelegatedTasks()` — created by me, assigned to others
4. `fetchProjectTasks()` — tasks from projects I'm a member of (lookback window on full sync)
5. `sweepRecentlyCompleted()` — catch completed tasks missed by above
**Incremental sync**: uses `modified_since` cursor from `sync_state` table.
**Full sync**: triggered when `full_sync_at` is older than configured threshold.
### generate-status.js
Reads from PostgreSQL → writes `asana_context.md` + `status.md`.
Run: `node ~/Developer/personal-os/generate-status.js`
### run-pipeline.sh
Wrapper: runs sync.js + generate-status.js + healthcheck.
`~/scripts/run-pipeline.sh`
Healthcheck: `asana_context.md` must be < 40 minutes old. Exits 1 if stale.
## Known Bugs (Fixed)
### possibly_deleted + overdue tasks appearing
**Root cause**: Full sync marks tasks absent from Asana response as `possibly_deleted=true`.
These tasks remained `completed=false`, causing them to appear overdue.
**Fix 1 — generate-status.js**: Added `AND t.possibly_deleted = FALSE` to all 5 active-task queries.
**Fix 2 — sync.js**: Added `sweepRecentlyCompleted()` called after `syncMyTasks()`.
Uses `searchTasksForWorkspace(completed:true, completed_at.after=cursor)` to catch tasks
that vanished from sections (because completed tasks are excluded from section membership results).
### Completion gap
`getTasksForSection` drops completed tasks. `fetchFollowingTasks` has `completed:false` filter.
→ Completion events never reach DB incrementally.
`sweepRecentlyCompleted()` fills this gap.
## Debugging Checklist
**Pipeline seems stuck:**
```bash
bash ~/scripts/run-pipeline.sh
# Check exit code and output
```
**Check when last sync ran:**
```sql
SELECT key, last_sync_at, full_sync_at FROM sync_state ORDER BY last_sync_at DESC;
```
**Check possibly_deleted tasks (should be 0 active):**
```sql
SELECT count(*) FROM tasks WHERE possibly_deleted=true AND completed=false;
```
**Check asana_context.md freshness:**
```bash
stat -f "%Sm" ~/Developer/personal-os/asana_context.md
```
**Manual full sync (reset cursor):**
```sql
UPDATE sync_state SET cursor = null, full_sync_at = null WHERE key = 'my_tasks';
```
Then run `node sync.js`.
## Files
- `~/Developer/personal-os/sync.js` — main sync script
- `~/Developer/personal-os/generate-status.js` — status generation
- `~/Developer/personal-os/asana.js` — thin Asana API client
- `~/Developer/personal-os/config.json` — workspace GID, user GID, section GIDs
- `~/.config/personal-os/env` — ASANA_API_KEY, POSTGRES_URL
## Related
[[personal-os-architecture]] [[personal-os-agent-rules]] [[ddg-asana-workflow]]
+20
View File
@@ -0,0 +1,20 @@
---
title: Research Queue
updated: '2026-05-24'
type: meta
---
# Research Queue
> Topics detected as gaps in the wiki — mentioned in vault but no wiki page.
> Ordered by priority. Completed items move to ## Completed with link + date.
## Queue
(empty)
## Completed
- [x] WireGuard VPN → [[tech/wireguard-vpn]] (2026-05-23)
- [x] Executor Architecture v2 → [[concepts/executor-orchestrator]] (2026-05-19)
- [x] Executor Security Incident → [[concepts/executor-security-incident]] (2026-05-22)
+84
View File
@@ -0,0 +1,84 @@
---
title: Vault ↔ Agent Integration
created: '2026-05-23'
updated: '2026-05-23'
type: concept
namespace: work
tags: [vault, obsidian, wiki, ingest, mcp, hermes, system, architecture]
sources: [wiki/obsidian-mcp-wrapper.md, wiki/wiki-ingest-process.md, wiki/personal-os-architecture.md, wiki/vault-filling-guide.md]
confidence: high
---
# Vault ↔ Agent Integration
How Eagle, wiki-ingest, and the obsidian-mcp-wrapper work together as a
unified knowledge layer. Three distinct subsystems each own a slice of
the vault.
## The Three Subsystems
### 1. obsidian-mcp-wrapper (runtime read/write)
A Node.js proxy (`~/scripts/obsidian-mcp-wrapper.js`) wraps `obsidian-mcp`
to fix four production bugs: ZodError on init, race condition at gateway
restart, UTF-8 chunk corruption on large payloads, and a 5-second watchdog
for hung child processes.
Eagle reads the vault through this wrapper via MCP tool calls. It can search,
read, and write notes in real time during conversations.
**Allowed write paths** (Eagle only, with prior draft shown):
- `work/projects/`, `work/decisions/`, `personal/`, `family/`
- Never `wiki/` — that's wiki-ingest territory
### 2. wiki-ingest (nightly synthesis, 22:00 launchd)
A `claude -p` session (not Hermes) that runs nightly. It reads files in
`~/obsidian/raw/` (symlinks to external project files), computes SHA256
hashes, and synthesises wiki pages only when the source has changed.
**Invariants:**
- Output is synthesis, never verbatim copy
- Frontmatter `source` + `content_hash` are the ingestion contract
- Pages with `confidence: 1.0` are immutable
### 3. wiki-curation (daily Hermes cron, 02:00)
A Hermes cron job (this script) that maintains the wiki as a compounding
knowledge base: processes inbox, crystallises session knowledge, creates
synthesis pages, lints orphans and broken links.
## Division of Labour
| Layer | Tool | Vault path | Trigger |
|-------|------|-----------|---------|
| Reactive reads/writes | obsidian-mcp-wrapper | `work/`, `personal/`, `family/` | On-demand |
| Source synthesis | wiki-ingest (claude -p) | `wiki/``raw/` | 22:00 nightly |
| Knowledge curation | wiki-curation (Hermes cron) | `wiki/` | 02:00 daily |
| Vault sync | sync-vault.sh | entire vault | Hourly |
## Why obsidian-mcp Is Wrapped
The wrapper exists because `obsidian-mcp` v1.0.6 had four production-breaking
bugs that would have been too fragile to fix upstream (updates would reintroduce
them). A proxy wrapper is cleaner: it intercepts the MCP protocol stream without
modifying the underlying package.
See [[obsidian-mcp-wrapper]] for the four bugs and their fixes.
## Vault Sync (Eagle ↔ Taiga ↔ Kraken)
All three nodes share a sparse vault via a TrueNAS bare git repo. Eagle has
the full vault; Kraken and Taiga have `personal/` and `family/` only (sparse
checkout). The `sync-vault.sh` cron runs hourly on all three.
```
Eagle (full) ──push/pull──┐
Taiga (sparse) ────────────├── mallexxx.duckdns.org:/mnt/RED_2TB/storage/git/obsidian-vault.git
Kraken (sparse) ───────────┘
```
## See Also
[[obsidian-mcp-wrapper]] [[wiki-ingest-process]] [[vault-filling-guide]]
[[personal-os-architecture]]
+78
View File
@@ -0,0 +1,78 @@
---
namespace: work
tags: [system, vault, guide]
last_synced: 2026-04-27
confidence: 1.0
---
# Vault Filling Guide
How to populate `~/obsidian/`. Eagle can write to any section below except `wiki/` and `raw/`.
## Directory Map
```
~/obsidian/
├── wiki/ ← LLM-generated ONLY (wiki-ingest). Never write here manually.
├── raw/ ← Symlinks to external files ONLY. Never write content here.
├── work/ ← Alex's work notes. Eagle can write with confirmation.
│ ├── projects/ One file per active project.
│ ├── decisions/ Architectural/process decisions with date prefix.
│ └── tech-design/ References to designs, review notes.
├── personal/ ← Alex's personal notes. Eagle can write with confirmation.
│ ├── documents/ WHERE docs live (not the docs themselves).
│ ├── instructions/ Personal how-tos and procedures.
│ └── projects/ Personal side projects.
└── family/ ← Household knowledge. Eagle can write with confirmation.
├── how-to/ Practical guides (devices, home equipment, procedures).
├── documents/ WHERE family documents live.
├── contacts/ Emergency contacts, doctors, services.
└── schedule/ Recurring schedules, school, events.
```
## File Naming
- `work/projects/``kebab-case-project-name.md` (e.g. `ios-duck-ai-toggle.md`)
- `work/decisions/``YYYY-MM-topic.md` (e.g. `2026-04-personal-os-arch.md`)
- `family/how-to/``kebab-case-topic.md` (e.g. `truenas-access.md`, `router-reset.md`)
- `family/contacts/``emergency.md`, `doctors.md`, `services.md`
- `family/documents/``apartment.md`, `insurance.md`, `passports.md` (location pointers, not the files)
## Frontmatter
Every file should have:
```yaml
---
namespace: work | personal | family
tags: [tag1, tag2]
created: YYYY-MM-DD
updated: YYYY-MM-DD
---
```
## What Goes Where
| Info type | Location |
|-----------|----------|
| Active work project status, decisions, links | `work/projects/SLUG.md` |
| "We decided X because Y" (architectural) | `work/decisions/YYYY-MM-TOPIC.md` |
| Home network / NAS / router config | `family/how-to/DEVICE.md` |
| Where passport/insurance/lease is stored | `family/documents/TOPIC.md` |
| Emergency contacts, doctors | `family/contacts/CATEGORY.md` |
| Recurring events, school schedule | `family/schedule/TOPIC.md` |
| Personal project notes | `personal/projects/SLUG.md` |
| Personal procedures (backups, etc.) | `personal/instructions/TOPIC.md` |
## Eagle Write Protocol
1. Show draft content before writing (never silently create)
2. Confirm namespace (`work` / `personal` / `family`)
3. Write the file
4. Run `cd ~/obsidian && git add -A && git commit -m "..."` to save
## What Eagle Should NOT Do
- Write anything to `wiki/` — that's wiki-ingest's job
- Modify `raw/` — those are symlinks managed manually
- Create files without showing the content first
- Overwrite existing files without showing a diff
+35
View File
@@ -0,0 +1,35 @@
---
title: Vault Namespace Rules
created: '2026-05-23'
updated: '2026-05-23'
type: tech
namespace: personal
tags: [vault, meta, conventions]
related:
- "[[vault-filling-guide]]"
---
# Vault Namespace Rules
Rules for where notes live in the Obsidian vault. See also [[SCHEMA]] for
full frontmatter conventions.
## Namespace Assignments
- `personal`: Eagle infra, personal projects, dev tooling
- `family`: Kraken media stack, HTPC, Jellyfin, family content
- `work`: DuckDuckGo / DDG tasks and tooling
## Directory Map
```
wiki/concepts/ — how-it-works explanations
wiki/tech/ — infra how-tos, tool configs, pitfalls
wiki/ideas/ — speculative, not yet decided
personal/projects/ — project status & decisions
family/projects/ — family-shared project status
family/how-to/ — family infra procedures
```
Note: this file was reconstructed from a lost pointer. Extend with actual
rules as they emerge.
+100
View File
@@ -0,0 +1,100 @@
---
source: raw/wiki-ingest-prompt.md
content_hash: 29f6a0914d428cf62eeb2a832d6b3048edf3e1d16aa7714c09f686377394d128
namespace: work
last_synced: 2026-04-27
confidence: 0.9
tags: [wiki, ingest, agent, prompt]
---
# Wiki Ingest Process
The agent prompt that maintains `~/obsidian/wiki/` as a synthesised
knowledge base over external project files exposed via symlinks in
`~/obsidian/raw/`. Run locally by `claude -p` (the launchd job
`run-wiki-ingest.sh` at 22:00) — not via API, because file-system
writes require a local Claude Code session. See
[[personal-os-architecture]] for where this fits in the broader system.
## Discovery loop
For every file in `~/obsidian/raw/`:
1. Read the file (skip if the symlink target is missing).
2. Compute SHA256 with `shasum -a 256`.
3. Look for a wiki page whose frontmatter has `source: raw/<filename>`.
4. If a page exists and its `content_hash` already matches, skip.
5. Otherwise enqueue for synthesis.
If nothing changed, the agent prints
"Wiki is up to date. Nothing to ingest." and exits.
## Synthesis rules
- Wiki pages are named by **topic**, not source filename
(e.g. `personal-os-schema.md`, not `schema.md`).
- Output is a **synthesis**, never a verbatim copy. Extract facts,
decisions, and structure. Hard cap of 800 words per page; split
into linked pages if the topic is larger.
- Use `[[double brackets]]` for cross-references between wiki pages.
## Page format
Frontmatter is the source of truth for incremental ingestion:
```yaml
source: raw/<filename>
content_hash: <sha256>
namespace: work
last_synced: <YYYY-MM-DD>
confidence: 0.8
tags: [tag1, tag2]
```
Body has a title, synthesis prose, a `## Key Points` list, and a
`## Related` line of `[[wiki-links]]`.
## Confidence ladder
- **1.0** — reserved for human-written notes in `work/`, `personal/`,
`family/`. The agent must never edit these files.
- **0.9** — highly structured / authoritative source (e.g. a schema).
- **0.8** — clear single source. Default.
- **0.6** — inferred or partial content.
## Hard rules
- Never copy source files verbatim — always synthesise.
- Never edit any file with `confidence: 1.0`.
- Never process files in `namespace: family` unless explicitly told.
- Always update `content_hash` and `last_synced` after writing a page.
- Skip broken symlinks; do not create a wiki page for them.
## Reporting
After the run, the agent emits a summary:
```
Wiki Ingest — <date>
Processed: N files
Created: N new pages
Updated: N pages
Skipped: N (unchanged)
Pages updated:
- wiki/<filename> (source: raw/<source>)
```
## Key Points
- Hash-based incremental: re-runs are cheap when nothing changed.
- Topic-named pages decouple the wiki from source-file naming.
- Frontmatter `source` + `content_hash` is the ingestion contract;
the [[personal-os-schema]] mirrors the same idea in `wiki_pages`.
- Human-edited (confidence 1.0) pages are immutable to the agent —
the trust boundary between synthesis and curated knowledge.
- Local-only execution: writes to `~/obsidian/` need a real FS,
so the job runs under launchd via `claude -p`, not the API.
## Related
[[personal-os-architecture]] [[personal-os-schema]]
+125
View File
@@ -0,0 +1,125 @@
---
title: Wiki Schema
created: '2026-05-13'
updated: '2026-05-25'
type: meta
---
# Wiki Schema
## Domain
Knowledge base covering Alex's Personal OS ecosystem: the multi-agent
productivity/context system, home infrastructure (Eagle Mac M4, Taiga TrueNAS,
Kraken RPi5), personal projects (Reflect app, etc.), and DuckDuckGo work tooling.
**In scope:** agent architecture, data pipelines, home infra, dev tooling,
personal projects, vault management.
**Out of scope:** raw Asana tasks, family documents, ephemeral briefs.
## Conventions
- File names: lowercase, hyphens, no spaces (e.g., `personal-os-architecture.md`)
- Every wiki page starts with YAML frontmatter (see below)
- Use `[[wikilinks]]` to link between pages (minimum 2 outbound links per page)
- When updating a page, always bump the `updated` or `last_synced` date
- Every new page must be added to `index.md` under the correct section
- Every action must be appended to `log.md`
- Subdirectories: `concepts/` for concept/how-it-works pages,
`tech/` for infra/tool how-tos, `comparisons/` for side-by-side analyses,
`entities/` for people/orgs/projects, `queries/` for filed query results
## Frontmatter
Wiki pages synthesised from `raw/`:
```yaml
---
source: raw/<filename>
content_hash: <sha256>
namespace: work | personal | family
last_synced: YYYY-MM-DD
confidence: 0.8
tags: [tag1, tag2]
---
```
Hand-written or agent-synthesised pages:
```yaml
---
title: Human Readable Title
created: YYYY-MM-DD
updated: YYYY-MM-DD
type: concept | entity | comparison | tech | query
namespace: work | personal | family
tags: [from taxonomy below]
sources: [raw/articles/source.md]
confidence: high | medium | low
---
```
**Confidence ladder:**
- `1.0` / `high` — human-written; agent must never edit these files
- `0.9` — highly structured / authoritative source (schema, infra docs)
- `0.8` / `medium` — single clear source; default for agent synthesis
- `0.6` / `low` — inferred or partial content
Pages with `confidence: 1.0` are immutable to the wiki-curation agent.
## Tag Taxonomy
Personal OS:
- `system` `architecture` `agent` `eagle` `executor` `pipeline` `sync`
- `rules` `self-modification` `meta` `purpose`
Work / DDG:
- `asana` `ddg` `workflow` `task-management`
Infrastructure:
- `infra` `kraken` `taiga` `htpc` `docker` `nas` `raspberry-pi` `vps`
- `obsidian` `vault` `wiki` `ingest`
- `media-pipeline` `arr`
Projects:
- `reflect-app` `psychology` `ios` `android` `skip-tools`
- `project` `startup`
Technical:
- `schema` `postgres` `database` `mcp` `hermes`
- `debugging` `pitfalls` `how-to`
Rule: every tag on a page must appear in this taxonomy. Add new tags HERE
before using them. Avoid tag sprawl.
## Page Thresholds
- **Create a page** when an entity/concept appears in 2+ sources OR is
central to one source
- **Add to existing page** when a source mentions something already covered
- **DON'T create a page** for passing mentions or things outside the domain
- **Split a page** when it exceeds ~200 lines
- **Never edit** pages with `confidence: 1.0`
## Update Policy
When new information conflicts with existing content:
1. Check dates — newer sources generally supersede older ones
2. If genuinely contradictory, note both positions with dates
3. Never silently overwrite — show what changed
## Directory Structure
```
wiki/
├── SCHEMA.md ← this file
├── index.md ← page catalog
├── log.md ← action log (append-only)
├── *.md ← root-level pages (legacy + core)
├── concepts/ ← how-it-works, design rationale
├── tech/ ← infra/tool how-tos (device-specific)
├── comparisons/ ← side-by-side analyses
├── entities/ ← people, projects, products
├── queries/ ← filed query results
└── raw/ ← immutable sources
├── inbox/ ← new files to process
└── inbox/processed/ ← after ingestion
```
+34
View File
@@ -0,0 +1,34 @@
---
title: Reflect — Skip Fuse (SwiftUI/Kotlin)
created: '2026-05-23'
updated: '2026-05-23'
type: tech
namespace: personal
tags: [reflect, swift, kotlin, ios, android, pitfalls]
related:
- "[[entities/psychologist-app]]"
- "[[tech/gitea-config]]"
---
# Reflect — Skip Fuse (SwiftUI/Kotlin)
## Key Pitfall
When adding a **new SwiftUI View** to the Reflect iOS codebase, Skip Fuse
does **not** auto-generate the Kotlin counterpart. A manual Kotlin stub is
required in **2 places**:
1. The View class file in the Android module
2. The registration/factory in the Android navigation/router
## Why
Skip Fuse transpiles shared SwiftUI code to Kotlin, but new View types
require explicit Kotlin stubs until Skip's transpilation coverage catches up.
## Checklist for New SwiftUI Views
- [ ] Add SwiftUI view as normal in iOS target
- [ ] Create matching Kotlin stub in `android/src/.../views/`
- [ ] Register in Android router/factory
- [ ] Run `./gradlew build` to verify no missing class errors