Nirav PatelandClaude Fable 5 2952b0c289 fix(intake): resolve the hermes CLI per call, prefer the release shim
The shared checkout's venv is deleted and recreated by release cutovers; a
cutover mid-run left every intake tick failing ENOENT on
<checkout>/venv/bin/python. ~/.local/bin/hermes is repointed by the release
recipe itself, so resolve it fresh on each call and keep the checkout venv as
a fallback.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-22 22:33:38 -04:00

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 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:<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 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 <checkpoint> 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:

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 itemshre-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 taskhermes kanban create … --created-by pulse-bridge --idempotency-key pulse-<post id> --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 <id>, kanban <task> on the user's post.
  4. Tracking — each poll reads kanban list --json once; 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 done on 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 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.
S
Description
Mirror the shre-items work ledger into Pulse (mib007) posts/comments
Readme
159 KiB
Languages
Python 100%