Files
pulse-bridge/README.md
T
Nirav PatelandClaude Fable 5 5896d6cab1 intake: configurable chat-only opt-out prefixes (πŸ’¬, chat:)
Posts starting with an opt-out prefix (PULSE_BRIDGE_INTAKE_OPTOUT,
case-insensitive) are never turned into a ledger item or kanban task and
get no bridge reply. Codex review: no findings.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YECpkAwQUwgu7NVy91R8fW
2026-08-22 16:12:35 -04:00

132 lines
6.7 KiB
Markdown

# pulse-bridge v0
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 (as discovered 2026-08-22)
The Pulse UI is mib007's **Activity** app (`/activity`, `PULSE_ROUTE` in
`ui/src/platform/lib/app-routes.ts`). Its feed is backed by the **comms**
tables, not the unified message envelope: the envelope work (`feed_items`
view, migrations 0101-0111) is in shreai PR #163 and is **not merged** into
the running mib007 (local checkout is at migration 0100). The
`/api/workspaces/:id/feed` route proxies to shre-feed (:5436), which is
**down** locally. So the write path Pulse really uses is:
- create post: `POST /api/workspaces/:wid/comms/channels/:cid/messages`
`{content, type: "text", userName}` β†’ returns the row (`id` = post id)
- comment/reply: same endpoint with `threadId: <post message id>`
- channel: the Activity app reads/writes the channel named `activity`
(creates it if missing) β€” same behaviour here.
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.
## 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`:
```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-<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 content-based (user name, `πŸ€–` prefix,
`ledger:` tag line, threaded); renaming the poster or ignore list while
old posts are inside the intake window could ingest bridge output. The
comms API has no structured message metadata to mark authorship.
## v0/v1 limitations
- **No real user-mention primitive.** mib007 comms has *agent* @mentions
only (the first `@handle` in a message that matches a workspace agent
triggers an AI reply). There is no user mention/notification hook, so
needs-you escalations are the literal text `@nir NEEDS YOU: …`.
- **Mention path is broken on this instance (found during smoke):** ANY
`@handle` in a message makes `POST …/messages` **500 after the row is
inserted** β€” the mention agent-lookup queries a nonexistent `url_key`
column on `agents` (`comms.ts` ~line 226; error in mib007 stderr log).
Retrying such a failure duplicates the message. The bridge therefore
(a) zwsp-neutralises **every** `@` it emits, including its own
`@nir` escalation (renders identically in the UI), and (b) treats an
HTTP error on a *comment* as non-retryable (`DROP … comment not
retried` in the log) β€” comments are best-effort; posts still retry.
- 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 v1.
- Fork-links (post β†’ ledger deep link and back) and reverse intake
(posting in Pulse creating/annotating ledger items) are **v1**.
- Reactions on posts are not mirrored back to the ledger.
- If the envelope migration (shreai #163) lands and Pulse moves to
`feed_items`, the write path here must be revisited.