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
+136
View File
@@ -0,0 +1,136 @@
---
title: Arr Stack — Kraken
created: '2026-05-23'
updated: '2026-05-27'
type: tech
namespace: family
tags: [arr, radarr, sonarr, prowlarr, transmission, jellyfin, infra, kraken]
related:
- "[[tech/jellyfin-config]]"
- "[[tech/arr-stack-taiga]]"
- "[[concepts/kraken-media-stack]]"
---
# Arr Stack — Kraken
## Pipeline
```
Prowlarr → Radarr + Sonarr → Transmission → Jellyfin
router.py (cron 10min)
symlinks → /media/{movies,cartoons,series,documentaries,...}/
```
Два параллельных пути поступления контента:
1. **Radarr/Sonarr** → качают в `/media/movies-radarr/` и `/media/series-sonarr/`
2. **Transmission (ручные торренты)**`on-download-complete.sh``sync.py``/media/{movies,cartoons,...}/`
### router.py — жанровая маршрутизация
`router.py` читает NFO-файлы (которые Radarr/Sonarr создают при загрузке), определяет жанр и возрастной рейтинг, и создаёт symlink в нужную Jellyfin-библиотеку:
| Источник | Условие | Цель |
|---|---|---|
| movies-radarr/ | Documentary | `/media/documentaries/` |
| movies-radarr/ | Animation + !R/18+ | `/media/cartoons/` |
| movies-radarr/ | всё остальное | `/media/movies/` |
| series-sonarr/ | Documentary | `/media/documentaries-series/` |
| series-sonarr/ | Animation + !TV-MA | `/media/cartoons-series/` |
| series-sonarr/ | всё остальное | `/media/series/` |
**Расположение:** `/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/docker/media-pipeline/router.py`
**Запуск:** `python3 router.py --dry-run` (проверка) / `python3 router.py --apply` (исполнение)
### media-router.sh — cron-скрипт для Telegram-уведомлений
Обёртка над `router.py`, которая:
- запускает `router.py --apply`
- если `Nothing to do`**SILENT**, ничего не шлёт
- если созданы symlink'и или ошибка — отправляет отчёт в Telegram Kraken
**Расположение:** рядом с `router.py``media-router.sh`
**Cron (Kraken crontab):** каждые 10 минут
**Telegram:** kraken_htpc_bot → чат @mallex87
### sync.py — обработка ручных торрентов (Transmission)
`sync.py` работает внутри Transmission-контейнера. При завершении загрузки:
1. Определяет название через TMDB API
2. Определяет жанр
3. Перемещает файл в `/media/{movies,cartoons,series,documentaries}/`
4. Обновляет `downloadDir` в Transmission через RPC
**Расположение:** `/srv/.../docker/media-pipeline/sync.py`
**Триггер:** `on-download-complete.sh` (смонтирован в Transmission как `/config/on-download-complete.sh:ro`)
## Transmission
- **download-dir:** `/downloads` (без `complete`)
- **incomplete-dir:** `/downloads/incomplete`
- **Язык:** отключён auth для локальных
- **RPC:** `localhost:9091`
При перемещении файлов для уже завершённых торрентов:
1. Переименовать файл/папку под кейс торрента (Linux ext4 case-sensitive)
2. Вызвать `torrent-set-location` с `move=false`
3. Запустить торрент на сидирование
## Cron'ы на Kraken (crontab)
```
0 * * * * bash ~/scripts/sync-vault.sh # Obsidian sync
0 * * * * pgs-to-srt.sh /media/downloads # PGS→SRT новые
0 3 * * * pgs-to-srt.sh /media # PGS→SRT вся библиотека
0 1 * * * bash ~/scripts/watchlist-nightly.sh # watchlist nightly
0 9 * * 0 bash ~/scripts/watchlist-discover.sh # watchlist discover
*/10 * * * * bash .../media-pipeline/media-router.sh # media router (Telegram)
```
## API Keys
| Service | API Key |
|-------------|----------------------|
| Radarr | `cbcb8ec3...` |
| Sonarr | `15fbec32...` |
| Prowlarr | `134ac38a...` |
(Full keys truncated — retrieve from each service's settings page.)
## Ports (default Docker network)
All services accessible at `kraken:<port>`:
| Service | Port |
|--------------|------|
| Radarr | 7878 |
| Sonarr | 8989 |
| Prowlarr | 9696 |
| Transmission | 9091 |
| Jellyfin | 8096 |
## Jellyfin библиотеки
| Библиотека | Тип | Путь |
|---|---|---|
| Movies | movies | `/media/movies` |
| Series | tvshows | `/media/series` |
| Cartoons | movies | `/media/cartoons` |
| Cartoon Series | tvshows | `/media/cartoons-series` |
| Documentaries | movies | `/media/documentaries` |
| Documentary Series | tvshows | `/media/documentaries-series` |
> `movies-radarr` и `series-sonarr` НЕ добавлены в Jellyfin — router.py создаёт symlink'и из них в перечисленные пути.
## Известные проблемы
- **17 фильмов в Radarr `hasFile=False`** — ждут подходящего релиза в Prowlarr. Scream (1996) в процессе загрузки.
- **Prowlarr** — если долго не находит релизы, проверить статус индексеров (`/api/v1/health`).
## Notes
- See [[arr-stack-taiga]] for Taiga (secondary) arr stack
- Media pipeline pitfalls: [[media-pipeline-pitfalls]]
- Синтез: [[concepts/kraken-media-stack]]
+31
View File
@@ -0,0 +1,31 @@
---
title: Arr Stack — Taiga
created: '2026-05-23'
updated: '2026-05-23'
type: tech
namespace: family
tags: [arr, taiga, infra, media, pitfalls]
related:
- "[[tech/arr-stack-kraken]]"
---
# Arr Stack — Taiga
Secondary arr stack on Taiga (TrueNAS), added 2026-05-20.
## Notes from Setup (2026-05-20)
- Config and pitfalls recorded during initial Taiga arr stack deployment
- `router.py` was customized — check `/mnt/RED_2TB/docker/arr-taiga/router.py`
for current state
## Key Differences from Kraken Stack
- Taiga = TrueNAS host (storage-focused)
- Kraken = Raspberry Pi 5 (playback-focused)
- Taiga stack handles acquisition; Kraken handles serving to Jellyfin
## Pitfalls
(Details were referenced but not captured. Update this page after next
Taiga arr maintenance session.)
+27
View File
@@ -0,0 +1,27 @@
---
title: Gitea Config
created: '2026-05-23'
updated: '2026-05-23'
type: tech
namespace: personal
tags: [infra, gitea, git, credentials]
related:
- "[[tech/kraken-network]]"
---
# Gitea Config
**URL:** `http://git.mallexxx.duckdns.org` (external) / `http://172.16.3.4:3000` (internal LAN)
**User:** `git_admin`
## API Token
Token name: `eagle-reflect`
Token value: `f2e679a571f4c599d64b384a711043f995fd8844`
Used by Eagle for repo operations (push, PR creation, hook management).
## Notes
- External URL uses DuckDNS DDNS pointing to home IP
- Internal API endpoint preferred when on home network
+37
View File
@@ -0,0 +1,37 @@
---
title: Hermes Docker on Kraken
created: '2026-05-23'
updated: '2026-05-29'
type: tech
namespace: personal
tags: [hermes, docker, kraken, infra, how-to]
related:
- "[[tech/kraken-network]]"
- "[[concepts/hermes-deployment-patterns]]"
- "[[tech/hermes-kraken-api]]"
---
# Hermes Docker on Kraken
## Key Rule
Hermes runs via **`docker run`**, NOT `docker compose`. No compose file.
## Launch Pattern
```bash
docker run -d \
--name hermes \
--restart unless-stopped \
-v ~/.hermes:/root/.hermes \
... \
hermes-image:tag
```
(Add actual flags from the running container: `docker inspect hermes`)
## Notes
- Using `docker run` keeps restart behavior explicit
- No compose means no accidental `docker compose down` wipes it
- Config/memory volume: `~/.hermes` on Kraken host
+229
View File
@@ -0,0 +1,229 @@
---
title: Hermes на Eagle (Mac M4 Max) — Настройка и подводные камни
type: reference
namespace: work
tags:
- hermes
- mac
- eagle
- claude-proxy
- zulip
- pitfalls
created: '2026-05-21'
updated: '2026-05-22'
last_synced: '2026-05-22'
confidence: 0.9
---
# Hermes на Eagle (Mac M4 Max) — Настройка и подводные камни
Hermes работает нативно (не в Docker) на Mac через `hermes gateway`.
Транспорт — Zulip (запущен в Docker). Провайдер модели — openclaw-claude-proxy
(см. ниже).
## Компоненты
| Компонент | Расположение | Запуск |
|-----------|-------------|--------|
| Hermes config | `~/.hermes/config.yaml` | — |
| claude-proxy (Claude proxy) | `/opt/homebrew/bin/claude-proxy` | launchd `ai.claude-proxy` |
| Zulip stack | `~/Developer/zulip/docker-compose.yml` | `docker compose up -d` |
| Obsidian MCP | mcpvault | встроен в Hermes toolset |
---
## openclaw-claude-proxy — обход rate limit Claude API
### Проблема
`provider: claude-code` в Hermes использует OAuth-токен напрямую через
API Anthropic — и упирается в rate limit подписки. Лимиты сбрасываются
раз в час. API-ключа нет (политика организации).
### Почему не cmappy
cmappy (`claude-max-proxy-py`) молча выбрасывает поле `tools` из запроса —
передаёт только текст в `claude --print`. Результат: Hermes не может
использовать **ни один инструмент** (скиллы, MCP, терминал). Только голый
чат.
### Решение: openclaw-claude-proxy
[mehdic/claude-proxy](https://github.com/mehdic/claude-proxy) (npm:
`openclaw-claude-proxy`) — Node.js сервер, запускает `claude --print` как
subprocess и предоставляет OpenAI-совместимый `/v1/chat/completions` на
порту 3456. **Поддерживает tool_use** — инжектирует схемы инструментов в
системный промпт, парсит JSON tool_call из ответа, возвращает стандартный
OpenAI `tool_calls`. Caller (Hermes) сам выполняет инструменты.
**Важно:** `CLAUDE_PROXY_TOOLS_TRANSLATION=1` НЕ включать — этот режим
выполняет MCP инструменты внутри CLI и Hermes ничего не получает.
### Установка
```bash
npm install -g openclaw-claude-proxy
```
### Wrapper-скрипт (обязателен для launchd)
`~/.local/bin/claude-proxy-start.sh`:
```bash
#!/bin/zsh
# launchd не наследует среду login-сессии — токен нужно загружать явно
set -a
source /Users/admin/.hermes/.env 2>/dev/null
set +a
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
exec /opt/homebrew/bin/claude-proxy 3456
```
**Pitfall:** без явного `source ~/.hermes/.env` claude-proxy не видит
`CLAUDE_CODE_OAUTH_TOKEN` и прогревочные процессы падают с "Not logged in".
**Pitfall:** без явного PATH Claude CLI не найден (`/opt/homebrew/bin/claude`
не в launchd PATH).
### launchd сервис
`~/Library/LaunchAgents/ai.claude-proxy.plist`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>ai.claude-proxy</string>
<key>ProgramArguments</key>
<array>
<string>/Users/admin/.local/bin/claude-proxy-start.sh</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key><string>/Users/admin</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key>
<string>/Users/admin/.hermes/logs/claude-proxy.log</string>
<key>StandardErrorPath</key>
<string>/Users/admin/.hermes/logs/claude-proxy.log</string>
</dict>
</plist>
```
```bash
launchctl load ~/Library/LaunchAgents/ai.claude-proxy.plist
```
**Pitfall при перезагрузке:** если старый процесс ещё держит порт 3456:
```bash
lsof -ti :3456 | xargs kill -9
launchctl unload ~/Library/LaunchAgents/ai.claude-proxy.plist
launchctl load ~/Library/LaunchAgents/ai.claude-proxy.plist
```
### Конфигурация Hermes
`~/.hermes/config.yaml` (секция model):
```yaml
model:
default: claude-sonnet-4-6
# provider: claude-code # отключён — упирается в rate limit OAuth API
provider: custom
base_url: 'http://localhost:3456/v1'
```
**Pitfall:** `provider: openai` не существует в Hermes — нужно `custom`.
**Pitfall:** `base_url` должен включать `/v1` (Hermes дописывает
`/chat/completions`). Без `/v1` → 404.
---
## Zulip Docker — подводные камни
### RabbitMQ: пользователи сбрасываются после перезапуска
**Симптом:** Zulip отдаёт 500 на `/api/v1/register`. В логах RabbitMQ —
паника Khepri (Raft WAL). Пользователи в RabbitMQ исчезают.
**Причина:** RabbitMQ 4.x использует Khepri вместо Mnesia. При переполнении
диска WAL не может записаться → Khepri сбрасывает состояние → пользователи
исчезают. `RABBITMQ_DEFAULT_USER/PASS` применяются только при **первом
старте** с пустым volume — повторный запуск их не восстанавливает.
**Решение:**
1. `docker system prune` — освободить место на диске (Docker VM sparse disk
не освобождает место автоматически).
2. Добавить `RABBITMQ_ERLANG_COOKIE` в env rabbitmq (стабилизирует cookie
через перезапуски).
3. При повреждённом volume — стереть и пересоздать:
```bash
docker compose down
docker volume rm zulip_zulip-rabbitmq
docker compose up -d
```
### Log rotation (обязательно!)
Без ротации логи заполняют Docker VM (~6 ГБ за несколько месяцев).
`docker-compose.yml` — добавить к каждому сервису:
```yaml
# zulip:
logging:
driver: json-file
options:
max-size: "50m"
max-file: "5"
# rabbitmq, memcached, redis:
logging:
driver: json-file
options:
max-size: "20m"
max-file: "3"
```
### Docker VM sparse disk
Mac Docker Desktop использует sparse virtual disk. Место, освобождённое
внутри VM, не возвращается хосту автоматически. `docker system prune`
запускает compaction.
---
## Obsidian MCP
Везде используется `mcpvault` (не `obsidian-mcp`).
| Конфиг | Путь |
|--------|------|
| Claude Code CLI | `~/.claude/.mcp.json` |
| Claude Desktop App | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Hermes | встроен через toolset |
Пример конфига (одинаковый для обоих):
```json
{
"mcpServers": {
"obsidian": {
"command": "mcpvault",
"args": ["/Users/admin/obsidian"]
}
}
}
```
**Pitfall:** `obsidian-mcp` (npm) был удалён — если остался в конфиге,
Claude падает с "Failed to spawn process". Проверить логи:
`~/Library/Logs/Claude/mcp-server-obsidian.log`.
---
## Связанные страницы
- [[tech/hermes-docker-kraken]] — Hermes на Кракене (Docker)
- [[tech/kraken-network]] — сетевая топология
- [[concepts/hermes-deployment-patterns]] — сравнение трёх моделей деплоя Hermes
+93
View File
@@ -0,0 +1,93 @@
---
title: Hermes Kraken — OpenAI-Compatible API Server
created: '2026-05-29'
updated: '2026-05-29'
type: tech
namespace: personal
tags: [hermes, kraken, infra, agent, how-to]
sources:
- family/projects/aide-kraken-backend.md
confidence: medium
related:
- "[[tech/hermes-docker-kraken]]"
- "[[tech/vps-qentra]]"
---
# Hermes Kraken — OpenAI-Compatible API Server
Hermes on Kraken exposes an OpenAI-compatible `/v1/chat/completions`
endpoint (`gateway/platforms/api_server.py`). Exposed externally via
Cloudflare Tunnel → used by the Android **Aide** app as a Hermes-backed
assistant.
## Architecture
```
Android (Aide)
└── HTTPS → hermes.kraken.qentra.top/v1/chat/completions
↓ Cloudflare Tunnel
cloudflared (Kraken, host network)
↓ localhost:8642
hermes-kraken (docker run, network_mode: host)
Hermes gateway → Gemini / OpenRouter / ...
```
`network_mode: host` means port 8642 is directly on the Kraken host —
no port mapping needed.
## Config (Kraken /opt/data/config.yaml)
```yaml
api_server:
enabled: true
port: 8642
host: "0.0.0.0"
key: "<secret>" # openssl rand -hex 32
```
Restart container via Portainer or:
```bash
BASE=http://localhost:9000
KEY="ptr_AJY+Ba9A7f6pcAHZfDD5koU4stkKgJCdbTEXLDLxn0g="
EP=3
CID=$(curl -s -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/json" \
| jq -r '.[] | select(.Names[] | contains("hermes-kraken")) | .Id')
curl -s -X POST -H "X-API-Key: $KEY" \
"$BASE/api/endpoints/$EP/docker/containers/$CID/restart"
```
Health check: `curl -s http://localhost:8642/health`
## Cloudflare Tunnel Setup
Tunnel name: `kraken`. Public hostname (CF Zero Trust dashboard):
- **Hostname:** `kraken.qentra.top`
- **Type:** HTTP (not SSH)
- **URL:** `localhost:8642`
SSH to Kraken still works via VPS reverse tunnel (port 2223) — the CF
hostname change does not affect SSH access.
## Aide Android Client Config
Provider: Custom Endpoint
| Field | Value |
|-------|-------|
| Base URL | `https://kraken.qentra.top/v1` |
| API Key | secret from config.yaml |
| Model | `hermes-agent` (maps to Hermes internally) |
## What Was Not Changed
- cloudflared container — already running
- VPS reverse SSH tunnel — unaffected
- `network_mode: host` on hermes-kraken — already set
## See Also
- [[tech/hermes-docker-kraken]] — how Hermes runs on Kraken (docker run
pattern, no compose)
- [[tech/vps-qentra]] — VPS qentra.top: Cloudflare + nginx stack
+74
View File
@@ -0,0 +1,74 @@
---
title: HTPC Bazzite Ecosystem — Overview
created: '2026-05-28'
updated: '2026-05-28'
type: concept
namespace: family
tags: [htpc, infra, how-to]
sources: []
confidence: medium
related:
- "[[htpc-bazzite-proton]]"
- "[[tech/htpc-steam-emulators]]"
- "[[tech/htpc-kodi-layout]]"
- "[[tech/htpc-magic4pc]]"
---
# HTPC Bazzite Ecosystem — Overview
Synthesis across [[htpc-bazzite-proton]], [[tech/htpc-steam-emulators]],
[[tech/htpc-kodi-layout]], and [[tech/htpc-magic4pc]].
Intel i7-6800K / RX 570 / Bazzite 44 at `192.168.1.86`. Connected to
LG webOS TV (`192.168.1.75`) via HDMI.
## Mode Map
| Mode | Entry | What runs |
|------|-------|-----------|
| Steam Big Picture (gamescope) | Default boot | Steam Gaming Mode + 39 non-Steam shortcuts |
| KDE Desktop | Ctrl+Alt+F2 or Steam → "Switch to Desktop" | Full desktop + Kodi launcher |
| Kodi | From KDE | NFS/SMB media browsing (see [[tech/htpc-kodi-layout]]) |
| Emulators | Steam shortcuts (gamescope or KDE) | RetroArch, EmuDeck |
## Input Devices
| Device | Role |
|--------|------|
| 2× 8BitDo Ultimate dongles | Primary gamepads |
| Xbox 360 Wireless Receiver | Secondary gamepad |
| LG Magic Remote (magic4pc) | TV remote → mouse/keyboard via [[tech/htpc-magic4pc]] |
**8BitDo pitfall:** both dongles must be on separate physical USB ports
(not the same hub). See [[tech/htpc-steam-emulators]].
## Windows Games via Proton
39 games added as non-Steam shortcuts. Most on GE-Proton10-34.
Compatibility matrix and fix recipes: [[htpc-bazzite-proton]].
Highlights:
- VK GameCenter (Atomic Heart) — working ✅
- Rayman Origins — GE-Proton + `d3d9=b`
- Doom 3 BFG — Luxtorpeda (rbdoom-3-bfg flatpak) ✅
- 5 games still broken (Brawlhalla, Castle Crashers, Worms ×2, Duck Game)
## Non-Steam Shortcut Management
VDF shortcuts via Python `vdf` module. **Rules:**
1. Stop Steam before editing `shortcuts.vdf`
2. Use `vdf.binary_load` — never manual byte editing
3. Keep `bak-eagle` (23 May, 106 entries) as recovery baseline
See full guide in [[htpc-bazzite-proton]] § VDF.
## Cover Art
`steam-grid-artwork.py` (at vault root) — fetches artwork from
SteamGridDB for non-Steam shortcuts. Run when adding new shortcuts.
## Maintenance Notes
- `rpm-ostree upgrade` resets `/usr/local/bin/magic4pc` — rebuild and redeploy
- VDF backup: `saves-backup.service` (systemd, daily rsync)
- Proton updates: extract tar.gz into `~/.steam/steam/compatibilitytools.d/`
+319
View File
@@ -0,0 +1,319 @@
---
confidence: medium
created: '2026-05-23'
namespace: personal
sources:
- family/projects/htpc-windows-games-fix.md
tags:
- htpc
- infra
- how-to
- pitfalls
- vk-play
- atomic-heart
- vdf
- doom
title: HTPC Bazzite — Proton Compatibility
type: tech
updated: '2026-05-27'
---
# HTPC Bazzite — Windows Game Proton Compatibility
Bazzite HTPC at `bazzite@192.168.1.86`. 39 non-Steam Windows games added as
Steam shortcuts (2026-05-22). Default tool: Proton Experimental; older titles
on Proton 8.0.
---
## Working ✅
- Fallout 4
- Vladik Brutal
- Red Alert 3
- **Rayman Origins** (fixed 2026-05-23 — GE-Proton10-34 + `WINEDLLOVERRIDES=\"d3d9=b\" %command%`)
- Samurai Gunn (works but **gamepad not detected**)
- GTA V, Cuphead (assumed; not retested)
<<<<<<< HEAD
- **VK GameCenter** (GE-Proton10-34, shared prefix c Lutris, авторизация через Lutris + `vk-login-helper`)
- **Atomic Heart VK** (через VK GameCenter, 97GB, `Data/gamecenter/atomic heart/`, URL-схема `vkplay://play/0.2015959`)
=======
- **VK GameCenter** (GE-Proton10-34, `Data/gamecenter/GameCenter/`)
- **Atomic Heart VK** (через VK GameCenter, 97GB, `Data/gamecenter/atomic heart/`)
>>>>>>> nas/main
- **Doom 3 BFG** (через flatpak Classic-RBDOOM-3-BFG)
## Removed from Steam shortcuts ✅
- Human Fall Flat (3928445186) — already in real Steam
- Shovel Knight (2526703384) — already in real Steam
## Mapped to GE-Proton10-34 but still broken
| Game | AppID | Symptom | LaunchOpt set |
|------|-------|---------|---------------|
| Brawlhalla | 2410005953 | Doesn't start | `WINEDLLOVERRIDES=\"msvcp140,vcruntime140=n,b\"` |
| Castle Crashers | 3048471472 | "crash, mini dump sent" | `WINEDLLOVERRIDES=\"msvcp140,vcruntime140=n,b\"` |
| Worms WMD | 3846411911 | Doesn't start | `WINEDLLOVERRIDES=\"msvcp140,vcruntime140,d3dcompiler_47=n,b\"` |
| Worms World Party | 2401592779 | Doesn't start | `WINEDLLOVERRIDES=\"msvcp140,vcruntime140=n,b\"` |
| Duck Game | 2926094216 | Won't launch | `WINEDLLOVERRIDES=\"msvcp140,vcruntime140=n,b\"` |
- **Doom 3 BFG** (flatpak, appid `-2113091226`) — работает ✅
- **Atomic Heart VK / VK GameCenter** — VK GameCenter запускается через Steam (GE-Proton10-34). Atomic Heart (97GB) установлен через GameCenter на Data. Exe GameCenter скопирован на Data (не на Windows/ — read-only). Symlink `d:` в dosdevices ведёт на `/run/media/bazzite/Data`.
## Target:
## Still broken — fundamental issues
| Game | AppID | Symptom | What's known | What might help |
|------|-------|---------|--------------|-----------------|
| Atomic Bomberman / BM95 | 2875360586 | Crash "instruction at 0x0 referenced 0x7" | Win3.1-era PE32. Tried: `proton_8`, registry `Version=win98`, virtual desktop 640×480, WineBus controllers off | Dedicated 32-bit `WINEARCH=win32`; or DOSBoxX |
| Discworld Noir | 2361018632 | "Please insert disk III" | Russian 2-CD "Zatmenie v2". 69 missing files copied ✅. Drive D: → cd2 as CD-ROM | Try cd1; or mount `.nrg` images |
| Final Fantasy X HD | 2642741094 | Was Japanese, no saves | `GameSetting.ini` replaced (en+HD) from Windows | Test pending |
| Metal Slug X | 2938712902 | Speed wrong, controls dead | Neo-Geo emulator config issue, not Wine | — |
| Metro Exodus | 3594910936 | "Unable to create interface isteamuser" | VC++ runtime copied | Needs Goldberg `steam_api64.dll` + `steam_appid.txt` |
| Split Second | 3871231787 | Green artifacts | `PROTON_USE_WINED3D=1` | Switch to GE-Proton; copy `d3dx9_*.dll` |
| Super Bomberman R2 | 3340354231 | Gamepad not detected | Steam Input config issue | — |
## Gamepad Issues
- **Samurai Gunn, Super Bomberman R2**: not detected → Steam Input "Force Off" / "Use Default" cycle; or `SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD=1`
- **Metal Slug X**: wrong speed in Neo-Geo wrapper
---
## Common Fix Recipes
### "Doesn't start" → try vcrun2019
```
protontricks <AppID> vcrun2019 d3dcompiler_47
```
### XNA 4.0 games (Duck Game, etc.)
```
protontricks <AppID> xna40
```
### DOS-era games (e.g. Atomic Bomberman)
Proton won't work. Use **Steam Tinker Launch** → wrap with DOSBox (dosbox-staging).
### Non-Steam copy of a Steam game (e.g. Metro Exodus)
Game needs `steam_api64.dll` emulator:
1. Drop in Goldberg `steam_api64.dll`
2. Add `steam_appid.txt` with the real App ID (e.g. 412020 for Metro Exodus Enhanced)
### Cyrillic encoding issues (e.g. Discworld Noir)
Try in order:
1. `LANG=ru_RU.UTF-8 %command%`
2. `WINEDLLOVERRIDES=\"gdiplus=n,b\"`
3. Install `ttf-mscorefonts` + set Cyrillic codepage in Wine registry
### White screen / broken D3D9 (Rayman Origins, Split Second)
```
PROTON_USE_WINED3D=1 %command%
```
Or try `WINEDLLOVERRIDES=\"d3d9=b\"` or an older Proton version.
### Doom 3 BFG Edition — чёрные текстуры, краш после интро
Proton ломает текстуры — известный регресс. **Фикс:** Luxtorpeda → rbdoom-3-bfg (native Linux engine).
---
## VK Play / VK GameCenter на Bazzite
### Аккаунты VK Play
- **Основной:** `xxxellam@gmail.com` — Google, залогинен на Bazzite
- **Дополнительный:** `mallexxx@mail.ru` — подключён к VK Play
### Общая схема
VK Play версии игр запускаются через лаунчер VK GameCenter (`GameCenter.exe`) поверх Proton. Лаунчер отвечает за авторизацию и лицензирование (в т.ч. Denuvo-эмуляцию).
Известно:
- VK Play официально подтвердил совместимость Atomic Heart со Steam Deck/Proton.
- Для авторизации используется `mycomgames://` URL-схема. На HTPC без Steam Deck API может потребоваться ручной ввод логина/пароля.
- Альтернатива: Lutris с конфигом из gist.github.com/keyCat (обработчик `mycomgames://` схемы).
- Перед первым запуском может понадобиться `protontricks -555655402 vcrun2019`.
### Как добавить VK Play игру
1. Запустить VK GameCenter через Steam (GE-Proton10-34)
2. Авторизоваться в VK Play
3. Установить игру через лаунчер (Atomic Heart уже установлен)
4. После авторизации игра появится в библиотеке VK Play
### Lutris login workaround (shared prefix with Steam/Proton)
**Проблема:** Steam CEF browser не умеет обрабатывать `mycomgames://` URL-схему, необходимую для OAuth-логина VK Play. При клике "Login" — `ERR_NETWORK_IO_SUSPENDED`.
**Решение:** использовать Lutris для первичной авторизации, потом запускать игру через Steam/Proton.
### Implementation status (2026-05-28)
Все компоненты настроены через SSH (Kraken → HTPC). Последний шаг (логин) требует GUI-сеанса на HTPC.
| Component | Status | Notes |
|-----------|--------|-------|
| `d:` dosdevices symlink (→ Data) | ✅ | `/run/media/bazzite/Data` |
| `handle-vkplay-scheme` script | ✅ | `~/.local/bin/` — обрабатывает `mycomgames://` через Proton 10.0 |
| `.desktop` → MIME `x-scheme-handler/mycomgames` | ✅ | Зарегистрирован на `Vkplay-scheme-handler.desktop` |
| Lutris flatpak | ✅ | `flathub net.lutris.Lutris` |
| Lutris game config (shared prefix) | ✅ | `~/.config/lutris/games/vk-play-shared.yml` |
| `start-vk.cmd` in Proton prefix | ✅ | `drive_c/VKPlay/gamecenter/start-vk.cmd` |
| Steam shortcut VK GameCenter | ✅ | AppID=-1973339958, GE-Proton10-34 |
| Steam shortcut Atomic Heart VK | ✅ | AppID=-1973339958 (через GameCenter) |
| Atomic Heart installed (97GB) | ✅ | `/run/media/bazzite/Data/gamecenter/atomic heart/` |
**Осталось сделать (на HTPC в десктоп-режиме):**
1. Открыть Lutris → VK Play Game Center → Login
2. Авторизоваться в браузере, разрешить `mycomgames://`
3. Вернуться в игровой режим
4. Запустить Atomic Heart через Steam → ожидать что токен в shared prefix работает
1. Установить Lutris через flatpak:
```
flatpak install flathub net.lutris.Lutris
```
2. Создать кастомный пресет VK GameCenter в Lutris, указав **тот же Wine prefix**, что использует Steam:
```
~/.local/share/Steam/steamapps/compatdata/-1973339958/pfx/
```
3. Зарегистрировать `mycomgames://` protocol handler — поместить .desktop-файл, который ассоциирует схему с Lutris:
- Референс: https://gist.github.com/keyCat/a77b1d4d1e0f651b9d3a9818e7f739c5
4. Выполнить логин через Lutris — откроется внешний браузер, авторизация сработает, редирект `mycomgames://` попадёт в Lutris.
5. После успешного логина запускать игру через Steam — токен уже лежит в shared prefix, `mycomgames://` больше не нужен.
**Почему shared prefix:** игра (Atomic Heart, 97GB) НЕ копируется — она уже установлена в `/run/media/bazzite/Data/gamecenter/atomic heart/` через Steam-версию VK GameCenter. Lutris будет использовать тот же префикс, где уже есть dosdevices symlink `d:` → `/run/media/bazzite/Data` и все токены авторизации.
---
## How to Add a Non-Steam Game via VDF
**Шаг 0: Остановить Steam**
```
systemctl --user stop gamescope-session-plus@steam.service
```
**Шаг 1: Прочитать shortcuts.vdf backup**
AppID в shortcuts.vdf — signed int32. В config.vdf CompatToolMapping — unsigned uint32.
```python
import vdf
# read backup
with open('shortcuts.vdf.bak-eagle', 'rb') as f:
d = vdf.binary_loads(f.read())
# edit d['shortcuts']
# write
with open('shortcuts.vdf', 'wb') as f:
f.write(vdf.binary_dumps(d))
```
**Шаг 2: Добавить CompatToolMapping в config.vdf**
Unsigned appid = signed & 0xFFFFFFFF.
**Шаг 3: Запустить Steam, проверить**
**Критически важно:**
- Редактировать ТОЛЬКО при остановленном Steam
- Делать бэкап **до** изменений
- `vdf.binary_loads` — регистрозависим: `AppName`, `Exe`, а не `appname`, `exe`
---
## VDF Troubleshooting
### shortcuts.vdf испорчен (все записи пустые)
Steam перезаписывает VDF при старте если не понимает ручные правки. Восстанавливай из бэкапа.
**Бэкапы** (в `~/.steam/steam/userdata/147839491/config/`):
| Файл | Дата | Entries | Примечание |
|------|------|---------|------------|
| `shortcuts.vdf.bak-eagle` | 23 мая 18:32 | 106 | **Последний чистый бэкап** — не тронут |
| `shortcuts.vdf.bak-launch` | 22 мая 22:25 | 108 | Старый, без Atomic Heart |
| `shortcuts.vdf.whale-restored-YYYY-MM-DD` | по ситуации | 108 | Текущее состояние после восстановления |
| `/run/media/bazzite/Data/backups/saves/shortcuts/shortcuts.vdf.YYYY-MM-DD` | daily | — | Автоматический через saves-backup.service |
Проверка бэкапа:
```python
import vdf
with open("shortcuts.vdf.bak-eagle", "rb") as f:
d = vdf.binary_loads(f.read())
named = sum(1 for e in d["shortcuts"].values() if e.get("AppName"))
print(f"{named} named entries")
```
### VDF Incident — 2026-05-27 (разбор)
**Что произошло:** при попытке добавить VK GameCenter + Atomic Heart VK через Python vdf на работающем Steam, Steam переписал файл при старте, создав 110 пустых записей-заглушек.
**Почему бэкапы тоже пустые:** все бэкапы были сделаны уже после того как Steam испортил файл. Единственный выживший — `bak-eagle` (23 мая, до начала экспериментов).
**Как нашли:** hexdump показал имена игр, но `vdf.binary_loads` не видел их — проблема в регистре ключей (`AppName`, а не `appname`).
**Как восстановили:**
1. Остановлен Steam
2. Считан `bak-eagle` (106 записей) + добавлены VK GameCenter (index 106) и Atomic Heart VK (index 107)
3. Обнаружено что config.vdf уже имеет CompatToolMapping с другими appid от предыдущих попыток
4. Перегенерация с appid, совпадающими с config.vdf
5. Удалены дублирующиеся unsigned записи из config.vdf
6. Создан `shortcuts.vdf.whale-restored-2026-05-27`
**Уроки:**
- Только при остановленном Steam
- Бэкап ДО изменений
- Проверять signed↔unsigned соответствие
- Регистр ключей в vdf.binary_loads
---
## Key Infra Notes
- **shortcuts.vdf**: `~/.steam/steam/userdata/147839491/config/shortcuts.vdf` (binary VDF)
- **config.vdf**: `~/.steam/steam/config/config.vdf` (text VDF, CompatToolMapping block)
- **Wine prefix**: `~/.local/share/Steam/steamapps/compatdata/<AppID>/pfx/`
- **protontricks**: Flatpak, needs `flatpak override --user --filesystem=/var/home/bazzite/.local/share/Steam`
- **Background jobs**: `systemd-run --user --unit=<name>`
- **GE-Proton**: extract tar.gz into `~/.steam/steam/compatibilitytools.d/`, restart Steam
### saves-backup.service
Systemd user service (`/etc/systemd/user/saves-backup.service`):
- Ежедневный rsync Steam userdata
- Копия shortcuts.vdf в `/run/media/bazzite/Data/backups/saves/shortcuts/shortcuts.vdf.YYYY-MM-DD`
- Ротация: удаление старше 30 дней
---
## Fixes Done (filesystem / config / cover-art)
- Cover art swapped: Atomic Bomberman, Discworld Noir, SBR2, FFVI, ALttP, Sunset Riders
- "Captain Commando" → "Sunset Riders"
- CompatToolMapping for all AppIDs:
- **GE-Proton10-34**: Brawlhalla, Castle Crashers, Worms WMD/WP, Duck Game, Rayman Origins, VK GameCenter, Atomic Heart VK, insaneramzes Atomic Heart
- **Proton 8.0**: Atomic Bomberman, Discworld Noir, Metal Slug X, Soldat, Red Alert 3, Split Second
- **Luxtorpeda**: Doom 3 BFG
- **Proton Experimental**: ~24 remaining games
- Doom 3 BFG → Luxtorpeda (rbdoom-3-bfg, 2026-05-27)
- Rayman Legends: Ubisoft Connect + registry fix
- Discworld Noir: symlink, Cyrillic locale, missing files, drive D: as CD-ROM
- FFX HD: English `GameSetting.ini`
- VC++ runtime + d3dcompiler DLLs copied into 6 prefixes from Windows partition
- Duck Game: XNA 4.0 framework dir copied
- Atomic Bomberman: `Version=win98` + virtual desktop 640×480 + WineBus controllers off
- GE-Proton10-34 installed
## Source Assets (Windows partition)
- Ubisoft Game Launcher: `/run/media/bazzite/Windows/Program Files (x86)/Ubisoft/`
- System DLLs: `/run/media/bazzite/Windows/Windows/System32/`, `SysWOW64/`
- XNA 4.0: `/run/media/bazzite/Windows/Program Files (x86)/Microsoft XNA/`
- .NET 4.0: `/run/media/bazzite/Windows/Microsoft.NET/Framework64/v4.0.30319/`
- Discworld Noir source: `/run/media/bazzite/Data/downloads/zatmenie v2 [torrents.ru]/`
- **Discworld Noir (скачан 2026-05-29):** `/run/media/bazzite/Data/downloads/Discworld Noir.isz` (ISZ, 1.1GB, Alcohol 120%)
- **Discworld (оригинал, скопирован 2026-05-29):** `/run/media/bazzite/Data/downloads/DISCWORLD.iso` (ISO, 434MB) — с TrueNAS `/mnt/RED_2TB/backup/Downloads/Distr/games/DISCWORLD.iso`
- FFX settings: `/run/media/bazzite/Windows/Users/mallexxx/documents/square enix/...`
## Open Questions
1. Delete `pfx/` folders for still-broken GE-Proton games (Brawlhalla, Castle Crashers, Worms WMD/WP, Duck Game)
2. Atomic Bomberman: 32-bit Wineprefix outside Proton
3. Discworld Noir: try cd1 or mount `.nrg` images
4. Metro Exodus: Goldberg Steam emulator?
5. **Atomic Heart VK:** test VK Play авторизацию, при необходимости `protontricks vcrun2019`
6. **Doom 3 BFG:** test с Luxtorpeda → rbdoom-3-bfg
## See Also
[[personal-os-architecture]] [[obsidian-mcp-wrapper]] [[family/how-to/htpc-steam-nonsteam-shortcuts]]
+31
View File
@@ -0,0 +1,31 @@
---
title: HTPC Kodi Layout & WoL
created: '2026-05-23'
updated: '2026-05-23'
type: tech
namespace: family
tags: [htpc, kodi, wol, infra, how-to]
related:
- "[[tech/htpc-steam-emulators]]"
---
# HTPC Kodi Layout & WoL
## Wake on LAN
HTPC MAC address stored in router DHCP config. WoL command:
```bash
wakeonlan <HTPC-MAC>
```
Or via Kodi remote / Home Assistant automation.
## Kodi Layout
- Home screen: custom skin (record actual skin name here)
- Sources: NFS mounts from TrueNAS (`/mnt/RED_2TB/...`)
- Add-ons: check Kodi installed add-ons list
Note: specific layout details were not migrated. Update this page
when next doing HTPC maintenance.
+72
View File
@@ -92,6 +92,78 @@ TV шлёт координаты в пространстве 0–1920 × 0–108
- **Бинарь слетает** после `rpm-ostree upgrade` → пересобрать и скопировать. - **Бинарь слетает** после `rpm-ostree upgrade` → пересобрать и скопировать.
- **Display switching** — при смене между `:0` и `:1` xdotool перезапускается. Если переключение происходит часто (мигающие окна) — возможны краткие потери мыши. - **Display switching** — при смене между `:0` и `:1` xdotool перезапускается. Если переключение происходит часто (мигающие окна) — возможны краткие потери мыши.
## WebOS TV App — сборка и деплой
TV-часть — React/Enact WebOS приложение + Node.js сервис, упакованные в IPK. Исходники: `~/Developer/magic4pc/webos/`.
### Сборка
```bash
cd ~/Developer/magic4pc/webos
NODE_OPTIONS=--openssl-legacy-provider npm run build
```
**Питфолл:** `--openssl-legacy-provider` обязателен — старый webpack несовместим с Node.js 25+.
### Упаковка IPK
```bash
/Users/admin/webOS_TV_SDK/CLI/bin/ares-package dist/ service/ --outdir .
```
**Питфолл:** нужны оба аргумента `dist/` и `service/` — без `service/` Node.js сервис не попадает в IPK.
**Питфолл:** `npm run package` = `ares-package -n` (unsigned) — TV отклоняет неподписанные пакеты. Использовать SDK-команду напрямую.
### Деплой (deploy.sh)
```bash
cd ~/Developer/magic4pc/webos && ./deploy.sh
```
Скрипт: build → package → scp → close → remove → install → launch.
Для install (subscribe mode) — прямой `ssh+script` с polling на `"state":"installed"`.
**Питфолл:** `ares-install` / `ares-launch` ненадёжны — не ждут завершения. Использовать `luna-send dev/install` через script-враппер.
Версия в UI отображается как `1.1.0 (YYYY-MM-DD HH:MM)` — инжектируется webpack через `process.env.BUILD_DATE`.
## Auto-launch при включении / пробуждении TV
Magic4pc запускает настроенное приложение при включении или пробуждении TV.
| Файл | Расположение | Назначение |
|------|-------------|-----------|
| `magic4pc-settings` | PERSISTENT_DIR | ID выбранного приложения |
| `magic4pc-last-app` | PERSISTENT_DIR | Последнее активное приложение |
| `magic4pc-run-state` | `/tmp/` | `running` после первого запуска |
`PERSISTENT_DIR = /media/developer/apps/usr/palm/services/me.wouterdek.magic4pc.service`
Логика:
- **Boot/wake:** `/tmp` очищается → нет `run-state``freshStart=true` → запускает настроенное приложение
- **Ручной запуск:** `run-state=running` уже есть → auto-launch пропускается
`init.d` скрипт на TV (`/var/lib/webosbrew/init.d/magic4pc`) удаляет `run-state` при suspend — wake снова тригерит fresh launch. Редактировать только в репо (`tv-scripts/init.d-magic4pc.sh`), не напрямую на TV.
## WebOS Back Key + системная клавиатура
Когда системная клавиатура открыта, **первый Back** поглощается OS (закрывает клавиатуру) — `keydown` и `onButtonDown` **не стреляют**. Второй Back приходит нормально.
Фикс в `MainPanel.js` (флаг `_kbWasOpen`):
```js
// Input.onActivate:
this._kbWasOpen = true;
// onButtonDown на Back:
if (this.state.wolMacActive || this._kbWasOpen) {
this._kbWasOpen = false;
return; // подавить закрытие панели
}
```
Подходы, которые не работают: `Popup.noAutoDismiss`, timeout-эвристика, polling `document.activeElement` (клавиатура — системный оверлей, фокус не переходит на INPUT).
## Деплой и sudoers ## Деплой и sudoers
На HTPC настроен sudo без пароля для systemctl: На HTPC настроен sudo без пароля для systemctl:
+14
View File
@@ -229,6 +229,20 @@ Imported 185 torrents from Windows Transmission-daemon into HTPC Transmission.
10 torrents had no data (X:\Movies\ — lost disk, permanently gone). 10 torrents had no data (X:\Movies\ — lost disk, permanently gone).
## Sudo / Пароль
Пароль пользователя `bazzite`: **`bazzite`**
Для удалённых команд через SSH:
```bash
ssh bazzite@192.168.1.86 'echo bazzite | sudo -S mount ...'
```
Также настроен sudo без пароля для systemctl (см. [[htpc-magic4pc#Деплой и sudoers]]):
```
bazzite ALL=(ALL) NOPASSWD: /usr/bin/systemctl
```
## OSTree / Обновления ## OSTree / Обновления
- `/usr/local/bin` слетает при `rpm-ostree` upgrade (новый `/usr` overlay) - `/usr/local/bin` слетает при `rpm-ostree` upgrade (новый `/usr` overlay)
- Бинари держать в `/home/bazzite/` или пересобирать после обновлений - Бинари держать в `/home/bazzite/` или пересобирать после обновлений
+33
View File
@@ -0,0 +1,33 @@
---
title: Jellyfin Config
created: '2026-05-23'
updated: '2026-05-23'
type: tech
namespace: family
tags: [jellyfin, infra, media, credentials]
related:
- "[[tech/arr-stack-kraken]]"
---
# Jellyfin Config
**URL:** `http://kraken:8096`
## Users
| User | Password | Notes |
|------|----------|-------|
| alex | (set) | Admin |
| lisa | (none) | Cartoons library only |
## API Key
`87af49b6ff62ea68da6abdab0d7a4fbc`
Used by automation scripts (watchlist-sync, media-pipeline agents).
## Database / Maintenance
- **Before any DB operation:** `docker stop jellyfin` first
- Jellyfin runs as a Docker container on Kraken
- Config stored in `/opt/jellyfin/config/` (check docker-compose for exact path)
+108
View File
@@ -0,0 +1,108 @@
---
title: Jellyfin Транскод на RPi5 — PGS/Subtitle Pitfalls
type: reference
namespace: work
tags:
- jellyfin
- kraken
- transcode
- subtitles
- pitfalls
created: '2026-05-18'
updated: '2026-05-18'
---
# Jellyfin Транскод на RPi5 — PGS/Subtitle Pitfalls
## Контекст
Клиент: Konka TV с WebOS. Сервер: Raspberry Pi 5 (Крaken, ARM64).
## Direct Play условия
Direct Play работает при выполнении всех условий:
- Аудио: AAC
- Субтитры: **отсутствуют** (или внешние SRT, без burn-in)
## Проблема PGS/ASS субтитров
PGS и ASS (SSA) субтитры вызывают **burn-in** — Jellyfin рендерит субтитры прямо в видеопоток.
**Результат:** полный транскод на CPU → **275% CPU load** на RPi5 → лаги, перегрев.
**Экспериментальный PGS rendering** в настройках Jellyfin — **не помогает** на RPi5.
## Решение: конвертация PGS → SRT
Установка через uv (arm64/aarch64 совместимо):
```bash
~/.local/bin/uv tool install pgsrip
sudo apt-get install -y libgl1 libglib2.0-0 tesseract-ocr tesseract-ocr-rus tesseract-ocr-eng
```
Скрипт конвертации:
```
/srv/dev-disk-by-uuid.../docker/media-pipeline/pgs-to-srt.sh
```
```bash
# один файл
pgs-to-srt.sh /media/movies/Film.mkv
# вся директория
pgs-to-srt.sh /media
```
Логи: `/srv/.../docker/media-pipeline/pgs-to-srt.log`
Скрипт:
- Скипает файлы без PGS-дорожек (S_HDMV)
- Скипает если .srt уже есть рядом
- Пишет лог с timestamp
После конвертации Jellyfin читает внешние `.srt` без транскода.
## Transmission completion hook
Хук `/config/on-download-complete.sh` (монтируется в контейнер `:ro`) после завершения загрузки:
1. Запускает media-pipeline sync
2. Запускает pgs-to-srt.sh для скачанного файла/директории
Лог: `/srv/.../docker/media-pipeline/download-complete.log`
## Итог по субтитрам
| Тип | Direct Play | Транскод |
|-----|------------|---------|
| Нет субтитров | ✅ | — |
| Внешний SRT | ✅ | — |
| ASS/SSA | ❌ | burn-in, 275% CPU |
| PGS (MKS/MKV) | ❌ | burn-in, 275% CPU |
## NFD/NFC filename encoding pitfall
**Проблема:** HTPC сохраняет имена файлов в NFD, Kraken — в NFC.
→ Jellyfin видит **две** папки: одна с `.avi` файлом (NFD), другая с `.nfo` (NFC).
→ NFO не применяется — Jellyfin берёт NFD-папку (с видео) и игнорирует NFC-папку (с NFO).
→ Метаданные пустые.
**Фикс A (быстрый):** скопировать `movie.nfo` + `poster.jpg` в NFD-папку:
```bash
# python3 — использовать bytes для путей
import os, shutil
nfc_path = "…/Movie.NFC/movie.nfo"
nfd_path = "…/Movie.NFD/movie.nfo"
shutil.copy(nfc_path.encode(), nfd_path.encode())
```
**Фикс B (радикальный):** переименовать всё в NFC (нормализовать).
**Диагностика:**
```bash
ls -la | cat -v # покажет NFD-escape символы как ^ escape sequences
python3 -c "import os; [print(repr(f)) for f in os.listdir('.')]"
```
## Связанные страницы
- [[concepts/kraken-media-stack]] — полный медиастек: *arr + Jellyfin + media-pipeline
- [[tech/kraken-network]] — инфра Кракена, HDD error recovery
+34
View File
@@ -0,0 +1,34 @@
---
title: Kraken Network & Infra
created: '2026-05-23'
updated: '2026-05-23'
type: tech
namespace: personal
tags: [infra, kraken, ssh, wireguard, network]
related:
- "[[tech/arr-stack-kraken]]"
---
# Kraken Network & Infra
## SSH Access
```
ssh kraken
```
IP: `192.168.1.15` (wlan0, primary). SSH alias `kraken` resolves via `~/.ssh/config`.
## WireGuard Topology
Split-tunnel: Eagle ↔ VPS ↔ Kraken. Full details: [[tech/wireguard-vpn]].
- Eagle: `10.99.0.2`, Kraken: `10.99.1.2`, VPS relay: `10.99.0.1`/`10.99.1.1`
- `wg-auto.sh` on Eagle (LaunchDaemon) — up when off home Wi-Fi, down at home
- VPS as relay; two interfaces (wg0/wg1) avoid hairpin forwarding
## Media Volume Mount Paths
Docker containers on Kraken mount media from NAS over NFS/SMB.
Paths were documented here — check docker-compose files in
`/opt/media-toolbox-kraken` for current mount config.
+30
View File
@@ -0,0 +1,30 @@
---
title: TrueNAS Inpxer / Books Setup
created: '2026-05-23'
updated: '2026-05-23'
type: tech
namespace: family
tags: [truenas, books, infra, storage]
related:
- "[[tech/kraken-network]]"
---
# TrueNAS Inpxer / Books Setup
## Books Storage Path
```
/mnt/RED_2TB/storage/books/
```
Books are stored on the RED_2TB pool. Inpxer (or similar indexer) serves
the library from this location.
## Related TrueNAS Notes
- General TrueNAS access: [[family/how-to/truenas-access]]
- Remote access reverse proxy: [[personal/docs/truenas-remote-access-reverse-proxy]]
- Rclone backup: [[family/how-to/truenas-rclone-backup]]
Note: Inpxer-specific config was not migrated — add details here when
revisiting this setup.
+14
View File
@@ -40,6 +40,20 @@
Переменная `TV_HOST` переопределяет таргет (default: `root@LGwebOSTV`, alias `root@tv`, IP: `root@192.168.1.75`). Переменная `TV_HOST` переопределяет таргет (default: `root@LGwebOSTV`, alias `root@tv`, IP: `root@192.168.1.75`).
DNS резолвится через роутер (192.168.1.1), см. [[router-bishkek-asus]]. DNS резолвится через роутер (192.168.1.1), см. [[router-bishkek-asus]].
## Common Luna URIs
| URI | Назначение |
|-----|-----------|
| `.../applicationmanager/running` | Список запущенных приложений |
| `.../applicationmanager/closeByAppId` | Закрыть приложение по ID |
| `.../appInstallService/dev/install` | Установить IPK (subscribe) |
| `.../appInstallService/dev/remove` | Удалить приложение |
| `.../applicationManager/launch` | Запустить приложение |
| `.../applicationManager/getForegroundAppInfo` | Текущее активное приложение |
| `...magic4pc.service/query` | Статус сервиса magic4pc |
Полные префиксы: `com.webos.service.applicationmanager`, `com.webos.appInstallService`, `com.webos.applicationManager`.
## Ограничение ## Ограничение
`luna-send -i` (subscribe, бесконечный) не работает через враппер. Для install (`dev/install`) используется прямой ssh+script с polling в deploy.sh. `luna-send -i` (subscribe, бесконечный) не работает через враппер. Для install (`dev/install`) используется прямой ssh+script с polling в deploy.sh.
+94
View File
@@ -0,0 +1,94 @@
---
title: Vault Git Sync — Setup & Pitfalls
created: '2026-05-23'
updated: '2026-05-23'
type: tech
namespace: wiki
tags: [vault, git, sync, infra, obsidian]
---
# Vault Git Sync
Obsidian vault is a bare git repo on TrueNAS (`mallexxx.duckdns.org:/mnt/RED_2TB/storage/git/obsidian-vault.git`).
Three hosts sync to it: Eagle, Kraken, Taiga.
## Sync Strategy (correct)
All hosts must follow this order:
```
1. stash local changes (git stash)
2. pull from remote (git fetch + git merge)
3. pop stash (git stash pop)
4. commit if anything new (git add -A + git commit)
5. push (git push)
```
**Why this order matters:** `git add -A` before pull stages deletions of files
that exist on remote but not locally. This caused Taiga to delete 32+ wiki files
on 2026-05-23 (two incidents: 3a44a55, c9020c4).
## Scripts
| Host | Script | Status |
|--------|------------------------------------|----------------|
| Eagle | `~/scripts/sync-vault.sh` | ✅ Correct |
| Kraken | `~/scripts/sync-vault-partial.sh` | ✅ Correct |
| Taiga | `/opt/data/sync-vault.sh` | ✅ Fixed 2026-05-23 |
All scripts now use scoped `git add personal/ family/ .obsidian/` (not `git add -A`) before stash.
Taiga additionally uses sparse checkout (only `personal/` and `family/`) — double safeguard:
even if a bug reintroduces `git add -A`, sparse checkout means wiki/ is never checked out locally.
## Taiga Architecture
Taiga is TrueNAS running Hermes in Docker (`/mnt/RED_2TB/docker/hermes/`).
The sync script runs **inside the container** with:
- `GIT_WORK_TREE=/vault` → mounted from `/mnt/RED_2TB/storage/obsidian`
- `GIT_DIR=/vault.git` → mounted from `/mnt/RED_2TB/storage/git/obsidian-vault.git` (the bare repo itself)
Taiga IS the repo — no remote push needed. Eagle pushes to `nas/main`, which is this same bare repo.
Script location: `/mnt/RED_2TB/docker/hermes/config/sync-vault.sh` (= `/opt/data/sync-vault.sh` inside container)
SSH access: `ssh taiga` (alias in ~/.ssh/config → `truenas_admin@mallexxx.duckdns.org`)
To deploy a script fix:
```bash
scp ~/scripts/sync-vault-taiga.sh taiga:/mnt/RED_2TB/docker/hermes/config/sync-vault.sh
```
**NEVER use local IP for TrueNAS** — always `mallexxx.duckdns.org` or `ssh taiga`.
## .gitignore
Plugin binaries are excluded to prevent cross-device obsidian-git version conflicts:
```
.obsidian/plugins/obsidian-git/main.js
.obsidian/plugins/obsidian-git/styles.css
.obsidian/plugins/obsidian-git/manifest.json
```
Each device manages its own plugin binaries via Obsidian's built-in update mechanism.
`data.json` (plugin config) IS tracked — shared settings across devices.
## Incident: 2026-05-23
**Root cause:** Taiga's `/opt/data/sync-vault.sh` did `git add -A` before `git pull`.
**Sequence:**
- 22:02 UTC Eagle created 15 wiki files (d757029, 6dc4b89)
- 00:00 UTC Taiga ran sync → staged deletions (local didn't have wiki/) → commit 3a44a55
- 03:02 UTC Taiga ran again → deleted 8 more files (c9020c4)
**Recovery:** `git checkout <hash> -- <file>` for each file from last-good commits.
32 files restored in commit 620e2df.
## Stash Cleanup
Orphaned stashes from obsidian-git mobile syncs accumulate. Safe to drop:
```bash
git stash drop stash@{N} # drop specific, or:
git stash clear # drop all (only if no unrecovered work)
```
+74
View File
@@ -0,0 +1,74 @@
---
title: VPS qentra.top
created: '2026-05-24'
updated: '2026-05-27'
type: tech
namespace: personal
tags: [infra, vps]
confidence: medium
related:
- "[[tech/kraken-network]]"
- "[[tech/wireguard-vpn]]"
---
# VPS qentra.top
**IP:** 91.207.28.205
**Stack:** nginx + Python 3.11, Cloudflare proxy
**Panels:** panel.qentra.top:5430, v.qentra.top:8964
## Subdomain Setup
1. Add Cloudflare A-record → 91.207.28.205
2. Add nginx vhost in `/etc/nginx/sites-enabled/`
## Backup
Ежедневный бэкап конфигов через cron (04:00):
- Скрипт: `/root/vps-backup.sh`
- Cron: `/etc/cron.d/vps-backup`
- Куда: `/root/backups/` (`vps-config-YYYY-MM-DD_HHMMSS.tar.gz`)
- Ретеншн: 90 дней
**Что бэкапится:**
| Файл | Описание |
|------|----------|
| `xray/config.json` | Xray конфиг (все inbound/outbound, клиенты) |
| `x-ui/config.json` | 3X-UI панель (полная копия клиентов) |
| `systemd/xray.service` | systemd unit Xray |
| `cron/xray-update` | Еженедельное обновление Xray и 3X-UI |
| `nginx/sites-enabled/nolvu` | nginx vhost → nolvu.qentra.top |
| `nginx/sites-available/panel` | nginx vhost → panel.qentra.top |
| `nginx/sites-available/xray` | nginx vhost → v.qentra.top (WS прокси) |
| `nginx/nginx.conf` | Основной конфиг nginx |
| `scripts/add-client.sh` | OpenVPN скрипты |
| `scripts/revoke-client.sh` | OpenVPN скрипты |
| `scripts/setup-openvpn.sh` | OpenVPN скрипты |
**Восстановление:**
```bash
tar xzf /root/backups/vps-config-latest.tar.gz -C /
# После восстановления — перезапустить сервисы:
systemctl daemon-reload
systemctl restart xray nginx
```
## Xray (VLESS+REALITY)
- **Inbound 443** — REALITY, 4 клиента
- `2D9F24C4-21FE-4784-9843-F11C384DA67A``user1` (телефон)
- `f80b579b-baa6-4887-a76e-682635335c15``natali`
- `62814419-722a-4f45-b560-eaed2d0df6af``alexander_martemyanov`
- `23aa4d8e-ef16-43ab-bdef-3d2c11d8ef02``taiga` (TrueNAS vless-proxy)
- **Inbound 8964** — WebSocket, через nginx v.qentra.top/qentra
- `2D9F24C4-21FE-4784-9843-F11C384DA67A``124hyews`
- `d10834e6-ba84-4dbd-b5e6-b5f4df9e64d6``natali@duck.com`
- PrivateKey: `CMu-Sz49V5HW-s2c0P33EDlCwY-JMhQbxfdn7LhvY1A`
- Публичный ключ (для клиентов): `Rtkptj9Cij2go_oE0Klgf_Mwfhq-d_oaW4mQubHzXm0`
**Важно:** REALITY на 443, nginx на 8443 + 80. При перезапуске — сначала Xray (забирает 443), потом nginx.
## See Also
- [[tech/kraken-network]] — Kraken home network topology
- [[tech/wireguard-vpn]] — WireGuard tunnel used for Eagle↔VPS traffic
+5
View File
@@ -94,3 +94,8 @@ ssh kraken "sudo wg show"
# Логи auto-connect Eagle # Логи auto-connect Eagle
cat /var/log/wg-auto.log cat /var/log/wg-auto.log
``` ```
## Связанные заметки
- [[kraken-network]] — SSH к Кракену, пути медиаволюмов
- [[personal-os-architecture]] — Eagle hardware, домашняя инфра
+150
View File
@@ -0,0 +1,150 @@
---
created: '2026-05-20'
updated: '2026-05-27'
status: active
tags:
- kraken
- watchlist
- automation
- movies
---
# Watchlist Automation — Полный Flow
Полная цепочка: как `family/documents/movies-watchlist.md` превращается в скачанный контент.
## Полная цепочка
```
[вс 09:00] watchlist-discover ← KP API топ фильмов 2024-2025
↓ добавляет в секцию "Новинки 👍/👎"
movies-watchlist.md
Alex ставит 👍 / 👎
[ежедн 09:00] watchlist-resolve ← SSH Кракен → resolve → commit
[ежедн 10:00] watchlist-sync-down
→ process-thumbs (перемещает 👍 → начало списка)
→ sync-down (добавляет батч в Radarr/Sonarr)
Radarr (7878) / Sonarr (8989)
→ Prowlarr (9696) → RuTracker / Kinozal(M) / NoNaMe / etc.
→ Transmission (9091)
→ /media/movies-radarr или /media/series-sonarr
router.py (Custom Script) → читает жанры из NFO/TMDb
→ symlink в /media/{movies,cartoons,documentaries,...}/
Jellyfin (8096) → rescan → виден контент
```
## Статусы в movies-watchlist.md
| Символ | Значение | Действие |
|--------|---------|---------|
| `[ ]` | не смотрел, в очереди | sync-down добавляет в Radarr/Sonarr |
| `[x]` | просмотрено | sync-up из Jellyfin watched history |
| `⬇️` | добавлено в Radarr/Sonarr | ждёт скачки |
| `👍` | нравится (новинка) | process-thumbs переносит в начало → попадёт в sync-down |
| `👎` | не хочу (новинка) | process-thumbs перемещает в `## Новинки👎` |
| `🗓️` | upcoming (ещё не вышел) | discover помечает, не скачивается |
| `❓` | не найдено / ambiguous | ждёт ручного уточнения |
| `🧟‍♀️` | horror (genre_id=27) | информационный маркер |
## Структура секций movies-watchlist.md
```
## Новинки - 👍/👎 ← discover добавляет сюда
## Фильмы ← основной список, отступ 2 пробела ( - [ ])
## Мультфильмы
## Аниме
## Новинки👎 ← без пробела перед 👎
## Связанные заметки
```
**Важно:** `process_thumbs` автоматически определяет отступ из `## Фильмы` и сохраняет при переносе. `SECTION_THUMBDN = "## Новинки👎"` (без пробела) — должно точно совпадать с заголовком.
## Cron Jobs (на Кракене с 2026-05-27)
Весь watchlist pipeline работает на Кракене (мигрировал с Eagle 2026-05-27).
| Job | Расписание | Скрипт |
|-----|-----------|--------|
| `watchlist-nightly` | ежедн. 01:00 | `~/.hermes/scripts/watchlist-nightly.sh` |
| `watchlist-discover` | вс 09:00 | `~/.hermes/scripts/watchlist-discover.sh` |
**watchlist-nightly.sh** (на Кракене):
1. `sync-vault.sh` (pull) → `process-thumbs --apply``sync-vault.sh` (push)
2. `resolve --recheck --apply``sync-down --apply --batch 5``sync-up --apply``sync-vault.sh` (push)
**Пути на Кракене (HDD UUID 49e8f586-3839-4c5d-a1e1-58bfc3579ade):**
- `/home/kraken/obsidian` → symlink на `/srv/dev-disk-by-uuid-.../obsidian`
- watchlist-sync: `/srv/dev-disk-by-uuid-.../Developer/watchlist-sync`
## Discover — Фильтрация
| Тип | Порог |
|-----|-------|
| Мировые | IMDb ≥ 7.0 или KP ≥ 7.0 |
| Российские | KP ≥ 8.5 (защита от накрутки) |
| Upcoming | год ≥ текущий−1 |
| Минимум голосов | IMDb ≥ 1000 или KP ≥ 500 |
KP API endpoint: `https://api.poiskkino.dev/v1.4/movie` (redirect с api.kinopoisk.dev).
## *arr Stack — Индексеры (Prowlarr)
| ID | Имя | Тип | Примечание |
|----|-----|-----|-----------|
| 1 | RuTracker | semi-private | блокирует при rate limit на сутки |
| 2 | RuTor | public | поиск по imdbid |
| 3 | Byrutor | public | |
| 4 | NoNaMe Club | semi-private | анонимный |
| 5 | Kinozal | semi-private | резерв |
| 6 | Kinozal (M) | semi-private | магнет ✅ — без grab limit, **предпочтительный** |
**Pi-hole питфолл:** блокирует `skyhook.sonarr.tv` → Sonarr/Radarr поиск молча не запускается. Фикс: `dns: [8.8.8.8]` в docker-compose.yml.
## Router Script — Маршрутизация по жанру
После импорта Sonarr/Radarr: `router.py --apply` создаёт symlinks:
- Animation (не R/18+) → `/media/cartoons/` или `/media/cartoons-series/`
- Documentary → `/media/documentaries/` или `/media/documentaries-series/`
- Прочее → `/media/movies/` или `/media/series/`
Копирует NFO + постер рядом с symlink (Jellyfin не видит метадату через symlink напрямую).
## watchlist-sync — Python CLI
Репо: `~/Developer/watchlist-sync` (GitHub: `mallexxx/watchlist-sync`).
```
watchlist-sync resolve — TMDB + KP API → ID, тип (movie/tv)
watchlist-sync sync-down — добавить батч (max 5) в Radarr/Sonarr, поставить ⬇️
watchlist-sync sync-up — Jellyfin watched → [x] в .md
```
**Резолвер:** TMDB `/search/multi` + KP fallback. Пороги: `min_rating: 6.0`, `min_score: 0.75`. При 2+ кандидатах → `❓` (LLM разрешает в cron каждые 6ч, нерешённые → уточнение в Zulip).
**Stage 2 (после ≥50 просмотров):** AI-curated recommendations — три списка (с женой / один / семья) на основе TMDB профиля, добавляются в конец watchlist как `## 🤖 Рекомендации (YYYY-WNN)`.
Jellyfin library mapping: Movies → `movies`, Cartoons → `mixed` (movies + episodes).
## Питфоллы
### KP Markdown Link Parsing Bug
Фильмы из Kinopoisk с синтаксисом `[Title](https://kinopoisk.ru/film/12345/) (2025)` передавались как есть в TMDB search → не находились → помечались `❓` навсегда (`not i.ambiguous` пропускает при следующем запуске). Фикс: commit `2db1ed9` — извлекать plain title из markdown link до передачи в TMDB. Ошибочно помеченные `❓` требуют ручного `--recheck`.
### KP API Endpoint
`api.kinopoisk.dev` редиректит на `api.poiskkino.dev/v1.4/movie`. Использовать destination redirect напрямую.
## Open Tasks (2026-05-20)
- 🟡 Передавать Transmission locations через router script при каждом импорте (сейчас вручную)
- 🔴 rsync невыполненных фильмов с HTPC на Кракен (`Фильмы не перенесенные с htpc на кракен.md`)
- 🟡 Watchlist sync-up: добавить перенесённые фильмы в нужные разделы
## Связанные заметки
- [[watchlist-discover]] — шаг discover: KP API, format, CLI команды
- [[watchlist-sync]] — основной проект (resolve, sync-down, sync-up)
- [[arr-stack-kraken]] — детальные питфоллы *arr стека
- [[jellyfin-transcode-rpi5]] — Jellyfin PGS/ASS питфоллы
+3 -3
View File
@@ -76,12 +76,12 @@ watchlist-sync process-thumbs --apply
--- ---
## Cron jobs (Hermes) ## Cron jobs (Hermes, на Кракене с 2026-05-27)
| Job | Schedule | Действие | | Job | Schedule | Действие |
|-----|----------|----------| |-----|----------|----------|
| `watchlist-discover` (`6aba6979b8c4`) | вс 09:00 | discover --apply + git commit | | `watchlist-discover` | вс 09:00 | discover --apply + git commit (Кракен) |
| `watchlist-sync-down` (`7d12b6d48786`) | ежедн. 10:00 | process-thumbs --apply → sync-down --apply | | `watchlist-nightly` | ежедн. 01:00 | process-thumbs → resolve → sync-down → sync-up (Кракен) |
--- ---
+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
+152
View File
@@ -0,0 +1,152 @@
---
namespace: personal
created: '2026-05-15'
updated: '2026-05-16'
last_synced: '2026-05-16'
tags:
- profile
- user
- alex
confidence: 0.9
---
# User Profile — Alex
> Этот файл читают enrichment- и monitoring-агенты как источник истины о пользователе.
> Обновлять при любых изменениях в жизни. Дата последнего обновления: 2026-05-15.
---
## Идентификация
- **Имя:** Александр (Alex)
- **ДР:** 11.08.1987, Лев
- **GitHub:** mallexxx
- **ADHD** — диагностирован. Гиперфокус, трудности с переключением, поздний старт (~11:00), уходит в работу до 20:00+
---
## Локации
| Место | Статус |
|-------|--------|
| Бишкек | Основное проживание. Коворкинг для работы. WireGuard домой → Kraken. |
| Новосибирск (Тайга) | Дом. Летний отпуск + зимовка (ноябрь–март). |
| Алтай | Дача, строится. |
| Дубай | Жил 2 года. Разрабатывал Quran Memorization Program для iPad. |
---
## Семья
- **Жена:** Наталия
- **Дочь:** Лиза, 7 лет — [[father-profile]] для деталей
- Живут вместе в Бишкеке
---
## Работа
- **Должность:** macOS Browser Developer / DRI
- **Компания:** DuckDuckGo (async-first, Asana + Zulip)
- Приносит доход, но устал. Хочет в инди.
- **Направление:** serial indie startuper — найти большое решение, реализовать одну небольшую фичу хорошо. Пока сложно с идеями.
- **Активный проект:** AI-психолог / рефлексия-компаньон (Reflect) — приложение для психологической рефлексии без sycophancy. Anti-sycophancy via dual-bot архитектура (Narrator + Analyst). Стек: Skip.tools (SwiftUI → Android), Claude Sonnet/Haiku backend, OpenAI Realtime API для голоса. Планируется в App Store. Юрисдикция — Казахстан ИП или Эстония OÜ.
- **Идеи приложений:** [[app-ideas]]
---
## Навыки
### Разработка
- Swift / macOS / iOS — основной стек (DDG browser, AXPressDeck, Quran app)
- JavaScript / Node.js
- Docker, WireGuard, Home Assistant, Zigbee
- Arduino, прошивки микроконтроллеров
- AI agents, MCP, LLM orchestration (personal-os)
### Руками
- Ремонт авто + компьютерная диагностика
- Стройка (два дома построил, вник в процесс полностью)
- Сборка мебели, ремонты
- Паяльник, электроника, электрика
---
## Текущий жизненный контекст
- Недавно бросил пить (май 2026) — мотивация: неприятные инциденты + ультиматум жены + разговор с другом Серёгой → личное решение
- **Регуляторный стек вместо алкоголя:** зал, сауна, массаж, баня+квас, б/а IPA, чтение вечером, прогулки пешком 20 мин до коворкинга
- Ходит в спортзал (рядом с коворкингом, есть сауна)
- Коворкинг в Бишкеке — структура для ADHD, разделяет работу и семью
- Работает над personal-os как системой для себя
---
## Интересы
- Home automation (Home Assistant, Zigbee, RPi)
- 3D-печать — [[3d-print-wishlist]]
- **Видеоигры:** игровой ПК собран как TV-приставка (лончер, джойстики, эмуляторы настроены). Играет редко — паттерн накопления без использования. Прогресс: прошёл с Лизой уровень Shovel Knight, запустил Witcher 3 (вводная сцена).
- Настолки: Руммикуб, Шакал, Ticket to Ride, Hive, Rush Hour, Диксит
- Совместные игры с Лизой — [[games-wishlist]]
- Геокэшинг
- Хайкинг (Алтай и окрестности)
- **Готовка:** рецепты собирает, готовит редко в одиночку. В компании / на выезде — шашлыки, куурдак, плов в казане (НГ в Сибири). Паттерн: нужен социальный контейнер.
- Кино — [[movies-watchlist]]
- Чтение — [[books-reading-list]] (широкий диапазон: философия, история, sci-fi, психология); читает вечером регулярно
- Электроника / DIY
---
## Музыка
- Закончил музыкальную школу по фортепиано
- Ходил на курсы гитары дважды (последний раз ~пол года, потом стоп); сейчас играет на 3 аккордах
- Гитара: **LAVA ME** — лежит, не используется
- Музыкальный гаджет: **Teenage Engineering Pocket Operator** — побаловался, лежит
- Хорошо поёт (каraoke — [[karaoke-songs]])
- Сделал несколько треков сам (нужно курировать и доделывать)
- Экспериментирует с **Bulka** (AI layer over Strudel — music programming)
- Сохранены курсы (Instagram): продюсирование + импровизация на фортепиано — не начаты
- Паттерн: покупает/сохраняет → не использует без внешней структуры (→ [[psychology/profile]])
---
## Планы поездок 2026
| Период | Направление | Статус |
|--------|-------------|--------|
| Скоро | Алматы — Cirque du Soleil | Планируется |
| 20.07–14.11 | Новосибирск + трип на машине в Братск (к родителям Наталии) | Планируется |
| Ноябрь–март | Новосибирск (зимовка + НГ) → обратно в Бишкек | Регулярно |
| Желаемое | Турция / острова — куда вписать непонятно | Wishlist |
---
## Психологическое направление
- С психологом не работает (сессии были, но не регулярно)
- Хочет добавить в personal-os: регулярные дайджесты, проработки, анализ прогресса
- AI-психолог как постоянный компонент системы
- Идея непредвзятого агента: обсуждать Алекса "о нём" (в 3м лице), а не "обо мне" — чтобы избежать сycophancy → [[unprejudiced-ai-psychologist]]
- Искать скиллы AI-психолога для ассистентов
---
## Финансы
- Вёл детальную Excel-таблицу с мультивалютными счетами и расходами
- Последнее время не ведёт — "денег хватает"
- Открытые вопросы: инвестиционная стратегия, дополнительный доход, пенсионное планирование
- Есть идея budget-app в [[app-ideas]] и [[budget-app-features]]
---
## Неизвестно / уточнить
- [ ] ДР Наталии
- [ ] ДР Лизы (точная дата)
- [ ] Митя (одноклассник) — ДР "14.04 или около того", уточнить
- [ ] Лена Москаленко — ДР "12 сентября что ли", уточнить (попробовать через ВКонтакте)
- [ ] Школа Изобретателей — что за программа, чему учат
- [ ] Музыкальный плейлист Лизы — добавить в её профиль