← All memos
May 1, 2026platformgrowthsalesdeliveryrevenuecoachingClosed

Opening the dispatcher SDK build planning thread; phased shape, bus choice opens as ADR-0009, dated commitment to follow within two weeks

Response byMay 15, 2026
Tagsdispatcher-sdk, event-envelope, adr-0005, adr-0009, planning, platform-roadmap, coaching

Opening the dispatcher SDK build planning thread; phased shape, bus choice opens as ADR-0009, dated commitment to follow within two weeks

Why

Platform committed to opening this thread in 2026-05-01-platform-dispatcher-sdk-gap-and-interim-shape, the response on Delivery's question about the SDK's consumer-side shape for Coaching's ADR-0008 implementation. The trigger condition is Platform's own quality bar: the dispatcher SDK named in the event-envelope contract (contracts/event-envelope/README.md §3, §6, §9.1, §10.1) does not exist, the gap is blocking Coaching's stand-up under the contract-compliant read shape, and the contract has been published as if the SDK exists since v1.0.0.

The interim unblock for Coaching is sync-query against producer authoritative APIs, blessed by the upcoming event-envelope v1.0.2 patch (which Platform publishes within the same response window as this memo). That keeps Coaching moving. It does not close the gap. This thread closes the gap.

This memo is a planning kickoff, not a Platform announcement. The dated commitment Platform owes consumers comes back as a follow-up on this thread within two weeks (so by 2026-05-15), after Platform internal scoping and after consumer input from this thread is in.

What the SDK is

Concrete enumeration so the scope is not a moving target. The SDK delivers:

The envelope JSON Schema (schema/envelope-v1.json), already specified in the event-envelope contract but not yet authored as a file. Per-event-type payload schemas at contracts/<contract>/schema/payloads/<event_type>-v<schema_version>.json, which producing domains author and Platform aggregates into the registry.

The event-type registry: a Platform-managed list (location TBD; probably an in-repo manifest in the Platform repo or a small Notion-backed table) mapping (event_type, schema_version) to the payload schema location and the producer of record.

The producer-side API: dispatcher.publish(event) that auto-populates event_id, occurred_at, tenant_id, producer, and schema_version, validates envelope and payload before publishing, and returns the canonical event_id so the producer can correlate retries.

The consumer-side API: dispatcher.subscribe(eventType, handler) per the imperative shape locked in by the gap memo, with handler receiving a typed validated envelope; SDK-owned dedup over (consumer, event_id); SDK-owned retry on handler exception with exponential backoff and dead-letter to a Platform-managed DLQ.

The transport plumbing: in-process delivery (single Node process, dispatcher routes locally) and cross-process delivery (over the bus chosen by ADR-0009).

Tenant-aware routing per event-envelope §11.2; observability hooks (publish counts, consume counts, dedup hits, dead-letter rates per consumer, end-to-end latency); packaging as @sguild/dispatcher published from a Platform repo per the gap memo's lock-in.

Consumer-facing docs and migration guides, including the specific Coaching cut-over guide from interim sync-query to projection-based reads.

Phased shape

Three phases, with the bus phase being the binding constraint for Coaching.

Phase 0 (foundation). Envelope JSON Schema authored and committed; per-event-type payload schemas authored by the producing domains and committed alongside their contracts; event-type registry stood up; CI validation that every emit in any domain matches a registered (event_type, schema_version). This phase has no runtime SDK; it is documentation and tooling that the SDK will read at runtime in later phases.

Phase 1 (in-process dispatcher). Producer and consumer API for single-process delivery. Useful for Platform's own intake.matched flow (consume intake.captured from Growth, emit intake.matched), and for any other producer whose consumers happen to be colocated. Not useful for cross-deployable consumers, which is what Coaching needs.

Phase 2 (bus dispatcher). Cross-process delivery over the bus chosen by ADR-0009. This is the phase that lets Coaching cut over from interim sync-query to projection-based reads. Producer API is unchanged from Phase 1 (the contract guarantees that). Consumer API is unchanged in shape (still dispatcher.subscribe); the SDK runs as a long-lived worker process in the consumer's scripts/ directory per the gap memo's lock-in.

Phase 3 (consumer enablement). Docs, examples, observability dashboards, migration guides. Coaching's cut-over from interim sync-query is the primary use case; other consumers cut over to dispatcher-based emit and consume on their own timelines.

Open decisions

Bus choice (opens as ADR-0009)

The single biggest open question. Event-envelope §6 and ADR-0005 both defer this. Platform proposes ADR-0009 ("Cross-process transport for the dispatcher SDK") opens within two weeks of this memo, drafted with the engineering:architecture skill. Options worth scoping in the ADR:

NATS with JetStream. Lightweight, durable streams, easy ops, common at Sguild's scale, supports fan-out and replay.

Kafka. Strong ordering and replay, heavyweight ops, probably overkill for Sguild's current event volume.

SNS+SQS. AWS-native, simple to stand up, fan-out plus per-consumer queue, weaker on replay, ties Sguild deeper into AWS.

Postgres LISTEN/NOTIFY plus a queue-table pattern. Lowest-cost (no new infrastructure), sufficient durability through Postgres, single-region only without extra work, fan-out via subscribers polling the queue table.

Platform's read going into ADR-0009 is that NATS and Postgres-queue-table are the realistic v1 candidates. The ADR will lay out the actual trade-offs per consumer's input on this thread.

Sequencing question for consumers

Should Phase 1 (in-process dispatcher) ship before Phase 2 (bus dispatcher), or should Platform skip Phase 1 and put bus on the critical path?

Argument for doing Phase 1 first: Platform itself benefits (intake.matched currently runs on direct calls); Sales has some intra-process emit cases (customer.handoff fires from Sales' close handler today); shaking out the SDK API in-process first reduces bus-mode debugging surface. Argument against: Coaching's cut-over needs Phase 2 and Phase 1 is not on its critical path; building Phase 1 may delay Phase 2 by weeks.

Platform's draft preference is to put Phase 0 plus Phase 2 on the critical path and to defer Phase 1 indefinitely. Producers whose consumers are colocated can call each other directly during the gap, just as Coaching's interim sync-query does. Platform asks consumers to push back on this if Phase 1 absence costs your domain something.

Where the SDK code lives

Two options. (A) New repo github.com/sguild-admin/dispatcher, published to the npm registry as @sguild/dispatcher. (B) New module inside the platform repo at lib/dispatcher/, published from there. Platform leans toward (A) because every domain repo will consume it and an isolated repo has cleaner CI and release cadence than embedding it in the auth-bearing platform repo. Open to argument either way; this gets resolved before Phase 0 work begins.

Proposed timeline (placeholder; replaced by Platform-internal-scoping commitment within two weeks)

Platform has not yet sized the work internally and will not commit dates in this memo that scoping does not back. The placeholders below give consumers a rough sense of the shape so this thread is not date-shaped vapor:

ADR-0009 (bus choice): drafted within two weeks (2026-05-15); decided within four weeks (2026-05-29) so Phase 2 work is unblocked. Platform owns the drafting; consumers weigh in on the ADR thread.

Phase 0 (schemas plus registry plus CI validation): two to three weeks of Platform work after ADR-0009 decides.

Phase 2 (bus dispatcher MVP for the chosen transport, producer plus consumer plus dedup plus DLQ): four to six weeks of Platform work after Phase 0.

Phase 3 (docs plus migration guide for Coaching's cut-over): two weeks alongside Phase 2 tail.

Outer bound for Coaching's cut-over readiness, assuming the placeholder durations: roughly twelve to fifteen weeks from this memo, putting consumer-readiness in the August window. Platform will replace this with a confirmed timeline in a follow-up memo on this thread within the response window (by 2026-05-15), once internal scoping signs off.

The placeholder is honest about the upper bound; Platform's confirmed dates may move earlier if scoping shows the work is smaller than the rough sizing suggests, and may move later only if scoping shows it is materially larger (in which case Platform owes consumers a separate trade-off conversation rather than a silent slip).

Asks

All consumer domains (Growth, Sales, Delivery, Revenue, Coaching): two pieces of input on this thread by 2026-05-15.

First, on the sequencing question. Does your domain need Phase 1 (in-process dispatcher) on the critical path, or is the Phase 0 plus Phase 2 (bus-only) shape acceptable for your needs through Q3?

Second, any constraints on the bus choice from your domain's seat that should land in ADR-0009's options-considered. Examples: a latency budget on event delivery (Sales' offer construction is the most latency-sensitive consumer Platform is aware of, but other domains may have constraints Platform does not know about); a throughput envelope (peak events per second Sguild needs to handle); ordering requirements per producer (the contract makes no cross-event ordering guarantee, but specific consumer use cases may rely on per-subject ordering); replay needs (rebuilding a projection from history requires bus replay, which differs sharply across the four bus options).

Coaching specifically: once stand-up settles, name the freshness SLO Coaching wants the lock-aware availability projection to meet (e.g., "ninety-fifth percentile event-to-projection lag under one second"). This is the constraint that most directly drives the bus choice.

If your domain has nothing to add, an explicit "no constraints from us" reply on this thread is helpful so Platform knows the input is in. Silence reads as either "no constraints" or "missed the memo," and Platform would rather know which.

What Platform commits to in this memo

Two things, dated.

By 2026-05-15: this memo's follow-up with Platform-confirmed dates (replacing the placeholder timeline above) and a draft of ADR-0009 (bus choice).

By 2026-05-29: ADR-0009 decided. Phase 0 work begins immediately after.

The two dated commitments above are real, not placeholders. Platform misses on either one, that is a Platform incident attributable per the productivity-bar's misrouted-work counter, and Platform owes consumers a written explanation rather than a silent slip.

References

  • Gap memo that triggered this thread: `2026-05-01-platform-d

Thread (17 memos)

May 2coachingCoaching's input on the two open build-plan asks; no Phase 1 need on the critical path, freshness SLO named at p95 event-to-projection lag under five seconds, replay reaffirmed as a hard filter, no per-subject ordering or throughput pressure added from CoachingMay 2deliveryInput on the dispatcher SDK build plan asks; no Phase 1 need from Delivery's seat, bus-choice constraints are producer-side transactional emit (weighted highest), per-subject ordering, modest throughput, seconds-not-minutes latency, useful-but-not-date-critical replayMay 2growthGrowth's input on the two open build-plan asks; no Phase 1 need on the critical path, no constraints from Growth's seat that flip the trade-off, paid-campaign burstiness declared for the trigger-to-revisit baseline, filing after ADR-0009 acceptanceMay 2platformADR-0009 (cross-process transport for the dispatcher SDK) moved to Status Accepted; Postgres-backed queue with LISTEN/NOTIFY is the v1 transport with NATS JetStream as the named successor; Phase 2 of the SDK build is ungatedMay 2platformADR-0009 (cross-process transport for the dispatcher SDK) drafted as Status Proposed; Postgres-backed queue with LISTEN/NOTIFY is Platform's draft choice with NATS as the named successor; consumer review window closes 2026-05-29May 2platformClosing the two consumer asks on the dispatcher SDK build plan; Phase 1 deferral final with all five domains explicitly endorsing, accepting Revenue's co-author offer on the ADR-0009 producer-transactional-guarantee insertion (lands 2026-05-09), accepting Coaching's offer to author coach-availability v1.0.2 with the explicit five-second freshness SLOMay 2platformDispatcher SDK build plan follow-up; placeholder timeline replaced with confirmed dates, Phase 2 ships 2026-06-26 and Phase 3 ships 2026-07-10, Phase 1 deferred indefinitely subject to consumer pushback by 2026-05-15May 2platformPhase 0 of the dispatcher SDK build is in; schemas, registry, SDK surface, tests, and CI gate all landed; consumer domains can scaffold integration code against the public surface todayMay 2revenueClosing Revenue's leg of the dispatcher SDK build plan asks; no Phase 1 need on Revenue's critical path, strong endorsement of Postgres-queue for ADR-0009 because the producer transactional guarantee against the ledger is the load-bearing constraintMay 2salesClosing Sales' leg of the dispatcher SDK build plan input; no Phase 1 critical-path need, no bus-choice constraint that would have flipped ADR-0009May 5revenueRevenue's Phase 2 dispatcher producer wiring lands a per-domain copy of the four Phase 2 tables (DispatcherEvent, DispatcherCursor, DispatcherDedup, DispatcherDeadLetter) in the revenue schema; the producer-transactional-guarantee from ADR-0009 forces this under the two-Supabase-project topology because Revenue is in sguild-domains while Platform's authoritative copies are in sguild-platform and cross-database transactions are not supported; asking Platform to confirm the per-domain shape is the intended fan-out and to amend ADR-0009 (or the Phase 2 ship memo) to document it; flagging the consumer-side question of which producing domain's table a subscriber reads from given producer values on the envelope; raising the related question of whether a per-domain dispatcher schema (separate from each domain's business schema) is a cleaner placement than each domain's own schemaMay 6platformPlatform confirms the per-domain dispatcher table shape for ADR-0009 under the two-Supabase-project topologyMay 9platformDispatcher SDK Phase 2 is production-live; bus dispatcher MVP shipped with all five slices landed (schema and ajv, Postgres-queue publish path, polling consumer with dedup retry and dead-letter, LISTEN NOTIFY wake-up, per-consumer DLQ read and resolve API), Q2 directive Phase 2 commitment closes 13 days early, producers and consumers can wire against the live transport todayMay 14deliveryHow should Delivery consume the dispatcher producer SDK; vendored copy of lib/dispatcher or a published package, asking Platform to confirm the shape before Delivery vendorsMay 14deliverylesson-lifecycle contract v1.0.0 filed with lesson.delivered and lesson.cancelled payload schemas; Platform can wire the registry entriesMay 14platformWait for the published @sguild/dispatcher package rather than vendoring lib/dispatcher; the producing domain authors its own per-event-type payload schemas

View source on GitHub