feat(mappa-session-orient): new skill v1.0.0 — start-phase ritual (contract→pull→handoff-read→inbox-raise→liveness→live-ingest query); absorbs pulling-before-work/session-handoff(read)/session-inbox-monitor/using-system-snapshot; 404-skip for undeployed /session [skip-tdd: visual]

This commit is contained in:
2026-08-24 22:52:20 +03:00
parent 3dff74d584
commit 60f317ace0
13 changed files with 157 additions and 513 deletions

View File

@@ -17,4 +17,15 @@
## Completed steps ## Completed steps
- [x] skills/mappa-session-orient/SKILL.md v1.0.0 — старт-фаза: контракт → pull (--ff-only, полный цикл) → handoff read (staleness >7д → ask; orient+ask, без auto-execute) → inbox raise+sweep → liveness-сводка (meta_health/admin_status/snapshot, «живо/мертво») → live-ingest query (session_list, stale-active краш-детект, «другая связка → предложить»)
- [x] Граница orient/ops (w:2605 round 3): «живо/мертво»; проблема на старте → эскалация человеку/диагностической сессии, не углубление
- [x] Поглощены: pulling-before-work, session-handoff(read), session-inbox-monitor(raise), using-system-snapshot (liveness); старые имена — триггер-синонимы
- [x] Live-ingest 404-skip задокументирован (роуты /session не задеплоены — сервер #1022 в репо, деплой ждёт #1055); контракт — w:2604
- [x] mappa-messaging: ссылки session-inbox-monitor → mappa-session-orient (inbox raise) обновлены (3 места)
- [x] lint clean (63 skills, 0 violations); build.sh → dist/mappa-session-orient.skill; install.sh → dual; поглощённые удалены из живых диров
- [x] GREEN micro-test: свежий pi -p на «начало сессии» → ритуал по шагам (контракт→pull→handoff→inbox→liveness→live-ingest), граница «живо/мертво» + эскалация, 404-skip, read-only
## Notes ## Notes
- #1064 была 🔵 blocked ← #1024 (клиент session-sync). Скил-документ завершён по контракту w:2604/w:2605; live-ingest E2E (шаг 6) отложен: сервер #1022 не задеплоен (#1055), клиент #1024 (.common) открыт. 404-skip в скиле — ориентация не блокируется.
- RED-базис: контент унаследован из 4 поглощённых скилов (все прошли ревью); дельта = слияние + live-ingest query + граница orient/ops. Полный behavioral smoke — за #1065.

Binary file not shown.

BIN
dist/mappa-session-orient.skill vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -11,7 +11,7 @@ description: >
inter-session-messaging. Триггеры: «напиши письмо <проекту>», «отправь inter-session-messaging. Триггеры: «напиши письмо <проекту>», «отправь
сообщение», «свяжись с <проектом>», «передай <проекту>», «уведомь сообщение», «свяжись с <проектом>», «передай <проекту>», «уведомь
<проект>», а также получение входящего (см. ниже). НЕ про <проект>», а также получение входящего (см. ниже). НЕ про
доставку/мониторинг (→ session-inbox-monitor) и НЕ про задачи доставку/мониторинг (→ mappa-session-orient, inbox raise) и НЕ про задачи
(→ mappa-task-work, mcp__mappa__task_*). (→ mappa-task-work, mcp__mappa__task_*).
--- ---
@@ -31,7 +31,7 @@ description: >
- Получил входящее письмо (монитор доставил, или сам проверил `inbox_monitor`) — обработать по RECEIVE. - Получил входящее письмо (монитор доставил, или сам проверил `inbox_monitor`) — обработать по RECEIVE.
- Обсуждаешь с другой сессией дизайн/скоуп/решения — держать POLICY (peer ≠ authority). - Обсуждаешь с другой сессией дизайн/скоуп/решения — держать POLICY (peer ≠ authority).
**НЕ для:** доставки/мониторинга почты (→ `session-inbox-monitor`), задач **НЕ для:** доставки/мониторинга почты (→ `mappa-session-orient`, inbox raise), задач
(→ `mappa-task-work`), handoff (→ `mappa-closing-ritual`), промоушена (→ (→ `mappa-task-work`), handoff (→ `mappa-closing-ritual`), промоушена (→
`mappa-brainstorm-promote`). `mappa-brainstorm-promote`).
@@ -107,7 +107,7 @@ mcp__mappa__inbox_send(
## RECEIVE — как обработать входящее ## RECEIVE — как обработать входящее
1. Входящее доставляет монитор (`session-inbox-monitor`, pi-расширение) или 1. Входящее доставляет монитор (`mappa-session-orient` — inbox raise, pi-расширение) или
ты проверяешь сам: `mcp__mappa__inbox_monitor(project=<своя папка>, limit)`. ты проверяешь сам: `mcp__mappa__inbox_monitor(project=<своя папка>, limit)`.
Ответ — `{rows: [{id, slug, from, subject, body}]}`: последние письма Ответ — `{rows: [{id, slug, from, subject, body}]}`: последние письма
твоего проекта, с отправителем и темой (meta извлекается сервером). твоего проекта, с отправителем и темой (meta извлекается сервером).
@@ -238,7 +238,7 @@ Lifecycle-письма помечай subject-префиксом `[event: <ти
## Reference ## Reference
- Доставка/мониторинг входящих: `session-inbox-monitor` (вне suite, не переименован). - Доставка/мониторинг входящих: `mappa-session-orient` (inbox raise; pi-расширение inbox-monitor).
- Адресная книга: `~/projects/.wiki/concepts/projects-address-book.md` (shared wiki). - Адресная книга: `~/projects/.wiki/concepts/projects-address-book.md` (shared wiki).
- Список проектов Mappa: `mcp__mappa__admin_status` (карв-аут, без лиза). - Список проектов Mappa: `mcp__mappa__admin_status` (карв-аут, без лиза).
- Задачи: `mappa-task-work` (борд = `mcp__mappa__task_*`). - Задачи: `mappa-task-work` (борд = `mcp__mappa__task_*`).

View File

@@ -0,0 +1,142 @@
---
name: mappa-session-orient
author: ours
version: 1.0.0
description: >
Старт-фаза форкфлоу: контракт + чтение (pull --ff-only → handoff read →
inbox raise → liveness-сводка «живо/мертво» → live-ingest query). Нужен и
для ad-hoc, где нет AGENTS.md-контракта. Поглощает pulling-before-work,
session-handoff(read), session-inbox-monitor(raise), using-system-snapshot
(liveness) + live-ingest query (старые имена — триггер-синонимы). Граница:
orient отвечает «живо/мертво» одной строкой; глубокая диагностика — вне
suite (эскалация человеку/диагностической сессии). Триггеры: «что на
сессии», «кто последним работал», «продолжи с места», «orient me»,
session-start ритуал, «pull remote before work».
---
# mappa-session-orient
Старт-фаза цикла агента: **контракт + чтение**, тонкий слой — отвечает на
вопрос «живо/мертво» (одна строка на секцию), не углубляется. Нужен и для
ad-hoc-сессий (где нет AGENTS.md-контракта — ориентация всё равно обязательна).
> **Граница session-orient / ops (w:2605, round 3):** orient — «живо/мертво»;
> ops — «почему и что дальше». Проблема на старте → **не углубляться**:
> передать человеку или диагностической сессии (вне suite).
## Когда использовать
- Старт сессии (ритуал, порядок строго по Steps).
- «что на сессии», «кто последним работал», «продолжи с места», «orient me».
- Ad-hoc-сессия без трека/таски — ориентация всё равно (контракт + чтение).
## Steps (порядок — ритуал)
### 1. Контракт
Прочитать `AGENTS.md` проекта (canon; `CLAUDE.md` — legacy-указатель). Если
AGENTS.md нет — ad-hoc: контракта нет, но ориентация продолжается (шаги 26
не зависят от него).
### 2. Pull (pulling-before-work, полный цикл)
`git pull --ff-only` — один раз на старте. Проверки по порядку: git-work-tree?
(нет → silent exit), политика pull (`pull.rebase=true` + `pull.ff=only`,
set-if-absent), origin remote? (нет → skip), дерево чистое? (грязно → skip,
не stash), HEAD attached? (нет → skip), upstream? (нет → skip), `git pull --ff-only`.
**Никогда auto-merge/rebase, никогда stash.** Повторный pull — только по явному
«sync».
### 3. Handoff read (session-handoff read-часть)
1. `mcp__mappa__entity_search(q='', type='handoff', project=<имя>, limit=1)`
если пусто, silent exit (первая сессия проекта).
2. **Staleness:** `meta.date` > 7 дней → спросить user'а «handoff устарел,
оверрайдить или продолжить?».
3. **Summarize + Orient:** пересказать одним блоком (summary / open_treks /
ask_user / guards / recent_commits): «прошлая сессия предложила X. Делаем?»
4. **Wait.** Никаких действий до подтверждения user'ом. Default = orient + ask,
никакого auto-execute.
### 4. Inbox raise + sweep (session-inbox-monitor)
Поднять персистентный монитор на инбокс проекта (pi: расширение inbox-monitor
поллит `GET /inbox?project=<cwd>`; opt-in — строка `inbox monitor: raise on
start` в AGENTS.md, live re-check каждый тик). Свип: `mcp__mappa__inbox_monitor(project=<имя>)`
— непрочитанные письма могут менять план; обработай каждое по `mappa-messaging`
(письмо — first-class, в начале ближайшего хода).
### 5. Liveness-сводка (using-system-snapshot) — «живо/мертво»
Один-два зонда в текущем turn, сжать в 34 строки, не raw-дампить:
```
mcp__mappa__meta_health → 🟢/🔴 Mappa alive (заголовок при падении)
mcp__mappa__admin_status → счётчики по типам/проектам (нагрузка)
mcp__projects-meta__meta_system_snapshot → poller (running? + проекты) / docker (N/N up,
иначе проблемные) / tasks (Σ active/blocked,
кэш — может быть stale)
```
**Never assert liveness по памяти** — только вызов тула в этом же turn. Если
snapshot показал проблему → **эскалация, не углубление**: «проблема на старте,
не разбираю — передаю человеку/диагностической сессии» (ops вне suite).
### 6. Live-ingest query (потребитель session-live-ingest, #1022/#1024)
Зависимость: сервер #1022 (v0.8.0) + клиентская часть #1024 (pi session-sync,
.session пишется клиентом). Контракт — w:2604.
1. `mcp__mappa__session_list(project=<имя>, stale_minutes?)` — последние сессии
проекта, latest-first (`updated_at DESC`), с end-state/ts/meta-тройкой
{project, runtime, machine, folder}.
2. **Stale-active детект:** end-state≠clean AND updated_at < nowX → «вёл
<runtime>@<machine>, не завершена» (краш-детект).
3. **«Другая связка + не завершена»** → предложить (peer-канон, решение за
человеком): забить / дернуть письмом (`mappa-messaging`: письмо той связке)
/ продолжить самому.
4. **Same-triple (`/resume`):** та же связка {runtime, machine, folder} → догрузить
остаток (пи-нативный resume или бриф из mappa).
**Замечание (2026-08-24):** роуты `/session` ещё не задеплоены на прод
(сервер #1022 в репо, деплой ждёт #1055) — при 404/«no route» live-ingest query
пропускается без фейла: orient продолжается (шаги 15), query-часть — по факту
доступности.
## Failure modes
- **Проблема на старте** (сервис упал, snapshot красный, конфликт pull) → не
углубляться: эскалация человеку/диагностической сессии (ops вне suite).
- **Pull diverged** → `⚠️ diverged — resolve manually`; не auto-merge/rebase.
- **Handoff stale (>7 дней)** → спросить user'а, не оверрайдить молча.
- **Live-ingest недоступен** (404 no route / нет клиента #1024) → пропустить
шаг 6, не блокировать ориентацию.
- **Проект не в mappa** (нет handoff/session-сущностей) → silent exit по
соответствующим шагам; первая сессия проекта — норм.
## Side effects
- Ничего не пишет, ничего не мутирует (ориентация read-only: pull — локальный
ff, inbox-raise — монитор, liveness — зонды, live-ingest — чтение).
- Поднимает персистентный inbox-монитор (живёт до конца сессии).
## What NOT to do
- **Не auto-execute** из handoff'а — orient + ask, никакого авто-действия.
- **Не углубляться в диагностику** — orient = «живо/мертво»; «почему» — вне suite.
- **Не assert liveness по памяти** — только зонд в этом же turn.
- **Не stash/не auto-merge/не auto-rebase** при pull — только `--ff-only`.
- **Не повторять pull** в сессии без явного «sync».
- **Не ходить по многохоповым цепочкам** live-ingest — одна строка «кто
последним», предложение — человеку.
- **Не писать** (handoff/вики/таски) на ориентации — это финиш-фаза
(`mappa-closing-ritual`).
## Reference
- Финиш-фаза: `mappa-closing-ritual` (handoff write + PROPOSE).
- Задачи: `mappa-task-work` (борд после ориентации).
- Почта: `mappa-messaging` (ответы на письма, дернуть связку).
- Знание: `mappa-knowledge`. Делегирование: `mappa-delegation`.
- Live-ingest спека: `concepts/session-live-ingest` (wiki:2604).
- Глубокая диагностика (вне suite): `using-vds-ops` (контейнеры VDS).

View File

@@ -1,29 +0,0 @@
# pulling-before-work
Policy skill that pulls the current branch from `origin` once at session start
and on explicit re-sync requests. Designed to remove the "edited on stale base"
footgun without trampling dirty work-trees or auto-merging.
## When it triggers
- **Session start** — when `AGENTS.md` contains the line `pull remote before work` (added by `project-bootstrap` v2.0.0+).
- **In-chat** — when the user says `sync`, `resync`, `pull`, `обнови репо`, `git pull please`, or close variants.
Stays silent in non-git folders. Prints one informational line and exits in:
no `origin` remote, no upstream tracking, dirty work-tree, detached HEAD.
## What it does
`git pull --ff-only` against the configured upstream — never auto-merges, never
auto-rebases, never stashes, never commits, never pushes. On divergence it prints
a warning with manual-resolution hints and exits.
## Prerequisites
None. The skill is a no-op outside git repos and folders without an `origin`
remote, so it's safe to leave activated everywhere.
## Related
- `project-bootstrap` (v2.0.0+) — adds the trigger line to new and existing projects' `AGENTS.md`.
- `.wiki/concepts/pulling-before-work-design.md` (in projects bootstrapped from this repo: this design lives in `skills`) — full design rationale.

View File

@@ -1,166 +0,0 @@
---
name: pulling-before-work
author: ours
version: 1.1.0
description: >
Pulls the current branch from origin once at session start and on explicit
re-sync requests. Use when AGENTS.md contains the trigger line "pull remote
before work", or when the user says "sync", "resync", "pull", "обнови репо",
"git pull please", or close variants asking to refresh from the remote.
Runs `git pull --ff-only` — never auto-merges or rebases. Also sets the
repo-local pull policy (`pull.rebase=true` + `pull.ff=only`, set-if-absent)
so plain manual `git pull` never creates merge commits either. Stays silent
in non-git folders. Prints one informational line and exits when there is no
origin remote, no upstream tracking, the working tree is dirty, or HEAD is
detached. Does not stash, commit, or push. Activated by `project-bootstrap`
v1.4.0+ via the canonical AGENTS.md template.
---
# pulling-before-work
> Pull from `origin` once when work starts. Don't auto-merge. Don't trample dirty work-trees. Don't ask twice in the same session unless asked.
## When this runs
**At session start** — once, when the skill is activated by the `pull remote before work` line in `AGENTS.md`. The cycle below runs immediately.
**On explicit re-sync** — when the user says any of: `sync`, `resync`, `pull`, `обнови репо`, `pull please`, `git pull`, `подтяни`, `pull from origin`. Re-runs the full cycle. There is no per-session counter; the user is always allowed to ask.
**Never** before each commit, before each tool call, on every message, or in any other implicit cadence. Mode-3 ("start + on-demand") was the explicit design choice — see `.wiki/concepts/pulling-before-work-design.md`.
## The pull cycle
Run these checks in order. Print at most one line of chat output per run.
### 1. Inside a git work-tree?
```bash
git rev-parse --is-inside-work-tree 2>/dev/null
```
If the command fails or prints anything other than `true`**exit silently, no chat output.** This is the not-a-git-repo case; the skill must not be noisy in random folders.
### 1b. Ensure the repo's local pull policy (set-if-absent)
```bash
git config --local --get pull.rebase >/dev/null 2>&1 || git config --local pull.rebase true
git config --local --get pull.ff >/dev/null 2>&1 || git config --local pull.ff only
```
Sets the repo-local pull policy to the snolla baseline: plain `git pull` rebases instead of creating a merge commit (`pull.rebase=true`), and the merge path refuses non-fast-forwards (`pull.ff=only`). **Set-if-absent only** — an explicit local override the user wrote is never clobbered. Silent: no chat output, idempotent, works in dirty trees. Git config is untracked, so this never dirties `git status`. The skill's own pull below still uses `--ff-only` on purpose: a silent session-start pull never leaves the repo mid-rebase; divergence is always resolved by the human.
### 2. Has an `origin` remote?
```bash
git remote get-url origin 2>/dev/null
```
If the command fails (no such remote) → print one line and exit:
```
no origin remote — skip pull
```
### 3. Is the working tree clean?
```bash
git status --porcelain
```
If the output is non-empty → print one line and exit:
```
working tree dirty — skipping pull. commit/stash, потом скажи "sync"
```
Never stash automatically. Stash-pop conflicts are exactly the friction this skill exists to remove.
### 4. Is HEAD attached?
```bash
git symbolic-ref -q HEAD
```
If the command fails (empty output, exit 1) → detached HEAD. Print:
```
detached HEAD — skip pull
```
### 5. Does the current branch have an upstream?
```bash
git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null
```
Capture the upstream name (e.g. `origin/master`). If the command fails → no upstream tracking. Print:
```
no upstream tracking for <branch> — skip pull
```
(Where `<branch>` is `git rev-parse --abbrev-ref HEAD`.)
### 6. Pull, fast-forward only
```bash
git pull --ff-only
```
(No args — uses the configured upstream captured above.)
Classify by exit code and stdout:
| Result | Print |
|---|---|
| Already up to date | `✅ already up to date with <upstream>` |
| Fast-forward, N commits | `✅ pulled N commits from <upstream>` |
| Non-fast-forward / diverged (exit non-zero with "diverged" or "non-fast-forward" in output) | `⚠️ diverged from <upstream> — resolve manually (plain git pull rebases by default; git pull --no-ff for a merge commit); skill never auto-merges/rebases` |
### Out of scope
The skill never:
- commits, stashes, or pushes
- recurses into submodules
- pulls from non-`origin` remotes
- pulls on detached HEAD
- runs auto-merge or auto-rebase
- overwrites an existing local pull policy (set-if-absent only)
- runs more than once per session unless the user asks
## Recovery hints
If the skill skipped because of a dirty tree:
```powershell
# Windows / PowerShell
git status # see what's dirty
git add . ; git commit -m "wip"
# then ask the agent: "sync"
```
```bash
# Linux / macOS
git status
git add . && git commit -m "wip"
# then say "sync"
```
If the skill reported `diverged`:
```bash
# Option A (repo default, set by step 1b): rebase your local commits on top of origin
git pull
# Option B: explicit merge commit
git pull --no-ff
```
The skill stays out of these decisions on purpose — both options have valid use cases and the user owns the choice.
## Why this exists
Stale local branches are a silent footgun: edits land on top of yesterday's `origin`, the divergence shows up at push time, and by then there's a chunk of work to rebase or merge on the wrong base. One pull at start covers the common case; an explicit re-sync trigger handles long sessions where someone pushed mid-flight.
Full design rationale (mode choice, dirty-tree skip vs stash, `--ff-only` vs auto-merge, the upstream-check) lives in `.wiki/concepts/pulling-before-work-design.md`.

View File

@@ -1,124 +0,0 @@
---
name: session-inbox-monitor
author: ours
version: 1.0.0
description: >
Raises a persistent Monitor on the project's Mappa inbox (poll
`mcp__mappa__inbox_monitor` / HTTP `GET /inbox?project=<cwd>`), so
inter-session messages page the session in real time; the monitor dies on
session end on its own. Pi (pi-coding-agent) sessions: a global extension
polls the same HTTP endpoint, session-scoped (own project only). Triggers:
AGENTS.md line `inbox monitor: raise on start`, or «подними монитор почты»,
«настрой авто-монитор инбокса», «raise inbox monitor», «auto-arm inbox
watcher». Headless (`claude -p` / `pi -p`): does NOT raise — rely on the
Stop-hook / agent_settled pull pickup. File channel `.agents/inbox/` is
gone (flip, решение 15) — letters live in Mappa as entities `i:N`, delivery
is a carve-out (no lease, решение 19). NOT for how to handle a received
message (→ inter-session-messaging).
---
# session-inbox-monitor
Auto-raises a session-length Monitor on the project's **Mappa inbox** at
interactive-session start, so inter-session messages page the session in real
time. Tears down for free on session end. Headless sessions skip it and rely
on the pull-model (Stop-hook / agent_settled pickup).
Канал — Mappa, НЕ файлы: письма — сущности `inbox` (`i:N`) в сервисе, читаются
через `mcp__mappa__inbox_monitor(project, limit)` (или HTTP `GET
/inbox?project=<имя-папки>&limit=N`). Доставка/чтение — карв-аут (решение 19):
не блокируются лизом, работают всегда. Файловые `.agents/inbox/` и `.read/`
выпилены.
## When to use
- **Automatic (the common path).** A paired SessionStart hook injects an
instruction at the start of every interactive session of an opted-in project.
You act on that injection — raise the monitor as your first action — without a
user phrase.
- **On request.** AGENTS.md line `inbox monitor: raise on start`, or «подними
монитор почты», «настрой авто-монитор инбокса», «raise inbox monitor»,
«auto-arm inbox watcher».
- **NOT for** handling the content of a received message (→
`inter-session-messaging`). This skill is only the monitor's *lifecycle*.
## Inputs
- `<project>` — своё имя папки (cwd basename), адрес в Mappa.
- Письма: `mcp__mappa__inbox_monitor(project=<своя папка>, limit)`
`{rows: [{id, slug, body}]}` (последние N). `from`/`subject` — в meta, через
`entity_get(id)` при ответе.
- Мопitor-команда при CC — HTTP-опрос `GET /inbox?project=...` (без токена на
текущем проде; если `MAPPA_API_TOKEN` задан — `x-api-token` header).
## Steps
1. **Mode check.** Headless / non-interactive (`claude -p`, `pi -p`,
`ctx.hasUI === false`): **STOP — do not raise a monitor.** The pull pickup
(CC Stop-hook / pi `agent_settled`) plus `Notify:` cover delivery there; a
Monitor can't idle-watch in headless.
2. **Raise exactly one persistent Monitor** on the Mappa inbox:
`mcp__mappa__inbox_monitor(project=<cwd-имя-папки>, limit=50)`, poll
interval ~15s, `description: "inbox watcher"`. Dedup by письменному id:
новое письмо — это id, которого ещё не было в предыдущих ответах.
3. **Do not sweep yourself.** Killing orphans is the hook's job (it runs before
you, at SessionStart).
4. **On an event** (новый id в ответе inbox_monitor), прочитай письмо (body) и
обработай per `inter-session-messaging`. Для ответа — `entity_get(id)`
`meta.from`. Письма не перемещаются (нет `.read/`) — обработанные остаются
в списке; дедуп по id в памяти монитора.
5. **Teardown is automatic.** The Monitor dies at session end. Do **not** add a
SessionEnd teardown.
## Pi (pi-coding-agent) support — session-scoped global extension
Same contract, pi-native, session-scoped: the extension polls ONLY the current
session's project inbox (`GET /inbox?project=<basename cwd>`); it never reads
other projects' inboxes (vitya's rule: an agent may only read its own
project's inbox). Installed globally so *every* pi session has the capability,
but each session only ever touches its own project.
- **Source of truth:** `~/projects/pi-extensions/extensions/inbox-monitor.ts`
(репо `OpeItcLoc03/pi-extensions`, Gitea — дом pi-расширений). Deploy:
`just install` в клоне репо — копирует с затираанием в
`~/.pi/agent/extensions/inbox-monitor.ts` (global → every pi, every
directory), hot-reload with `/reload`.
- **Opt-in per project:** AGENTS.md / CLAUDE.md line
`inbox monitor: raise on start`. (Файловой директории `.agents/inbox/` больше
нет — триггер только строка.)
- **PUSH:** ~15s poll of the session's own Mappa inbox (interactive only).
**PULL:** `agent_settled` sweep — same poll, backstop. Both share one dedup
set per process (by letter id).
- **Headless (`pi -p`, `ctx.hasUI === false`):** NO delivery — no watcher, no
sweep. Messages sit in Mappa until an interactive session picks them up.
- **Delivery:** `pi.sendUserMessage(body, { deliverAs: "followUp", triggerTurn:
true })` — paged into the transcript as a user message; the agent handles it
per `inter-session-messaging`.
## Failure modes
- **Нет opt-in строки** → монитор не поднимается; ожидаемо.
- **Сервис mappa недоступен** → poll-запрос падает; монитор ретраит следующий
тик. Письма в сервисе не теряются (они — сущности), доставятся когда сервис
вернётся. НЕ дублируй в файлы — фолбэк-канал выпилен.
- **Two live interactive sessions on the same project** → оба поллят один
инбокс; дедуп по id делает доставку first-wins (кто первый прочитал id —
тот и доставил; второй пропускает). Real-time paging теряет только тот, кто
опоздал; письма не теряются (pull-свип в конце хода).
- **Monitor auto-stopped** → harness останавливает мониторы с переизбытком
событий; дедуп по id держит частоту пейджинга под баром.
- **Обработанное письмо вернулось в списке** — не баг: в Mappa нет `.read/`,
письма не перемещаются. Сверяйся с уже виденными id, не перечитывай.
## What NOT to do
- **Don't watch the file inbox** (`.agents/inbox/`) — канал выпилен; пиши и
читай через Mappa.
- **Don't watch the inbox with a background Bash** (`run_in_background`) — it
leaks across `/clear` and accumulates zombies. Use the Monitor tool.
- **Don't add a SessionEnd teardown hook** — the Monitor self-terminates.
- **Don't raise more than one monitor.** The hook guarantees a clean slate
before you raise.
- **Don't handle message content here** — that's `inter-session-messaging`.
- **Don't rely on this in headless** — use the pull model (Stop-hook +
Notify).

View File

@@ -1,103 +0,0 @@
# SessionStart inbox-monitor injector hook (session-inbox-monitor skill, v1.0.0).
#
# Channel is Mappa, NOT files (flip, решение 15): letters are entities `inbox`
# (`i:N`) in the mappa service, read via HTTP `GET /inbox?project=<name>`.
# Two jobs, run on every SessionStart (startup / resume / clear / compact):
# (a) SWEEP - kill orphaned inbox-monitor OS processes of THIS project.
# A `/clear` does NOT fire SessionEnd, so a Monitor's underlying
# poll process can outlive the session it belonged to. Without a
# sweep, re-raising would stack duplicates. Match is by a sentinel
# string (CLAUDE_INBOX_MONITOR) baked into the poll command PLUS
# this project's directory - so we never touch unrelated processes.
# (b) INJECT - additionalContext telling the agent to raise a persistent
# Monitor (Monitor TOOL, not background Bash) polling the Mappa
# inbox of this project (HTTP GET /inbox).
#
# Opt-in per project: AGENTS.md or CLAUDE.md line `inbox monitor: raise on start`.
# (The `.agents/inbox/` dir trigger is gone - no file channel anymore.)
#
# Headless (`claude -p`): there is NO reliable hook-level signal to detect it
# (verified 2026-06-17 - `source` and CLAUDE_* env vars don't distinguish it).
# So the hook injects unconditionally and the SKILL instructs the agent to skip
# when headless. A Monitor raised in headless is harmless (killed ~5s after the
# run ends); a false-skip in an interactive session would silently lose the
# feature - so the default errs toward raising.
#
# Machine-local deploy target: ~/.claude/hooks/inbox-monitor.ps1 (registered in
# ~/.claude/settings.json SessionStart). Versioned here for multi-machine rollout.
param(
[string]$ProjectDir = $env:CLAUDE_PROJECT_DIR,
[string]$Endpoint = $env:MAPPA_CORE_URL
)
if (-not $ProjectDir) { exit 0 }
if (-not $Endpoint) { $Endpoint = 'https://mappa.vds.kzntsv.site' }
# UTF-8 stdout guard. This hook emits JSON (additionalContext) to a redirected
# pipe under WinPS 5.1 - the same context that mojibaked stop-dispatcher output.
# The project name (user-data) is interpolated into stdout, so set UTF-8 as a
# forward-guard. Idempotent.
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
$projectName = Split-Path $ProjectDir -Leaf
$agentsMd = Join-Path $ProjectDir 'AGENTS.md'
$claudeMd = Join-Path $ProjectDir 'CLAUDE.md'
# --- opt-in gate (line in AGENTS.md or CLAUDE.md) ---------------------------
$optedIn = $false
foreach ($md in @($agentsMd, $claudeMd)) {
if (Test-Path $md) {
if (Select-String -Path $md -SimpleMatch 'inbox monitor: raise on start' -Quiet -ErrorAction SilentlyContinue) {
$optedIn = $true
break
}
}
}
if (-not $optedIn) { exit 0 }
# Forward-slash project dir: the Monitor poll command (Git Bash) uses this form,
# so both the sweep match and the injected command share one literal.
$dirFwd = ($ProjectDir -replace '\\', '/')
# --- (a) sweep orphaned monitors of THIS project ----------------------------
# Match = sentinel AND this project's dir in the same process command line.
try {
Get-CimInstance Win32_Process -ErrorAction Stop |
Where-Object {
$_.CommandLine -and
$_.CommandLine -match 'CLAUDE_INBOX_MONITOR' -and
$_.CommandLine -like "*$dirFwd*"
} |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
} catch { }
# --- (b) build the canonical Monitor poll command ---------------------------
# `: CLAUDE_INBOX_MONITOR` is a bash no-op carrying the sweep sentinel in the
# process command line without polluting the event stream. Polls the Mappa
# inbox of this project over HTTP, extracts letter ids via node (present on
# every machine that runs the mappa MCP), de-dups by id so a sitting letter
# pages once, not every 15s (a noisy monitor is auto-stopped).
$auth = ''
if ($env:MAPPA_API_TOKEN) { $auth = "-H 'x-api-token: $($env:MAPPA_API_TOKEN)'" }
$cmd = @"
: CLAUDE_INBOX_MONITOR; s=' '; while true; do ids=`$(curl -s -m 10 $auth '__ENDPOINT__/inbox?project=__PROJECT__&limit=50' | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{const r=JSON.parse(d).rows||[];for(let i=r.length-1;i>=0;i--)console.log(r[i].id)}catch(e){}})"); for id in `$ids; do case "`$s" in *" `$id "*) continue;; esac; s="`$s`$id "; echo "New inter-session message in Mappa inbox (letter id `$id) - read it via inbox_monitor and handle now"; done; sleep 15; done
"@
$cmd = $cmd.Trim().Replace('__ENDPOINT__', $Endpoint.TrimEnd('/')).Replace('__PROJECT__', $projectName)
# --- (c) inject the raise-instruction --------------------------------------
$ctx = @"
[session-inbox-monitor] This project participates in inter-session messaging. As your FIRST action, raise a persistent inbox watcher so messages from other sessions page you in real time.
Use the Monitor tool with persistent: true, description "inbox watcher", and this EXACT command:
$cmd
Do NOT use a background Bash for this - it leaks across /clear. The Monitor tool is session-bound and tears down on its own at session end. The paired SessionStart hook already swept any orphaned watcher before this, so raise exactly one.
If you are running headless (claude -p / non-interactive), SKIP this - the Stop-hook inbox pickup plus Notify cover delivery there. See the session-inbox-monitor skill for the full contract.
"@
@{ hookSpecificOutput = @{ hookEventName = 'SessionStart'; additionalContext = $ctx } } | ConvertTo-Json -Compress -Depth 5
exit 0

View File

@@ -1,87 +0,0 @@
---
name: using-system-snapshot
author: ours
version: 1.0.0
description: "Use at the start of an ops-context session, and ALWAYS before asserting anything about the mappa meta-service, the agent poller, local docker containers, or cross-project task load — call `mcp__mappa__meta_health` + `mcp__mappa__admin_status` (сервис жив, нагрузка) and `mcp__projects-meta__meta_system_snapshot` (поллер/докер/таски) instead of running `tasklist` / `docker ps` / guessing by hand. Triggers on «что запущено», «что сейчас крутится», «состояние системы», «состояние машины», «поллер работает?», «поллер живой?», «что с докером», «сводка по задачам», «what's running», «system status», «system snapshot», «is the poller up», «is the runner alive», «what containers are up». Read-only — no per-session grant needed. Skip for deep single-container docker diagnosis (that's using-vds-ops for the VDS / docker logs locally) and for mutating or precise per-task work (that's using-projects-meta / mappa task.*)."
---
# using-system-snapshot
## Overview
Two layers, one mental model: **mappa meta-service** (решение 14/15 — мета в сервисе) + **машина** (поллер/докер/таск-нагрузка — внешняя инфра).
1. Сервис жив и как загружен: `mcp__mappa__meta_health` (no args → `{ok}`) + `mcp__mappa__admin_status` (счётчики сущностей по типам/проектам — живая нагрузка).
2. Машина: `mcp__projects-meta__meta_system_snapshot` — поллер agents-task-runner, локальные docker-контейнеры, сводка таск-нагрузки из кэша.
Заменяет старый разброс `tasklist` / `docker ps` / ручного `meta_status` одной-двумя зондами.
**Core rule: never assert состояние сервиса, поллера, контейнеров или таск-нагрузки без вызова тула в этом же turn.** Память и «он же работал ранее» — не evidence.
## When to use
- Session start в **ops-контексте** — ориентация перед infra/раннер/борд-работой.
- User спрашивает что живо: «что запущено», «состояние системы», «поллер работает?», «что с докером», «what's running», «is the poller up».
- **Перед любым claim** о живости mappa-сервиса, поллера, контейнера или числа активных/блокированных тасок.
- Быстрый взгляд на таск-нагрузку (где сейчас сконцентрирована работа).
## When NOT to use
- Глубокая диагностика **одного** контейнера (logs/inspect/stats/restart-loop) — это using-vds-ops для Rusonyx VDS или `docker logs` локально.
- **Мутации** тасок или чтение полного борда/тела задачи — using-projects-meta (и mappa task.* / локальный `.tasks/` для текущего проекта).
- Библиотечные доки, поиск по коду — не сюда.
## Prerequisites
- `mcp__mappa__*` — mappa MCP (сервис meta; установка — setup-проект mappa).
- `mcp__projects-meta__meta_system_snapshot` — projects-meta-mcp (поллер/докер/кэш-таски). Если тула нет — `setup-projects-meta`.
## The calls
`mcp__mappa__meta_health` — no args, read-only. `{ok: true, service: "mappa"}` — сервис жив.
`mcp__mappa__admin_status` — no args, read-only. Счётчики по типам и проектам.
`mcp__projects-meta__meta_system_snapshot` — no args, read-only. Три ключа:
| Key | Shape | Liveness |
|---|---|---|
| `poller` | `{ running: bool, projects: "<owner/repo …>" }` | **live** на момент вызова |
| `docker` | `[{ name, status }]` — локальные контейнеры | **live** на момент вызова |
| `tasks` | `{ "<owner>/<repo>": { active, blocked }, … }` | **из кэша** projects-meta — может быть stale |
`docker`**локальная** машина (включая `agents-task-runner-*`), НЕ VDS. `tasks` — кэш; для точного состояния — using-projects-meta Step 0 или mappa/локальный `.tasks/`.
## Output format — одна строка на секцию
Сожми в 34 строки, не дампи raw-object.
```
🟢 Mappa alive — 42 wiki / 18 tasks, 12 проектов
🟢 Poller running — OpeItcLoc03/skills (🔴 если running:false)
🟢 Docker — 8/8 up (иначе только проблемные)
📋 Tasks — 23 active / 41 blocked, 17 проектов (назвать 23 самых загруженных)
```
Правила на строку:
- **Mappa** — 🟢/🔴 + admin_status-сводка (счётчики по типам или проектам — кратко). Сервис упал = заголовок.
- **Poller** — 🟢/🔴 + running-flag + projects-строка. Остановлен — скажи прямо.
- **Docker** — если всё `Up` (вкл. `Up … (healthy)`) — `N/N up`. Иначе только проблемные (`Restarting`, `Exited`, `(unhealthy)`, `Created`, `Paused`) по имени + статусу.
- **Tasks** — суммы (Σ active / Σ blocked) + 23 проекта с наибольшей активностью. Полный расклад — только по запросу.
## What NOT to do
- **НЕ** утверждай «поллер работает» / «все контейнеры up» / «N активных тасок» по памяти или прошлому снапшоту. Зови тулы в текущем turn — liveness требует свежих данных.
- **НЕ** падай на `tasklist` / `docker ps` / ручной `meta_status` — это scatter, который скил заменяет. (`docker logs` — только для глубокой диагностики одного контейнера.)
- **НЕ** вставляй raw-JSON. Строки, по одной на секцию.
- **НЕ** подавай `tasks`-счётчики как точные — они из кэша. При важности точности — флаг сталезности + using-projects-meta.
## Common mistakes
| Mistake | Fix |
|---|---|
| «Сервис жив» без вызова в этом turn | Зови `meta_health`/`admin_status` — liveness требует текущих данных. |
| «Poller up» без вызова | `meta_system_snapshot` в текущем turn. |
| `docker ps` / `tasklist` вместо зондов | Один-два вызова тула; в этом смысл. |
| Читать снапшот-`docker` как VDS-флот | Это **локальная** машина. VDS — using-vds-ops. |
| `tasks`-счётчики как авторитетные | Они кэшированы. Точность — using-projects-meta Step 0 / mappa / `.tasks/`. |
| Дампить raw-объект | Сожми в строки (mappa / poller / docker / tasks). |