Skip to content

Mailboard: a shared, unaddressed board for loops (beta-ramped) - #229

Open
scgopi wants to merge 5 commits into
mainfrom
feat/mailboard-beta
Open

Mailboard: a shared, unaddressed board for loops (beta-ramped)#229
scgopi wants to merge 5 commits into
mainfrom
feat/mailboard-beta

Conversation

@scgopi

@scgopi scgopi commented Aug 31, 2026

Copy link
Copy Markdown
Owner

What this adds

node send and edges are addressed — a loop must already know a peer's id, and the daemon routes to that one peer. The Mailboard is the ambient counterpart: one append-only board per graph that any loop can post to for whoever comes next — a decision made, a dead end hit, a claim staked — and read back with one command, with no wiring and no ids. Posts survive their authors: a loop created after the writer is gone still finds the note at its next wake.

The verbs

graphcode mailboard post <path> [--topic <t>] <note>   # leave something behind
graphcode mailboard sync <path>                        # read your unread (and mark them read)
graphcode mailboard list <path>                        # peek without moving your cursor
graphcode mailboard watch <path> [--topic <t>]         # hear new posts while you live

Attribution rides ZMX_SESSION exactly like node send; posts from a human's shell show as "a human".

How it works

  • MailboardKit module (new Tuist target, Foundation-only): the post/watch model, caps (1 KiB body, 200 posts, oldest pruned), and the unread arithmetic every surface shares.
  • Board state rides LoopGraph: one writer (the daemon), persisted beside the graph file, snapshot in every .graphChanged — which is the CLI's read path, so no second read protocol. The global graph at graphcode://global becomes a cross-project board for free. Absent-while-empty encoding keeps untouched graph files byte-identical, and old files decode with an empty board.
  • Watcher wakes ride deliverAdHocMessage's follow-up semantics: typed into a live idle session, queued behind a busy turn, staged to memory otherwise. The post itself is the durable half; the ding is best-effort.
  • Discovery: the generated briefing teaches the verbs (only while the feature is on) and the wake digest reminds relaunching loops to check the board before redoing work a predecessor may have posted about.

The ramp

Beta only, kill-switchable. FeatureRamps.mailboard ships beta: 100 / stable: 0; the app resolves the ramp into mailboardEnabled in ~/.graphcode/settings.json — the one bit the daemon (which cannot see ramps) enforces on every command, the briefing section, and the digest line. An explicit Settings choice outranks the ramp forever; the daemon refuses board commands out loud with a pointer to the setting while it is off.

Verification

  • New tests: MailboardTests (store handlers, cursors, watcher wakes, pruning, Codable back-compat), MailboardCommandTests (CLI parsing/attribution), SettingsMailboardTests + FeatureRampsTests.mailboardShipsBetaOnAndStableOff.
  • Full suite: 1316 tests / 143 suites pass; GraphcodeKit and app schemes build clean.

scgopi added 2 commits August 31, 2026 09:39
node send and edges are addressed — a loop must already know a peer's id.
The Mailboard is the ambient counterpart: one append-only board per graph
(global graph included) that any loop can post to for whoever comes next —
a decision made, a dead end hit, a claim staked — and read back with one
command, with no wiring and no ids. Posts survive their authors; a loop
created after the writer is gone still finds the note.

- MailboardKit module: post/watch model, caps (1 KiB body, 200 posts),
  unread arithmetic shared by every surface
- Board rides LoopGraph: one writer, persisted beside the graph, snapshot
  in every .graphChanged (the CLI's read path), graphcode://global free
- GraphStore: mailboard post/sync/watch commands; watcher wakes ride
  deliverAdHocMessage's follow-up semantics (typed when idle, staged to
  memory otherwise); fresh-read beta gate with refusal said out loud
- Wake digest gains a check-the-board reminder; the generated briefing
  teaches the verbs only while the feature is on
- Ramp: FeatureRamps.mailboard ships beta:100/stable:0; the app resolves
  it into settings mailboardEnabled, explicit user choice outranks the
  ramp, Settings toggle offered only while the ramp has it on
- CLI: graphcode mailboard post|sync|list|watch, attributed via
  ZMX_SESSION exactly like node send

Signed-off-by: scgopi <scgopireddy@gmail.com>
Signed-off-by: scgopi <scgopireddy@gmail.com>
@scgopi

scgopi commented Aug 31, 2026

Copy link
Copy Markdown
Owner Author

Rename the Mailboard to Artifactory

@scgopi

scgopi commented Aug 31, 2026

Copy link
Copy Markdown
Owner Author

Every shared communication should be stored inthe artifactory as well

…p on delete

Review feedback on #229, all three:

- Mailboard -> Artifactory everywhere: the module (ArtifactoryKit), the
  types, the settings bit (artifactoryEnabled), the ramp key, the verbs
  (graphcode artifactory post|sync|list|watch), the briefing, and the
  digest line.

- Every shared communication is now stored in the artifactory as well:
  direct sends (node send, immediate or follow-up) land as a 'direct'
  record, delivered message-edge deliveries as 'direct', handoffs (with
  their payload) as 'handoff'. Record-only by design — mirroring never
  rings watchers, or a busy graph would double-deliver everything.
  Undelivered edge messages are not recorded: the artifactory records
  what actually was said, and an edge that failed transport said nothing.
  Cycle re-entries stay out for the same reason heartbeat ticks stay out
  of memory logs.

- Deleting a loop deletes the artifactory posts it authored, alongside
  the edges, session, and memory teardown delete already performs — one
  irreversible confirmation covers the whole blast radius, spawned
  descendants included. Posts where the loop was only the recipient stay
  (they are the other side's record); node stop keeps everything.

Full suite: 1323 tests / 143 suites pass; suite lint clean.

Signed-off-by: scgopi <scgopireddy@gmail.com>
@scgopi

scgopi commented Sep 1, 2026

Copy link
Copy Markdown
Owner Author

Both comments addressed (f613a66), plus the deletion question you raised:

  1. Renamed Mailboard → Artifactory end to end: ArtifactoryKit module, ArtifactoryPost/ArtifactoryWatch, artifactoryEnabled setting + ramp key, graphcode artifactory post|sync|list|watch verbs, briefing and wake-digest wording.

  2. Every shared communication is now stored in the artifactory: node send (immediate and follow-up) lands as a direct record, delivered message-edge deliveries as direct, handoffs — payload included — as handoff. Mirroring is record-only (it never rings watchers, so a busy graph doesn't double-deliver), and undelivered edge messages are deliberately not recorded: the artifactory is a record of what was actually said.

  3. Deleting a loop deletes the artifactory posts it authored along with the edges/session/memory teardown delete already does — spawned descendants included. Posts where the loop was only the recipient stay (the other side's record), and node stop keeps everything.

Full suite: 1323 tests / 143 suites pass, including 8 new tests for mirroring and deletion cleanup.

scgopi added 2 commits August 31, 2026 21:37
…uild 222)

Signed-off-by: scgopi <scgopireddy@gmail.com>
…unds

Independent review of #229, addressed in full:

- Major 1: the artifactory gate forwards into sub-graph stores
  (subGraphStore and runInSubGraph), so a piloted composite's workers can
  post, sync and watch — and their communication mirrors — instead of
  being refused by a nil gate while their briefing teaches the verbs.
  nil still means off; forwarding, not a nil-means-on reading, was the fix.
- Major 2: merged origin/main (was based on 1ecdec1; main had moved to
  0.1.57/221) — conflict resolution keeps both sides' fields; version is
  now 0.1.58-beta1 (build 222), past main's counter instead of colliding
  with it. The CLI stamp uses a fixed-dateFormat DateFormatter with a
  pinned locale, the one Foundation API Linux CI cannot argue with.
- Minor 3: mirror truncation reserves room for the ellipsis (the bound
  is now really 1024 bytes, not 1026).
- Minor 4: the sync race comment now states the race honestly instead of
  claiming the opposite of the behavior.
- Minor 5: mirrored direct/handoff records never ring watchers, so a
  watch on those topics alone stays silent — documented in the help and
  the briefing.
- Minor 6: MAILBOARD header and 'a Artifactory' grammar slips fixed.
- Minor 7: OrderedImports across the five files; all touched files pass
  swift-format --strict, repo-wide directories included.
- Minor 8: imported loops start with a clean cursor — a stale number from
  the source board could hide this board's mail forever. Watch travels as
  a preference.
- Nits 9, 10, 12: a foreign loop's post reads 'an outside loop' (id kept
  honestly); the briefing interpolates inline so off means byte-for-byte
  the pre-Artifactory document; watch --off when not watching is a no-op,
  not an error; help block spacing; the topic doc sentence now says what
  the implementation does. Nit 11 (renderPosted sequence echo under a
  concurrent post) is accepted as-is: the ack pattern is racy by design
  and the post body is what matters.
- Tests: refusal announcement, mirror truncation bound, live-idle watcher
  delivery, composite gate inheritance, import cursor reset, watch-off
  idempotence, settings round-trip, briefing on/off, wake digest
  on/off. Full suite: 1371 tests / 144 suites pass.

Signed-off-by: scgopi <scgopireddy@gmail.com>
@scgopi

scgopi commented Sep 1, 2026

Copy link
Copy Markdown
Owner Author

Independent review addressed in full (833e4e2), including the merge of the stale base:

Majors

  1. Composite gating — the gate now forwards into both sub-graph store construction sites; a piloted composite's workers post/sync/watch and mirror exactly like any loop, with a test pinning it. nil still means off — forwarding was the fix, not a nil-means-on reading.
  2. Stale baseorigin/main merged (was based on 1ecdec1). Version is now 0.1.58-beta1 / build 222, past main's 221 rather than colliding at 217. The one Linux-CI-sensitive call (Date.formatted) is replaced by a fixed-dateFormat DateFormatter with a pinned locale.

Minors — 3 (truncation now really ≤1024), 4 (race comment states the race), 5 (dead direct/handoff watches documented in help + briefing), 6 (MAILBOARD/grammar), 7 (imports fixed; all touched dirs pass swift format --strict), 8 (imported loops start with a clean cursor; watch travels as a preference).

Nits — 9 (foreign sender reads "an outside loop"), 10 (off-briefing is byte-identical to pre-Artifactory), 12 (idempotent watch --off, help spacing, topic doc sentence). Nit 11 accepted: the ack pattern is racy by design and the body is what matters.

Tests — 10 added for the review's holes; full suite 1371 tests / 144 suites pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant