Pulse's Activity UI (verified live on :5520, Vite dev serving the checkout at 7293d857) now reads AND writes /api/workspaces/:ws/messages, so the bridge follows: - writes: POST /messages with server-side idempotencyKey (ledger:<id> for roots, (id,op,at) for feed comments, pulse-* keys for intake replies); replay returns the ORIGINAL message, so the v0 find-existing recovery scan and drop-on-500 comment policy are gone (5xx now retries safely, only 400/404/410 drop) - reads: intake ingests envelope roots (kind=post), normalized to the old comms row shape at the boundary; comms GET would miss envelope-native user posts entirely - state carries over unchanged: backfill 0103 + mirror trigger 0104 preserve comms ids, so the ledger→post map and channel id stay valid and old threads accept replies (verified via /messages/threads/<old-id>) - self-recognition: envelope posts carry no userName override, so intake now skips the service actor id (00000000-…-0001) structurally - codex P2 fixed: adopt unmapped v0-era posts (no idempotency key) by ledger:<id> body scan before creating, so an old insert-then-error survivor cannot be duplicated Known cosmetic tradeoff (documented in README): no byline override on the envelope route — new posts render under the service actor, not "Ledger". E2E on the live feed: post 715f52ec, stage comment 74d19219, needs-you @rapidnir comment 2f898b75 + comment.mention notification row for rapidnir-admin, close comment 9f0a5097; timed-out create retried into the same row (idempotency proven, count=1). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01YECpkAwQUwgu7NVy91R8fW
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: <post message id> - read roots:
GET /api/workspaces/:wid/messages?channelId=&kind=post(root posts only; replies are nested inblocks.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:<id>) |
update |
comment on the mapped post: stage → review, ⛔ failed at verify, attempt 2 — <note>, kind → … |
update to kind: needs-you |
comment @nir NEEDS YOU: … |
close |
comment ✅ done — <why> or 🗑 dropped — <why> |
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 atseen), 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.envnext to the script (orPULSE_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 <checkpoint>and processes new records inatorder. 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:
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).
- 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'sop:addreaches the feed, so the outbound mirror adopts the user's post instead of creating a duplicate. - Kanban task —
hermes kanban create … --created-by pulse-bridge --idempotency-key pulse-<post id> --jsonon 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 tasksand start when a slot frees. - Acknowledgment — comment
🤖 picked up — ledger <id>, kanban <task>on the user's post. - Tracking — each poll reads
kanban list --jsononce; on a status change it comments (▶️ running/⛔ blocked/✅ completed — <result>) and advances the ledger item (running → stage build; done → closed with--why "kanban <id> 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 doneon a closed item is a logged no-op and--idempotency-key pulse-<post id>adopts the existing task on retry. - Bridge-output filtering is structural first (envelope-native bridge posts
carry the service actor id
00000000-…-0001, agents carryactorType: "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
dispatchMentionside-effect comms did: the first@handlematching a workspace agent fires an AI reply; a matching workspace member gets acomment.mentionnotification. The bridge zwsp-neutralises every@in ledger-derived text and deliberately keeps the needs-you@rapidnirescalation 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 (DROPnow 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
actorDisplayNamepassthrough 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.