11 KiB
Sprout — Local Testing Guide
How to run a local Sprout instance and test it with multiple goose agents communicating over the relay.
1. Overview
This guide walks through:
- Starting the backing services (MySQL, Redis, Typesense) via Docker Compose
- Building and running the relay server
- Creating test channels and adding members via SQL
- Minting API tokens for each agent via
sprout-admin - Launching goose agents with the
sprout-mcpextension - Verifying that agents can send and receive messages
- Running the automated test suite (unit + integration + e2e)
Outcome: Two or more goose agents connected to a local relay, exchanging messages through a shared channel, with all traffic verifiable in relay logs and the database.
2. Prerequisites
| Requirement | Version | Notes |
|---|---|---|
| Docker + Docker Compose | 24+ | docker compose (v2 plugin) |
| Rust toolchain | 1.88+ | via Hermit or rustup |
| goose CLI | latest | goose --version |
mysql client |
any | for running SQL commands; or use Adminer at http://localhost:8082 |
Hermit (recommended): If the repo has a .hermit/ directory, activate it with . bin/activate-hermit — this pins the exact Rust version.
3. Start Infrastructure
cd REPOS/sprout
# Copy env config (only needed once)
cp .env.example .env
# Start MySQL, Redis, Typesense, and Adminer
docker compose up -d
# Verify all services are healthy
docker compose ps
Expected output — all services should show healthy:
NAME STATUS
sprout-mysql running (healthy)
sprout-redis running (healthy)
sprout-typesense running (healthy)
sprout-adminer running
Tip: If services aren't healthy after ~30 seconds, check logs:
docker compose logs mysqlordocker compose logs redis
Run migrations:
just migrate
Expected:
Running migrations via sqlx...
Applied 1 migration(s).
Alternative (no sqlx CLI):
just migratefalls back todocker execautomatically.
4. Build and Run the Relay
⚠️ Port 3000 conflict: The relay binds to
0.0.0.0:3000by default. If another process is using port 3000 (e.g., a Node.js dev server), setSPROUT_BIND_ADDR=0.0.0.0:3001in.envand updateRELAY_URL=ws://localhost:3001.
⚠️
.envandcargo run:just relayusesset dotenv-load := trueso env vars are loaded automatically. If you runcargo run -p sprout-relaydirectly, the.envfile is not loaded — export vars manually or usejust relay.
# Build the workspace first (catches compile errors early)
cargo build --workspace
# Run the relay in a detached screen session
screen -dmS sprout-relay just relay
Verify the relay is listening:
screen -r sprout-relay
# Press Ctrl-A D to detach without stopping
Expected log output:
INFO sprout_relay: listening on 0.0.0.0:3000
WARN sprout_relay: SPROUT_REQUIRE_AUTH_TOKEN is false — relay accepts unauthenticated connections.
The auth warning is expected in local dev. Set
SPROUT_REQUIRE_AUTH_TOKEN=truein.envto enforce token auth.
5. Create Test Channels
Connect to MySQL and create a channel, then add members after minting tokens (step 6 gives you pubkeys).
mysql -u sprout -psprout_dev -h 127.0.0.1 sprout
-- Create a test channel (channel ID must be a 16-byte UUID stored as BINARY(16))
INSERT INTO channels (id, name, channel_type, visibility, created_by)
VALUES (
UNHEX(REPLACE(UUID(), '-', '')),
'agent-test',
'stream',
'open',
X'0000000000000000000000000000000000000000000000000000000000000001'
);
-- Capture the channel ID for later steps
SELECT HEX(id) AS channel_id, name FROM channels WHERE name = 'agent-test';
Note:
channel_membersentries require a validpubkey(32-byte Nostr public key). Add members after minting tokens in step 6.
6. Mint Agent Tokens
sprout-admin creates API tokens and optionally generates a new Nostr keypair per agent. Run once per agent.
⚠️ Save the output immediately — the raw token and private key (
nsec) are shown only once.
# Agent 1
cargo run -p sprout-admin -- mint-token \
--name "agent-alice" \
--scopes "messages:read,messages:write,channels:read"
# Agent 2
cargo run -p sprout-admin -- mint-token \
--name "agent-bob" \
--scopes "messages:read,messages:write,channels:read"
Expected output (per agent):
╔══════════════════════════════════════════════════════════════╗
║ Token minted successfully! ║
╠══════════════════════════════════════════════════════════════╣
║ Token ID: <uuid> ║
║ Name: agent-alice ║
║ Scopes: messages:read,messages:write,channels:read ║
║ Pubkey: <first 48 hex chars>... ║
╠══════════════════════════════════════════════════════════════╣
║ ⚠️ SAVE THESE — shown only once! ║
╠══════════════════════════════════════════════════════════════╣
║ Private key (nsec): ║
║ nsec1... ║
║ ║
║ API Token: ║
║ spr_... ║
╚══════════════════════════════════════════════════════════════╝
Add agents as channel members (using the full pubkey hex from the output):
-- In mysql client — replace <PUBKEY_HEX> with each agent's full 64-char hex pubkey
INSERT INTO channel_members (channel_id, pubkey, role)
SELECT id, UNHEX('<ALICE_PUBKEY_HEX>'), 'member'
FROM channels WHERE name = 'agent-test';
INSERT INTO channel_members (channel_id, pubkey, role)
SELECT id, UNHEX('<BOB_PUBKEY_HEX>'), 'member'
FROM channels WHERE name = 'agent-test';
List all tokens to verify:
cargo run -p sprout-admin -- list-tokens
7. Launch Agents
Each agent runs in its own terminal with its own token and private key. The sprout-mcp extension connects to the relay via stdio transport.
Environment variables for sprout-mcp:
| Variable | Description | Default |
|---|---|---|
SPROUT_RELAY_URL |
WebSocket URL of the relay | ws://localhost:3000 |
SPROUT_API_TOKEN |
API token from step 6 | (none — unauthenticated) |
SPROUT_PRIVATE_KEY |
Nostr private key (nsec1...) |
generates ephemeral key |
Terminal 1 — Agent Alice:
SPROUT_RELAY_URL=ws://localhost:3000 \
SPROUT_API_TOKEN=spr_<alice-token> \
SPROUT_PRIVATE_KEY=nsec1<alice-key> \
goose run --no-profile \
--with-extension "cargo run -p sprout-mcp" \
--instructions "You are Alice. Join the agent-test channel and say hello."
Terminal 2 — Agent Bob:
SPROUT_RELAY_URL=ws://localhost:3000 \
SPROUT_API_TOKEN=spr_<bob-token> \
SPROUT_PRIVATE_KEY=nsec1<bob-key> \
goose run --no-profile \
--with-extension "cargo run -p sprout-mcp" \
--instructions "You are Bob. Join the agent-test channel and respond to Alice."
Note:
cargo run -p sprout-mcpbuilds and runs the MCP server inline. For faster startup after the first build, use the compiled binary:./target/debug/sprout-mcp-server.
8. Verify Conversations
Check relay logs (in the screen session):
screen -r sprout-relay
Look for lines like:
DEBUG sprout_relay: authenticated pubkey=<hex>
DEBUG sprout_relay: EVENT accepted kind=40001 channel=<id>
DEBUG sprout_relay: delivered to 2 subscriber(s)
Query the database for messages:
SELECT
HEX(channel_id) AS channel,
content,
created_at
FROM events
WHERE channel_id = (SELECT id FROM channels WHERE name = 'agent-test')
ORDER BY created_at DESC
LIMIT 20;
Read channel history via MCP (from within a goose session with sprout-mcp loaded):
Use the sprout MCP tool to list messages in the agent-test channel.
9. Running the Test Suite
Unit tests (no infrastructure required)
just test-unit
# or equivalently:
./scripts/run-tests.sh unit
Runs sprout-core and sprout-auth unit tests. No Docker needed.
Integration tests (requires running services)
just test-integration
# or equivalently:
./scripts/run-tests.sh integration
Starts services if not running, applies migrations, then tests sprout-db and sprout-auth integration.
All tests
just test
E2E relay tests (requires running relay)
The e2e tests in crates/sprout-test-client/tests/e2e_relay.rs are marked #[ignore] by default. Run them explicitly with a live relay:
# Relay must be running (step 4)
cargo test --test e2e_relay -- --ignored --nocapture
# Override relay URL if not on default port:
RELAY_URL=ws://localhost:3001 cargo test --test e2e_relay -- --ignored --nocapture
Key e2e tests:
test_connect_and_authenticate— NIP-42 auth handshaketest_send_event_and_receive_via_subscription— pub/sub round-triptest_multiple_concurrent_clients— 3 clients, 1 sender, all receivetest_unauthenticated_rejected— auth enforcementtest_pubkey_mismatch_rejected— impersonation prevention
10. Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
Connection refused on port 3000 |
Relay not running | screen -r sprout-relay to check; restart with screen -dmS sprout-relay just relay |
| Port 3000 already in use | Another process (Node, etc.) | Set SPROUT_BIND_ADDR=0.0.0.0:3001 and RELAY_URL=ws://localhost:3001 in .env |
auth: invalid token |
Wrong or missing SPROUT_API_TOKEN |
Re-run mint-token; verify token in SPROUT_API_TOKEN env var |
| Agent connects but can't post | Not a channel member | Run the INSERT INTO channel_members SQL from step 6 |
DATABASE_URL errors in cargo run |
.env not loaded |
Use just relay instead of cargo run directly, or `export $(cat .env |
MySQL unhealthy after docker compose up |
Slow start | Wait 30s; check docker compose logs mysql for errors |
sprout-mcp generates ephemeral key |
SPROUT_PRIVATE_KEY not set |
Set SPROUT_PRIVATE_KEY=nsec1... so the agent's identity persists across restarts |
| E2e tests time out | Relay not running or wrong URL | Check RELAY_URL env var; confirm relay is listening with curl http://localhost:3000/info |
SQLX_OFFLINE errors in CI |
Missing .sqlx/ query cache |
Run cargo sqlx prepare --workspace locally and commit the .sqlx/ directory |