Collaborative AI memory for a crew on the same Jira initiative:
| Layer | Role |
|---|---|
| Jira | Initiative identity (YOU_JIRA_TICKET_HERE, …) |
| Supabase | Membership + shared memories (SoT) |
| Local cache | .team-brain/cache/<KEY>.json for agents |
| Markdown | Optional export under initiatives/<KEY>.md |
project.public.env is gitignored — copy project.public.env.example locally (cp …example …env) or let bootstrap --write-env create it. Never commit live URL/anon.
| Role | Needs Supabase account? | Needs |
|---|---|---|
| Crew admin (once) | Yes — create one project for the crew | URL + anon in local project.public.env / team.yaml / env |
| Joiner | No | Crew’s URL + anon (from admin) + invite code + Jira key |
Each engineer gets a personal credentials.json after register/join — never commit that file.
Junior onboarding (step-by-step): docs/team-brain-onboarding.md
Full plan: docs/team-brain-memory.md · MCP: mcp/team-brain/
Preferred (one command, ~10 minutes): crew bootstrap — configure → migrate → register → print share bundle.
project.public.env (replace the placeholders). Set TEAM_BRAIN_JIRA_SITE to your org.supabase db push) — see migrations/README.md.cd brainstack
bash core/scripts/team-brain-api.sh register "Team Atlas" "Your Name"
| Share with the crew | Keep private |
|---|---|
| Invite code + project URL + anon key | credentials.json / member api_key |
| Jira key | Supabase service_role key |
register prints the invite code once. join / onboard do not return it (members cannot re-share from credentials).
Collapses owner setup into one script (core/scripts/team-brain-bootstrap.sh).
Default (no psql / no CLI): SQL Editor + --skip-migrations — see onboarding Path B.
cd brainstack
# Step 1 — generates combined SQL + stops with instructions (or use SQL Editor path in onboarding)
bash core/scripts/team-brain-api.sh bootstrap \
--team "Spike Crew" --admin "Alice" \
--url "https://YOUR_REF.supabase.co" \
--anon "eyJ..." \
--jira YOU_JIRA_TICKET_HERE \
--write-env
# Step 2 — after pasting supabase/.bootstrap-migrations.combined.sql in SQL Editor:
bash core/scripts/team-brain-api.sh bootstrap \
--team "Spike Crew" --admin "Alice" \
--url "https://YOUR_REF.supabase.co" \
--anon "eyJ..." \
--jira YOU_JIRA_TICKET_HERE \
--write-env \
--skip-migrations
# One-shot (requires psql on PATH):
bash core/scripts/team-brain-api.sh bootstrap \
--team "Spike Crew" --admin "Alice" \
--url "https://YOUR_REF.supabase.co" \
--anon "eyJ..." \
--db-url "postgresql://postgres:YOUR_DB_PASSWORD@db.YOUR_REF.supabase.co:5432/postgres" \
--jira YOU_JIRA_TICKET_HERE \
--write-env
# Linked Supabase CLI:
bash core/scripts/team-brain-api.sh bootstrap --team "Spike Crew" --admin "Alice" --jira YOU_JIRA_TICKET_HERE
# Local Docker demo:
bash core/scripts/team-brain-api.sh bootstrap --team "Local Crew" --admin "Alice" --local --jira DEMO-1
| Migration path | When |
|---|---|
SQL Editor + --skip-migrations |
Default — no psql, no CLI; paste .bootstrap-migrations.combined.sql |
--local |
Docker supabase start (CLI applies migrations/) |
| Linked CLI | supabase db push |
--db-url |
Requires psql (brew install libpq) — applies each migration file |
Bootstrap prints a share bundle (invite + URL + anon + joiner checklist).
Never commit live URL/anon/DB password. --db-url is never written to project.public.env.
Dry-run (no mutations): add --dry-run.
Ask your admin for: invite code, Jira key, and the crew’s Supabase URL + anon key. Put URL/anon in project.public.env (or .team-brain/team.yaml / env), then:
cd brainstack
bash core/scripts/team-brain-api.sh onboard <INVITE> "Your Name" YOU_JIRA_TICKET_HERE
Day to day
bash core/scripts/team-brain-api.sh remember YOU_JIRA_TICKET_HERE research "What I learned…"
bash core/scripts/team-brain-api.sh recall YOU_JIRA_TICKET_HERE
bash core/scripts/team-brain-api.sh breakdown YOU_JIRA_TICKET_HERE
| Control | Status |
|---|---|
| API keys hashed (SHA-256); never stored plaintext | ✅ |
| Direct table access revoked; RPCs security-definer | ✅ |
Unique (team_id, display_name) — no unlimited join spam |
✅ |
| Invite codes 16 hex chars | ✅ |
| Live project URL/anon not committed to OSS | ✅ placeholders only |
join_team omits invite_code; whoami returns it only for admin |
✅ |
Anon register_team / join_team rate limiting |
✅ DB-level sliding window (default 5 register/h, 15 join/h) |
pgcrypto calls schema-qualified (extensions.digest) |
✅ Required on Supabase (search_path = public); see migration 60809 |
| Realtime push body encrypted app-layer, not plaintext on the wire | ✅ AES-256-CBC + HMAC-SHA256, per-team key, DB never decrypts |
remember body-size cap |
✅ 20,000 chars |
| RPC request timeouts (client) | ✅ curl --max-time (default 20s, TEAM_BRAIN_HTTP_TIMEOUT) |
| Client-side readiness preflight | ✅ team-brain-api.sh doctor |
Do not commit service_role keys or live anon keys to public repos. Rotate invite codes / anon keys if leaked.
Rate limiting (#32): register_team and join_team are rate-limited via a DB-level sliding window (…_rate_limits.sql). Defaults: 5 registers/hour, 15 joins/hour per fingerprint. Tunable via Postgres settings (app.rate_limit_register, app.rate_limit_join). Run select tb_cleanup_rate_limits() periodically (or via pg_cron) to purge old entries. Monitor: select * from tb_rate_limit_stats (authenticated/service_role only).
Existing projects: apply any new migration files once (… → realtime_broadcast → roles_and_invites → rate_limits → aggregate_metrics → full_push_and_semantic_hardening).
Sync mode: bash core/scripts/team-brain-api.sh start <JIRA-KEY> — one entry, background pull, idle sleep. Requires …_sync_mode.sql for merge-on-source_ref updates.
Correction / history: correct + learning kind (…_learning_kind.sql); history / restore soft rollback (…_memory_history.sql — revisions team-scoped via RPCs, no anon table SELECT).
Realtime push (#31, full content): Broadcast on remember (…_realtime_broadcast.sql + …_full_push_and_semantic_hardening.sql) — no anon SELECT on captures, and the body itself travels app-layer encrypted (body_ct: AES-256-CBC + HMAC-SHA256, per-team broadcast_key), never plaintext, never decrypted by the DB. A resolved member (valid api_key) fetches the key once via memory_broadcast_topic and decrypts locally with zero extra round-trip. Peers without the key/cryptography, or a row just restored (ciphertext cleared to force a correct re-pull), fall back to the original signal + authenticated list_recent pull. Peers: start / watch --push + pip install websockets cryptography (cryptography optional — omit it and you still get signal + pull). Fallback: poll/watch always work (TEAM_BRAIN_REALTIME=off to disable push).
Roles / invites (#40): admin | member (write) | viewer (read-only). Apply …_roles_and_invites.sql. Joiners: onboard … --role viewer. Admins: rotate-invite, set-role "Name" --role member.
Repo pin (#39): commit .team-brain/project.json (Jira key + team name only — never anon/api_key/invite). start / attach use the pin when the key is omitted.
Team aggregation (#35): team_aggregate_metrics RPC + metrics --team / aggregate. Returns counts + display_name only — never memory bodies, never personal BRAIN.md, never uploads local metrics.json. Apply …_aggregate_metrics.sql.
Semantic recall (optional, one-command opt-in — #4): bash core/scripts/team-brain-api.sh enable-semantic openai|ollama persists the provider/model to team.yaml (non-secret, shareable with the crew) and tests one embed call; otherwise recall uses FTS. The embed API key (TEAM_BRAIN_EMBED_API_KEY / OPENAI_API_KEY) is always env-only, never written to disk.