# pulse-bridge v1 Mirrors the estate's shared work ledger (`~/.local/bin/shre-items`, JSONL store at `~/.shre/open-items/items.jsonl`) into **Pulse** — the social-feed surface of the local mib007 instance (`http://127.0.0.1:5520`, launchd `ai.shre.mib007`). Ledger items become posts, stage changes become threaded comments, needs-you escalations mention the user. ## Where Pulse actually lives (re-verified 2026-08-23) The Pulse UI is mib007's **Activity** app (`/activity`, `PULSE_ROUTE` in `ui/src/platform/lib/app-routes.ts`). Since the unified message envelope landed (migrations 0101-0111 applied; `feed_items` view live) the Activity feed **reads AND writes the envelope** (`useActivity.ts` stage 3), so the bridge does too: - create post: `POST /api/workspaces/:wid/messages` `{channelId | channelSlug, body, idempotencyKey}` → returns the message (`id` = post id; a replayed idempotencyKey returns the ORIGINAL message) - comment/reply: same endpoint with `threadId: ` - read roots: `GET /api/workspaces/:wid/messages?channelId=&kind=post` (root posts only; replies are nested in `blocks.replies`) — the same query the Activity UI issues - channel: the envelope channel with slug `activity`. Comms-era channel and message **ids are preserved** in the envelope (backfill 0103 + mirror trigger 0104), so v0 state (channel id, ledger→post map) carried over unchanged and old threads still accept replies. Auth: mib007 service token (`~/.shre/service-tokens.json`, key `mib007`), board-level, valid from loopback. mib007 runs in `authenticated` mode, so requests without it are rejected. **Attribution caveat:** the envelope attributes writes to the authenticated actor and has no `userName` override, so bridge posts no longer carry the "Ledger" byline — they render under the service actor (UI fallback "User"). Legacy posts keep their mirrored `actorDisplayName: "Ledger"`. ## Mapping | ledger op | Pulse action | |---|---| | `add` | new post: emoji by kind + title, detail, tag line (`#kind #stage #tags surface: project: ledger:`) | | `update` | comment on the mapped post: `stage → review`, `⛔ failed at verify, attempt 2 — `, `kind → …` | | `update` to `kind: needs-you` | comment `@nir NEEDS YOU: …` | | `close` | comment `✅ done — ` or `🗑 dropped — ` | First run seeds posts for **currently-open items only** (`shre-items list --json`); history before the bridge is not replayed. ## State & config - State: `~/.shre/pulse-bridge/state.json` — checkpoint (`max at` seen), boundary-second `(id,op,at)` dedupe set (the feed window is inclusive), ledger-id → post-id map, resolved channel id. Written atomically (tmp + `os.replace`). - Config: `bridge.env` next to the script (or `PULSE_BRIDGE_ENV`); environment variables override. See the file for keys (poll interval, mib base URL, workspace, channel, mention handle, token file). - Poll cadence: every 30 s the daemon runs `shre-items feed --since ` and processes new records in `at` order. A failed record stops the batch **before** the checkpoint advances past it, so it is retried next cycle. ## launchd `ai.shre.pulse-bridge` — KeepAlive daemon, logs to `~/.shre/pulse-bridge/bridge.log`: ```sh cp ai.shre.pulse-bridge.plist ~/Library/LaunchAgents/ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.shre.pulse-bridge.plist ``` ## v1 intake (reverse direction) New top-level posts in the `activity` channel written by a human (not the bridge, not agent accounts, not `🤖`-prefixed, not mirror posts carrying a `ledger:` tag line) become executed, tracked background work. **Prefix a post with 💬 (or `chat:`) to just talk** — chat-only posts are never turned into a ledger item or task and the bridge never replies to them. The prefix list is configurable via `PULSE_BRIDGE_INTAKE_OPTOUT` (comma-separated, case-insensitive). 1. **Ledger item** — `shre-items add --kind pipeline --stage queued --tag pulse-intake`, title = first 80 chars of the post, detail = full post + post id. The ledger→post map is seeded with the USER'S post id *before* the item's `op:add` reaches the feed, so the outbound mirror adopts the user's post instead of creating a duplicate. 2. **Kanban task** — `hermes kanban create … --created-by pulse-bridge --idempotency-key pulse- --json` on the default board; the Hermes embedded-gateway dispatcher picks it up (~60 s). Cap: max **3** concurrent intake-spawned tasks; overflow posts get `⏳ queued behind N tasks` and start when a slot frees. 3. **Acknowledgment** — comment `🤖 picked up — ledger , kanban ` on the user's post. 4. **Tracking** — each poll reads `kanban list --json` once; on a status change it comments (`▶️ running` / `⛔ blocked` / `✅ completed — `) and advances the ledger item (running → stage build; done → closed with `--why "kanban completed"`). Intake state (`intake_checkpoint` epoch-ms + post↔ledger↔task map) lives in the same atomically-written state.json. First intake run only sets the checkpoint to the newest existing post — history is never ingested. ### Intake caveats (accepted, reviewer-flagged) - Status/ack comments are best-effort: a crash between a state save and its comment can drop or (once) repeat a comment. Ledger and kanban stay consistent — `shre-items done` on a closed item is a logged no-op and `--idempotency-key pulse-` adopts the existing task on retry. - Bridge-output filtering is structural first (envelope-native bridge posts carry the service actor id `00000000-…-0001`, agents carry `actorType: "agent"`), then content-based for legacy posts (display name, `🤖` prefix, `ledger:` tag line, threaded). ## Limitations - **Mentions dispatch server-side** — the envelope route runs the same `dispatchMention` side-effect comms did: the first `@handle` matching a workspace agent fires an AI reply; a matching workspace member gets a `comment.mention` notification. The bridge zwsp-neutralises every `@` in ledger-derived text and deliberately keeps the needs-you `@rapidnir` escalation live so the human is actually notified. The historical insert-then-500 mention crash is fixed AND harmless now: writes carry idempotency keys, so a retry returns the original message instead of duplicating it (`DROP` now only happens on 400/404/410 — thread target gone). - **No byline override.** See the attribution caveat above — posts render under the service actor, not "Ledger". Needs a mib007-side `actorDisplayName` passthrough for service tokens to restore. - Updates for items that pre-date the bridge and were never seeded (closed before first run) are skipped with a log line — there is no post to comment on. - One workspace, one channel. Multi-workspace fan-out is future work. - Reactions on posts are not mirrored back to the ledger.