Sovereign Sprout (#403)

This commit is contained in:
tlongwell-block
2026-04-28 11:09:39 -04:00
committed by GitHub
parent d1c84a621c
commit 0df883c490
3 changed files with 481 additions and 1 deletions
+13 -1
View File
@@ -1,4 +1,4 @@
# 🌱 Sprout — A Unified Communications Platform
# 🌱 Sprout — The relay is the workspace
> An engineer is debugging a production incident at 2am. They type in the incident channel: "What happened last time we saw this error?"
>
@@ -6,6 +6,8 @@
The platform made it possible. The agent made it happen. Sprout is the pipe — event store, search index, subscriptions, delivery — not the brain. Humans and agents bring the intelligence. Sprout gives them a shared space to use it.
One relay is your entire workspace. Work, conversation, agents, automation, artifacts, docs — one domain, one identity system, one search index. `myproject.com` in a browser shows your repos. `git clone repoa.myproject.com` works. Open the Sprout app and you're in the channels where the work happens. No GitHub. No Discord. No stitching five services together. The project lives in one place, and that place is yours. See [VISION_SOVEREIGN.md](VISION_SOVEREIGN.md) for the full picture.
---
## Surfaces
@@ -128,6 +130,16 @@ Beyond chat: channels are workspaces.
---
## Code
The relay hosts git repos. Smart HTTP — standard `git clone`, `git push`, nothing special. Your npub signs pushes. Same domain, same auth, same identity as everything else on the relay.
Branches are channels. Create a feature branch, Sprout creates a channel — CI results, review comments, and the merge decision all live there. When the branch merges, the channel archives into a permanent record of why that code exists.
See [VISION_PROJECTS.md](VISION_PROJECTS.md) for the full forge vision: the project model, the merge flow, branch protections, and how agents participate as contributors.
---
## Agent CLI
`sprout-cli` is a 48-command agent-first CLI covering the full MCP surface. JSON-only stdout, structured errors on stderr, three-tier auth (API token → auto-mint keypair → dev pubkey). Agents can script the entire platform without a GUI.
+233
View File
@@ -0,0 +1,233 @@
# 🌿 Sprout Projects — A Nostr-Native Forge
> Someone pushes a fix. Sprout creates a channel for the branch. The CI agent picks up the push, runs the tests, posts results back to the channel. A co-maintainer reviews the diff inline, approves it — a signed event, cryptographic proof. Merge. The workflow runs the integration. The channel archives into a permanent record of why that code exists.
>
> Bug report to merged patch. One place. One search index. One identity system. The branch channel was the pull request, the CI dashboard, and the discussion thread.
This document is the software-forge slice of the broader Sprout platform. [VISION.md](VISION.md) covers the platform. [VISION_SOVEREIGN.md](VISION_SOVEREIGN.md) covers the sovereign relay story — one domain, one relay, one project. This doc zooms in on what it looks like when that relay hosts code.
---
## The Project Model
A project lives on the relay. `myproject.com` in a browser shows the project home. Click a repo and you're at `repoa.myproject.com` — README rendered, file tree navigable, code syntax-highlighted, clone URL at the top. The same URL serves HTML to a browser and git protocol to `git clone`. Content negotiation. One URL, two audiences.
Git transport is standard Smart HTTP — `git clone`, `git push`, nothing special. Your npub signs pushes. Same domain, same auth, same identity as everything else on the relay.
The portable representation is a NIP-34 repo announcement (kind:30617) — standard metadata that any NIP-34 client can discover and render. Sprout extends it with `sprout-` prefixed tags for channel binding and visibility:
```json
{
"kind": 30617,
"tags": [
["d", "sprout"],
["name", "sprout"],
["clone", "https://repoa.myproject.com"],
["relays", "wss://myproject.com"],
["maintainers", "<co-maintainer-npub>"],
["sprout-channel", "<channel-uuid>"],
["sprout-visibility", "listed"],
["sprout-protect", "main", "push-allowed", "<alice-npub>", "<bob-npub>"],
["sprout-protect", "main", "require-approval", "2"],
["sprout-protect", "main", "no-force-push"]
]
}
```
Branch protections live in the same event — `sprout-protect` tags. The relay enforces them at the git transport layer. Only npubs listed in `push-allowed` can push to protected branches. Force pushes are blocked. Merges require the specified number of signed approval events (kind:46011) before the relay accepts the push.
Agents inherit access from their owner via [NIP-OA](NIP-OA.md). The relay checks: does the push carry a valid NIP-OA auth tag, and is the owner pubkey in that tag listed in `push-allowed`? If yes, the push is accepted — the agent's own pubkey doesn't need to be in the list. Add a maintainer, and all their authorized agents can push. Remove the maintainer, and all their agents lose access instantly. Agents without NIP-OA attestation are treated as their own identity and must be listed explicitly.
Standard NIP-34 clients see a normal repo. gitworkshop.dev renders it. ngit-cli works with it. Sprout clients read the `sprout-` tags and wire up the channel and project UI. One event, two audiences, zero custom kinds.
NIP-34 is the metadata and discovery layer. Git remains the transport. The transport is boring. The metadata is portable.
---
## Branches as Channels
A feature branch is a conversation.
When you create a branch, Sprout creates a channel. The branch's patches, review comments, CI results, and merge decision all live in that channel. When the branch merges, the channel archives. The conversation becomes the permanent record of why that code exists.
```
#feat-auth-fix
├── 🧑 alice: "Starting OAuth2 PKCE implementation"
├── 🤖 ci-agent: "Build triggered — commit a1b2c3d"
├── 🤖 ci-agent: "✅ All 47 tests pass (12.3s)"
├── 📎 kind:1617 patch — src/auth/pkce.rs (+120 lines)
├── 🧑 bob: "One nit on error handling line 45"
├── 📎 kind:1617 patch v2 — addressed review
├── 🤖 review-agent: "LGTM — error variants match trait spec"
├── ✅ bob: Approval event (kind:46011)
├── 🔀 Merged to main — kind:1631
└── 📦 Channel archived
```
No tab-switching between issue tracker, CI dashboard, chat, and code review. The channel IS the pull request, the CI dashboard, and the discussion thread. One stream. One search index.
---
## The Merge Flow
Push to merge, fully traced. Every step is a signed event.
```
Push CI Review Merge
│ │ │ │
│ kind:30618 │ │ │
│ (ref update) │ │
│───────────►│ │ │
│ │ Workflow │ │
│ │ triggers │ │
│ │ │ │
│ │ Build ✅ │ │
│ │ Test ✅ │ │
│ │ Lint ✅ │ │
│ │ │ │
│ │ kind:1630 ────►│ │
│ │ (CI passed) │ │
│ │ │ Review in │
│ │ │ branch channel│
│ │ │ │
│ │ │ kind:46011 │
│ │ │ (approved) ──►│
│ │ │ │
│ │ │ │ Merge to main
│ │ │ │ kind:1631
│ │ │ │
│ │ │ │ Channel archives
```
The approval event is signed by the maintainer's npub. The merge status references the approval. The audit log chains them together. Cryptographic proof of who approved what.
---
## The Web of Trust
Every contributor — human or agent — has a verifiable identity and a queryable contribution history across every project on the network.
A new contributor submits a patch. Before you read the code:
1. **Query their npub** — patches submitted, patches merged, projects contributed to.
2. **Check your trust graph** — have maintainers you trust vouched for this person? Signed approval events are public and queryable.
3. **Assess risk** — fresh npub with no history gets scrutiny. An npub with 50 merged patches across projects you respect gets fast-tracked.
This works because identity is cryptographic and portable. Your npub, your contribution history, and your trust relationships travel with you. No platform owns your reputation.
**For agents**: an agent with a persistent npub and verifiable contribution history is fundamentally different from an anonymous generator. The agent's reputation is on the line with every contribution, across every project it touches. See [NIP-OA](NIP-OA.md) for the owner attestation mechanism that proves which human authorized which agent — independent keys, contained blast radius.
---
## CI and Workflows
Workflows orchestrate. Agents perform the compute. The relay is the message bus, not the build server.
A push to a branch channel triggers the CI workflow. The workflow engine coordinates the steps — build, test, lint. Agents run the actual jobs on their own infrastructure: your server, a cloud function, a laptop. Results post back to the branch channel alongside the conversation.
Workflows live in the repo (`.sprout/workflows/`) or are defined at the project level and inherited by every branch channel automatically — no per-branch configuration, no copy-pasting YAML.
```yaml
name: CI
trigger:
on: diff_posted
steps:
- id: build
action: call_webhook
url: "https://ci.internal/build"
body: '{"commit": "{{trigger.commit}}"}'
- id: test
action: call_webhook
url: "https://ci.internal/test"
if: "steps.build.output.status == 'success'"
- id: gate
action: request_approval
message: "CI passed. Approve merge?"
if: "steps.test.output.status == 'success'"
```
Every step traced. Every trace a signed event. Change the project CI once and every branch gets it.
---
## Issues, Docs, Releases
### Issues → Forum + NIP-34
Bug reports are NIP-34 kind:1621 events, rendered through Sprout's forum surface. Threaded comments use NIP-22 kind:1111. Labels, assignees, milestones are nostr tags. Design discussions and RFCs use the forum's long-form async surface.
NIP-34 clients can discover and interact with issues. Sprout's forum gives them a home with threading, search, and agent triage.
### Docs → Canvases
Living documents, collaboratively editable by humans and agents via MCP tools. Not static HTML deployed to a CDN — documents that update when the code changes, because the doc writer agent watches ref updates and proposes edits.
### Releases → Agent + Workflow
An agent in `#releases` watches `main`. When a release is needed — triggered by a workflow or by a human posting "ship it" — it assembles the changelog from every merged patch since the last tag, posts a draft. The maintainer approves. The workflow builds artifacts, pushes to content-addressed storage (Blossom/S3), and publishes. Logged, signed, traceable.
---
## Agents as Contributors
Agents are project members with npubs, contribution histories, and reputations. The protocol treats them identically to humans. Visual badges distinguish them in the UI.
| | Human | Agent |
|---|---|---|
| Identity | secp256k1 keypair | secp256k1 keypair |
| Handle | `alice@sprout.dev` | `triage-bot@sprout.dev` |
| Events | Signed with npub | Signed with npub |
| History | On the relay | On the relay |
| Reputation | Earned by contributions | Earned by contributions |
| Role | Watches | Does |
|------|---------|------|
| **Triage** | Issues (kind:1621) | Labels, assigns, detects duplicates, pre-screens |
| **Review** | Patches (kind:1617) | First-pass code review, style checks, dependency audit |
| **Docs** | Ref updates (kind:30618) | Keeps docs in sync after merges |
| **Merge coordinator** | CI results | Runs the merge train, requests human sign-off |
| **Coding agent** | Jobs (kind:43001) | Implements tasks, submits patches for review |
---
## Nostr-Native
Standard kinds as substrate. Custom kinds only where genuinely novel.
| Layer | Standard NIP Kinds | Sprout Custom | Rationale |
|-------|-------------------|---------------|-----------|
| **Git state** | 30617, 30618, 1617, 1618, 1621, 1630-1633 (NIP-34) | — | Interop with ngit, gitworkshop.dev |
| **Comments** | 1111 (NIP-22) | — | Threaded replies everywhere |
| **Channels** | 9000-9022, 39000-39003 (NIP-29) | — | Project workspaces |
| **HTTP auth** | 27235 (NIP-98) | — | Git push authentication |
| **Agent identity** | 0 (NIP-01 profile) | — | Agents are npubs |
| **Artifacts** | 1063 (NIP-94) | — | Build outputs on Blossom/S3 |
| **Workflows** | — | 46001-46012 | No NIP equivalent |
| **Job dispatch** | — | 43001-43006 | Delegation trees |
| **Project binding** | 30617 (NIP-34) | `sprout-` tags | Channel, visibility |
| **Audit** | — | 48001 | Hash-chain tamper-evident log |
If Sprout disappears tomorrow, your repos still work on gitworkshop.dev, your patches still work with ngit-cli, your identities still work on any nostr client. Centralized deployment, decentralized protocol.
---
## Status
| Capability | Status |
|---|---|
| Channels, forums, DMs, canvases | ✅ Ships today |
| Workflow engine (triggers, traces, conditional logic) | ✅ Ships today |
| 44-tool MCP server + ACP agent harness | ✅ Ships today |
| Blossom media storage (SHA-256, S3) | ✅ Ships today |
| Approval gates | 🚧 Infrastructure exists; executor wiring in progress |
| Project binding (kind:30617 + `sprout-` tags) | 📋 Designed |
| Git hosting (smart HTTP + NIP-34) | 📋 Designed |
| Merge coordinator | 📋 Designed |
| NIP-34 issues (kind:1621) | 📋 Designed |
| Web-of-trust reputation | 📋 Designed |
The collaboration platform is built. The forge is the work ahead — wiring git hosting, the merge train, and the reputation system into the surfaces that already exist. See [VISION.md](VISION.md) for the platform and [VISION_SOVEREIGN.md](VISION_SOVEREIGN.md) for the sovereign relay story.
---
*Sprout 🌿 — the forge where identity is the foundation.*
+235
View File
@@ -0,0 +1,235 @@
# Sprout — Your Project, Your Domain
`myproject.com` is your workspace. Not a GitHub org page that happens to have your
name on it. Not a Discord server that Discord could delete tomorrow. Your domain.
Your relay. One thing.
The relay is the workspace. Code lives there. Conversation lives there. Agents connect
to it. Automation flows through it. Artifacts publish from it. You type the URL, you
see the workspace. That's the whole idea.
Nothing here is novel on its own. Git hosting is solved. Chat is solved. Agents are
solved. Nostr identity is solved. The insight is putting them all behind one domain
with one identity system. Each piece is boring. The combination is the thing.
The examples here are about software because that's the sharpest case — code,
branches, CI, releases. But the model works for anything a group builds together.
Research, hardware, writing, governance. The relay doesn't care what's in the
channels or the repos. It stores signed events, routes messages, runs workflows,
and hosts files. What you build with that is up to you.
---
## What You See
You type `myproject.com`. You see a list of repos — like a GitHub org page, but it's
yours, running on your server, with your data. You click one. You're at
`repoa.myproject.com`. The README renders. The file tree is there. Click into `src/`
and the code is syntax-highlighted. The clone URL sits at the top:
```
git clone repoa.myproject.com
```
Next to it, a "Connect on Sprout" button.
No separate website. No static site generator, no deploy step, no "pages" concept.
The relay serves rendered HTML to browsers and responds to `git clone` at the same
URL — content negotiation, one domain, two audiences. The repo *is* the website.
A browser gets a rendered page. A git client gets a git server. Same URL.
You clone it. It works. No special tooling. No new protocol. Git is git. You push
the same way. Agents clone and push the same way. The repos are repos.
You hit "Connect on Sprout" and the app opens. You're in `#general`. The team is
talking. There's a forum with open issues and design discussions. There's a canvas
with the architecture doc — updated last week by the docs agent after a refactor
landed. Three feature branches are active, each one a channel. You're in.
Everything is here.
A new contributor finds the project the same way. They browse the code, read the
README, click into the forum and see the open issues. They read a design doc in the
canvas. They understand the project before they write a line of code. Then they hit
"Connect on Sprout" and they're in the community. No separate onboarding flow. No
"read our wiki at wiki.myproject.com and our forum at forum.myproject.com and our
chat at discord.gg/..." Everything is at `myproject.com`. That's where everything
is.
Not every project needs to run its own relay. Most people will just join one that
someone else runs — the way most people use GitHub instead of running Gitea. And
you can use Sprout as a collaboration layer on top of GitHub if that's what makes
sense — work in Sprout channels, push releases to your public repo. The sovereign
setup is the full version. But the tools work at every level of commitment.
---
## How Work Happens
A contributor files a bug. It lands in the forum. The triage agent — sitting in the
channel like any other member — labels it, finds a duplicate from six weeks ago,
links the relevant docs. This happens before a human reads it. The contributor gets
a response in seconds. The maintainer sees a pre-processed report, not a raw inbox.
They read the triage summary, confirm it's real, assign it. Thirty seconds of their
time instead of five minutes.
Someone picks up the bug. They create a branch. Sprout creates a channel:
`#feat-auth-fix`. They work there. They push commits. The CI agent picks up the
work — it's watching for pushes, it's just a member with compute — runs the tests,
posts results back to the channel. Green. The patch lands in the channel as a
reviewable diff. A co-maintainer reviews it inline, leaves a comment on line 45,
approves it. The approval is a signed event — cryptographic proof of who said yes
and when.
Merge. The workflow runs: sequential integration, tests after each merge, release
event published. The channel archives. The conversation is now the permanent record
of why that code exists. Not a chat thread that scrolled away. Not a PR that might
get deleted. The event log, on your relay, in your database. Someone reading that
code in two years can pull up the channel and see exactly what was discussed, what
was tried, what was rejected, and why the final approach won.
The whole flow — bug report to merged patch — happened in one place. No
tab-switching between issue tracker, CI dashboard, chat, and code review. One
stream. One URL. One search index. You search "auth refresh" and you get the bug
report, the channel discussion, the patches, the CI results, and the design doc
that motivated the whole thing — all in one query.
**The release flow** is where it clicks. An agent in `#releases` watches `main`.
When a release is needed — triggered by a workflow, or by a human posting "ship
it" — it assembles the changelog from every merged patch since the last tag,
formats it, posts a draft. The maintainer edits two lines, approves. The workflow
kicks off: builds artifacts, pushes to content-addressed storage, deploys wherever
the project deploys. The result posts back to `#releases`. Done. The whole thing
is logged, signed, and traceable.
The release agent isn't special infrastructure. It's an npub with compute that
happens to be fast. Same for the CI agent, the triage agent, the docs agent. They
sit in channels. They watch for things. They clone repos and push code. They get
pinged by workflows or by humans. A CI agent picks up work from a branch channel,
runs tests, posts results back. A docs agent watches for merges and proposes doc
updates. A release agent watches `main` and ships. They're not a separate system.
They're contributors.
Workflows are the connective tissue. They fire when messages match filters, when
conditions are met, when a timer ticks. They don't execute work — they coordinate
it. The relay is the message bus. Agents are the workers. They run wherever they
run: your server, a cloud function, a laptop. The workflow just knows who to ping
and when. You define a workflow once at the project level and every branch channel
inherits it automatically — no per-branch configuration, no copy-pasting YAML.
---
## The Social Layer
Not everything belongs in the repo. Not everything belongs in a channel. Some
things are announcements. Some things are essays. Sprout has surfaces for both,
and they live on the same relay as the code.
Short notes — project announcements, "we just shipped X," the kind of thing you'd
post on social media — are just notes. They live on the relay, they're public, they
propagate across the nostr network. Someone following your project sees them in
their feed. No separate social media account to maintain. No "follow us on Twitter"
link pointing somewhere else. Your relay is your social presence. When you ship a
release, you post a note. It goes out to everyone following the project's npub.
That's it.
Long-form posts are for the thinking that doesn't fit anywhere else. The design
decision that deserves more than a channel message. The research write-up explaining
why you chose a particular approach. The post-mortem on that outage. The RFC that
shaped the architecture. These aren't commit messages and they're not channel
threads — they're documents that deserve to exist on their own, searchable and
permanent, alongside the code that implements them. Years later, someone reading
the code can find the reasoning. It's on the same relay. Same search. Same
identity. The blog post and the code it describes are in the same place.
Channels are the working conversation — fast, ephemeral-feeling, but actually
permanent. The repo is the code and docs that belong with the code. Notes are the
public face. Long-form is the thinking. Each surface has its job. Nothing bleeds
into the wrong place.
The result is that your project has a complete public presence at one domain. The
code, the conversation, the announcements, the deep thinking — all there, all
searchable, all yours. A new contributor can browse the repo, read the design docs,
skim the recent announcements, and join the community without leaving `myproject.com`.
A researcher studying the project's evolution can trace decisions from the long-form
posts through the channel discussions to the merged patches. It's all connected
because it's all on the same relay.
---
## Identity
Your npub is your identity everywhere. One keypair. No accounts to create on each
new platform. No "sign in with GitHub" that means GitHub owns your identity. Your
keys, your identity.
Reputation follows you across projects. When you contribute to three projects and
get patches merged, that history is on the relay — signed, verifiable, queryable.
When you show up at a fourth project, your track record shows up with you. A
maintainer can see: merged patches, vouches from people they know, a history of
good work. That's not a number a platform assigned you. It's your actual history,
cryptographically attested. You can't fake it. You can't buy it. You earn it by
doing the work.
Web-of-trust emerges naturally. Who vouches for whom. Who merges whose patches.
Who has a track record. No special reputation system to design and build — it's
the natural consequence of cryptographic identity plus public contribution history.
A maintainer you trust vouches for a contributor you've never heard of. That vouch
is a signed event. You can see it. You can weight it. The trust graph grows
organically as people work together, and it's queryable across the whole network.
This matters especially for agents. An agent with a persistent keypair and a
verifiable contribution history is fundamentally different from an anonymous
generator with no history. The agent has skin in the game. Its reputation is on
the line with every contribution, across every project it touches. Bad work
degrades its standing everywhere, not just in one repo. That's the right incentive
structure, and it falls out of the identity model for free.
You don't have to build a separate detection system for low-quality contributions.
You look at the npub's history. A fresh keypair with no history and no vouches gets
scrutiny. An npub with 50 merged patches across projects you respect gets
fast-tracked. The signal is in the history. The web-of-trust does the filtering.
No platform owns your identity. No platform can suspend your account. Your keypair
is yours. If you move to a different relay, your identity comes with you. Your
contribution history is portable. The work you did is yours. If `myproject.com`
goes down, your npub still exists. Your history still exists. You stand up a new
relay, point your domain at it, and you're back. The project continues.
---
## What You Give Up
You run infrastructure. A server, a domain, a relay. That's not hard — a modest
VPS handles a small project comfortably — but it's not zero. Someone has to keep
it running. Someone has to handle backups. Someone has to deal with the 3 AM alert
when the disk fills up. Managed hosting can take that off your plate — same
sovereignty, someone else handles the ops — but it's a cost either way, in time
or money. Worth knowing before you start.
Key management is harder than "sign in with Google." Losing your private key means
losing your identity. There's no "forgot password" flow, no support ticket to file,
no account recovery. Hardware keys help. Good practices help. But it's a real
tradeoff and you should go in knowing it. The same property that makes your identity
uncensorable makes it unrecoverable if you lose the key.
The ecosystem is young. The tooling is good and getting better, but it's not a
decade of polish. Some things will feel rough. Some integrations won't exist yet.
You're early, and early means occasionally hitting edges that haven't been smoothed.
Your contributors are early too. Most developers don't have a nostr keypair.
Onboarding friction is real — not insurmountable, but real. The "Connect on Sprout"
button is easy. Explaining what a keypair is takes a sentence. But it's a sentence
you'll have to write, and some contributors won't bother. You'll lose some people
at the door who would have clicked "sign in with GitHub" without thinking. That's
a real cost, especially early in a project when you're trying to grow a contributor
base.
These are honest costs. They're worth it if you care about owning your project —
your code, your community, your data, your identity. They're not worth it if you
just want the path of least resistance. Know which one you are.
---
## The Point
One domain. One identity. Everything in one place — and it's yours.