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
mib007's mention path is fixed (Nirlabinc/mib007 PR #32): mention resolution
is now fire-and-forget server-side (a lookup failure can no longer 500 the
POST after the insert), and an @handle matching an active workspace-member
user's lower(name) creates a comment.mention notification.
- update_body: the needs-you escalation line now emits a real @handle instead
of the zwsp-neutralised one, so the human actually gets pinged.
- neutralize() stays for all ledger-derived text (titles/details/notes): item
text containing @aros/@ellie must not accidentally fire an agent reply.
- bridge.env: PULSE_BRIDGE_MENTION nir -> rapidnir. 'nir' resolves to nothing
(mentions match lower(user.name) over ACTIVE workspace members; the only
human members of Nirlab Command Center are Board and RapidNir).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YECpkAwQUwgu7NVy91R8fW
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
intake_track() already narrates status on the user's post; mirroring the
same item's ledger update/close ops added near-duplicate comments (seen in
the E2E smoke: 'stage → build' next to '▶️ running').
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YECpkAwQUwgu7NVy91R8fW
- ledger_add_for_post adopts an existing open item carrying post:<id>
before adding (crash between add and state save no longer duplicates)
- intake window is now inclusive (ts >= cp, init cp = newest+1) with the
post-id map as the dedupe, so same-millisecond posts can't be lost to a
crash between them
- newcomers can't jump the queued backlog: loop order is track -> promote
-> ingest, and ingest queues when a backlog exists even if a slot is free
- documented the kanban --idempotency-key crash-recovery contract
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YECpkAwQUwgu7NVy91R8fW
- new top-level non-bridge posts in the activity channel -> shre-items
pipeline item (tag pulse-intake) + hermes kanban task (idempotency-key
pulse-<post id>, created-by pulse-bridge) + ack comment
- outbound dedupe: ledger->post map seeded with the user's post id before
the op:add hits the feed, so the mirror adopts the post
- status tracking via one 'kanban list --json' per poll: running/blocked/
done comments on the post, ledger stage build -> done
- cap 3 concurrent intake tasks; overflow queued with a comment and
promoted when slots free; intake state in the same atomic state.json
- first intake run only sets the checkpoint (no history ingestion)
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YECpkAwQUwgu7NVy91R8fW
- create_post: on HTTPError, look for an existing post tagged ledger:<id>
(insert-then-error recovery) and adopt it before retrying
- save_state: fsync tmp file before rename
- load_state: corrupt state is fatal (auto-reseed would duplicate every
open item's post)
- comment: drop only on 500/404/410; 401/403/429/502/503 retry next poll
Accepted as-is (with rationale): same-second ordering relies on the feed
replaying the JSONL in append order + Python's stable sort; seed-window
add+close races reduce to the documented 'predates bridge' skip.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YECpkAwQUwgu7NVy91R8fW
Smoke test found: any @handle in a comms message 500s AFTER the row is
inserted (agents.url_key column missing on this instance), so a retry
duplicates the message. Neutralise ALL '@' the bridge emits (incl. its
own @nir escalation, zwsp renders identically) and make comment HTTP
errors non-retryable best-effort drops; posts still retry.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YECpkAwQUwgu7NVy91R8fW
- daemon polls shre-items feed every 30s with inclusive-window (id,op,at) dedupe
- op:add -> post in 'activity' channel; op:update -> threaded comment
(stage/kind/attempt/note, needs-you escalation mentions @nir);
op:close -> done/dropped comment
- first run seeds currently-open items only
- state (checkpoint + ledger->post map) atomic at ~/.shre/pulse-bridge/state.json
- launchd ai.shre.pulse-bridge (KeepAlive, logs to bridge.log)
- '@' in ledger-derived text neutralised (zwsp) so comms agent-mention
regex can never fire an AI reply from item content
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YECpkAwQUwgu7NVy91R8fW