Summary
Sprout uses the Nostr wire protocol (NIP-01) and extends it with enterprise features: NIP-29 relay-based groups, workflows, canvases, forums, agent harness, and audit logging. However, standard Nostr clients (Damus, Amethyst, Coracle) cannot function normally because Sprout rejects most standard event kinds via a hard allowlist in required_scope_for_kind().
PRs #245, #246, and #249 begin addressing this by adding kind:1 (text notes), kind:3 (follow lists), kind:30023 (long-form articles), NIP-16 replaceable event routing, and NIP-33 parameterized replaceable event support. This issue tracks the remaining work to make Sprout a standard-compliant Nostr relay that also has its enterprise/agent collaboration extensions.
The goal: Sprout should be a superset — a good Nostr relay that standard clients can connect to, plus all the Slack-replacement and agent features that make it unique.
What standard clients need
Research into Damus, Amethyst, and Coracle source code shows these clients:
- On login, fetch kinds 0, 3, 10002, 10050, 10000, 10006, 10009, 10013, 30315 (profiles, follows, relay lists, DM relay lists, mute lists, blocked relays, group lists, interests, user status)
- For home feeds, query with multi-author filters (up to 500 authors per batch) for kinds 1, 6, 7, 30023
- For threads/replies, query with
#e tag filters referencing parent events
- For zap counts, query kind:9735 with
#e filters referencing the zapped event
- For DMs, use NIP-17 gift wraps (kind:1059, already supported) plus kind:13 seals
- All three clients fully support NIP-42 authentication
Remaining work (two PRs)
PR 4: Accept standard social kinds
Open the kind allowlist for the standard kinds that clients publish and query.
Regular kinds:
- kind:6 (NIP-18 reposts) — in every client's home feed filter
- kind:16 (NIP-18 generic reposts)
- kind:9734 / kind:9735 (NIP-57 zap request / zap receipt) — relay role is passive (just store events; the LNURL server handles Lightning)
- kind:13 (NIP-59 seal) — completes the NIP-17 DM flow alongside kind:1059 gift wraps. Must be
#p-gated on REQ to prevent metadata leakage (same treatment as kind:1059).
NIP-16 replaceable range (10000–19999) — open the entire range:
PR #245 already routes is_replaceable() kinds through atomic replace_addressable_event() with stale-write protection. The allowlist is the only remaining gate. Key kinds unlocked:
- kind:10000 (NIP-51 mute list)
- kind:10002 (NIP-65 relay list metadata — critical for Amethyst's outbox model)
- kind:10050 (NIP-17 DM relay list)
- kind:10009 (NIP-51 group list)
- All other NIP-51 list kinds
NIP-33 parameterized replaceable range (30000–39999) — open the range with NIP-29 carve-out:
PR #246 already routes is_parameterized_replaceable() kinds through atomic replace_parameterized_event(). Open the range with an explicit carve-out for NIP-29 group-state kinds (39000–39003) which remain channel-scoped. Key kinds unlocked:
- kind:30315 (NIP-38 user status — Amethyst fetches on login)
- kind:30078 (NIP-78 app-specific data)
Estimated effort: Small (~50 LOC, 1–2 days). The storage infrastructure is already built by PRs #245 and #246.
PR 5: REQ query pushdown (ids, multi-author, #e)
Fix silent data loss in standard client fetch patterns. The current REQ handler pushes only kinds, single author, since/until, #p, and #d into SQL. Everything else is post-filtered after a capped LIMIT query, which means events that exist in the database are silently not returned.
ids pushdown:
Clients fetch specific events by ID for thread root resolution, quote resolution, and profile lookups. The idx_events_id index exists but is never used by the query builder. Push ids into SQL as AND id IN (...).
Multi-author pushdown:
Clients send follow-feed queries with up to 500 authors. Currently only single-author is pushed to SQL — multi-author falls through to post-filter, consuming the entire LIMIT window with events from non-followed authors. Push multi-author into SQL as AND pubkey IN (...). For >500 authors, chunk into multiple SQL queries and merge results (no lossy fallback).
#e tag pushdown:
Clients fetch replies and zap counts via #e tag filters. Currently post-filtered — under volume, replies to older events and zap receipts are invisible. Add an event_references denormalized table (same pattern as existing event_mentions for #p tags) with a join-based query path.
Estimated effort: Medium (schema change + ~330 LOC, 1–2 weeks).
After these two PRs
Sprout becomes a standard-compliant authenticated Nostr relay with meaningful client interoperability: core social kinds, NIP-16 replaceables, NIP-33 parameterized replaceables, NIP-17 private DMs, NIP-29 relay-based groups, NIP-57 zap event storage, and correct REQ behavior for ids, multi-author feeds, and #e thread/zap lookups — alongside all existing enterprise features (workflows, canvases, forums, agent harness, audit logging, full-text search).
References
Summary
Sprout uses the Nostr wire protocol (NIP-01) and extends it with enterprise features: NIP-29 relay-based groups, workflows, canvases, forums, agent harness, and audit logging. However, standard Nostr clients (Damus, Amethyst, Coracle) cannot function normally because Sprout rejects most standard event kinds via a hard allowlist in
required_scope_for_kind().PRs #245, #246, and #249 begin addressing this by adding kind:1 (text notes), kind:3 (follow lists), kind:30023 (long-form articles), NIP-16 replaceable event routing, and NIP-33 parameterized replaceable event support. This issue tracks the remaining work to make Sprout a standard-compliant Nostr relay that also has its enterprise/agent collaboration extensions.
The goal: Sprout should be a superset — a good Nostr relay that standard clients can connect to, plus all the Slack-replacement and agent features that make it unique.
What standard clients need
Research into Damus, Amethyst, and Coracle source code shows these clients:
#etag filters referencing parent events#efilters referencing the zapped eventRemaining work (two PRs)
PR 4: Accept standard social kinds
Open the kind allowlist for the standard kinds that clients publish and query.
Regular kinds:
#p-gated on REQ to prevent metadata leakage (same treatment as kind:1059).NIP-16 replaceable range (10000–19999) — open the entire range:
PR #245 already routes
is_replaceable()kinds through atomicreplace_addressable_event()with stale-write protection. The allowlist is the only remaining gate. Key kinds unlocked:NIP-33 parameterized replaceable range (30000–39999) — open the range with NIP-29 carve-out:
PR #246 already routes
is_parameterized_replaceable()kinds through atomicreplace_parameterized_event(). Open the range with an explicit carve-out for NIP-29 group-state kinds (39000–39003) which remain channel-scoped. Key kinds unlocked:Estimated effort: Small (~50 LOC, 1–2 days). The storage infrastructure is already built by PRs #245 and #246.
PR 5: REQ query pushdown (ids, multi-author, #e)
Fix silent data loss in standard client fetch patterns. The current REQ handler pushes only kinds, single author, since/until,
#p, and#dinto SQL. Everything else is post-filtered after a cappedLIMITquery, which means events that exist in the database are silently not returned.idspushdown:Clients fetch specific events by ID for thread root resolution, quote resolution, and profile lookups. The
idx_events_idindex exists but is never used by the query builder. Pushidsinto SQL asAND id IN (...).Multi-author pushdown:
Clients send follow-feed queries with up to 500 authors. Currently only single-author is pushed to SQL — multi-author falls through to post-filter, consuming the entire LIMIT window with events from non-followed authors. Push multi-author into SQL as
AND pubkey IN (...). For >500 authors, chunk into multiple SQL queries and merge results (no lossy fallback).#etag pushdown:Clients fetch replies and zap counts via
#etag filters. Currently post-filtered — under volume, replies to older events and zap receipts are invisible. Add anevent_referencesdenormalized table (same pattern as existingevent_mentionsfor#ptags) with a join-based query path.Estimated effort: Medium (schema change + ~330 LOC, 1–2 weeks).
After these two PRs
Sprout becomes a standard-compliant authenticated Nostr relay with meaningful client interoperability: core social kinds, NIP-16 replaceables, NIP-33 parameterized replaceables, NIP-17 private DMs, NIP-29 relay-based groups, NIP-57 zap event storage, and correct REQ behavior for
ids, multi-author feeds, and#ethread/zap lookups — alongside all existing enterprise features (workflows, canvases, forums, agent harness, audit logging, full-text search).References
PLANS/SPROUT_PR4_STANDARD_SOCIAL_KINDS.md,PLANS/SPROUT_PR5_REQ_QUERY_PUSHDOWN.md