Governance any group can use — one-click proposals and votes for anyone with an OrangeCat account, Bitcoin signatures for anyone who wants a vote others can recount without trusting Solon, versioned policies and an append-only audit trail.
Live at solon.orangecat.ch.
Solon is the governance pillar of a three-product stack:
| Pillar | Product | Role |
|---|---|---|
| Economy | OrangeCat | Bitcoin-native economic layer — entities, wallets, payments, the public timeline |
| Execution | Loki | Where the work gets done — AI-agent fleet control plane, plus the people, commitments and spending the work runs on, and the deploy pipeline for the whole stack |
| Governance | Solon (this repo) | Proposals, one-click or Bitcoin-signed votes, versioned policies, append-only audit |
The ties are real, not marketing:
- OrangeCat's platform allocation policy is governed here. The Cat's spending ceiling is a Solon policy; OrangeCat re-verifies every Bitcoin vote signature against its own pinned keys before honoring a decision (a Solon decision is evidence, not authority).
- So is the originator share.
originator_sharev1 routes 10% of a product's net revenue, by default, to the originators of the code it is built from — the repository and every shared package it adopts, equal per originator, monthly, in BTC, on a public ledger. Who originated what is read from the fleet's origin register, derived from OpenTimestamps proofs, never typed into the policy.src/lib/domain/originator-share.tsis the deterministic split anyone can recount; changing the rule is anALLOCATION_POLICYvote. - Both sibling agents are voting members. The Cat (
orangecat:cat) and Loki (loki:loki) hold their own keys and cast Bitcoin signed-message votes viascripts/agent-vote.ts. - Loki ships Solon.
.github/workflows/deploy.ymlcalls Loki's sharedselfhost-deploy.yml; a merge tomaindeploys to production. - Decisions are self-verifying.
GET /api/v1/decisions/{sessionId}returns the full signed record so either sibling — or anyone — can recount the tally.
A legislature for an economy that AI agents help run. It exists because "who counts as in need", "what is a fair allocation" and "what happens when aid is abused" are legitimacy questions, not engineering ones — and if an agent silently decides who eats, that is rule-by-algorithm.
Four properties do the work:
- Easy by default. A member signs in with OrangeCat and votes with one
click — no wallet, no key. That vote is recorded with
proof: ACCOUNT: it is Solon's record, and every published document says so. - Verify, don't trust — when you want to. A member (and every agent) can
instead sign with their own Bitcoin key (
proof: BIP137). Those acts are published with their signatures, so anyone can recount them independently. - No keys, no custody. Solon never holds a private key. The treasury is watch-only — Solon stores addresses to observe, never funds or keys. There is no code path that can spend.
- Append-only record. Audit events are never updated or deleted.
- Red lines agents cannot cross. Some categories are constitutionally humans-only (below).
src/lib/config/governance.ts maps every decision category to an electorate.
Agents may vote on operational and policy matters; these four are HUMANS_ONLY
and no agent key can be counted on them:
| Category | Electorate |
|---|---|
ALLOCATION_POLICY |
all members |
TREASURY_SPEND |
all members |
OPERATIONS |
all members |
AID_DISBURSEMENT |
humans only |
MEMBERSHIP |
humans only |
SAFETY |
humans only |
GOVERNANCE_RULES |
humans only |
An organization picks its structure when it is founded, and can change it later
by a GOVERNANCE_RULES decision taken under the structure it already has.
src/lib/config/governance-profiles.ts is the SSOT; every profile says, for each
category, who decides (every member, or the members holding a mandate) and
how (method, threshold, quorum).
| Structure | Who decides |
|---|---|
One person decides (SOLE) |
The founder holds the only mandate and decides everything, including the rules. Members can propose; they do not vote. |
Everyone decides (TOWN) |
Every member votes on every decision. The default. |
Elected delegates decide (DELEGATED) |
Members grant mandates for a term (365 days unless the decision names an end). Delegates decide money and operations; members keep membership, safety and the rules — which is how they elect and recall. |
Association, Cooperative, Collective and Company board are house styles of "everyone decides" with different methods and bars.
The labels describe what a structure does, not what it resembles: no "monarchy", no "republic". Three rules keep every structure honest:
- It is said out loud. The organization page and
GET /api/orgs/{slug}publish the profile'swhoDecidessentence beside the roster, so nobody joins without knowing. - Nothing stalls. A category given to mandate holders when no mandate is
live — a term lapsed, a delegate resigned — goes to the members, and the
session's audit event says so (
src/lib/domain/mandate.ts). - A mandate narrows, never widens. Mandate holders are still filtered by the
category's electorate, so an agent holding a mandate cannot vote on a
humans-only category. Granting or ending a mandate is a
MEMBERSHIPdecision; switching structure isGOVERNANCE_RULES(src/lib/domain/effects.ts).
And the one that sits outside the code: Solon is open source. Anyone who wants a different structure — or a different Solon — can take it and run their own.
Proposal (DRAFT) ──open──> VotingSession (OPEN) ──signed votes──> CLOSED
│
Decision + Policy version, AuditEvent
- A proposal is drafted against an organization and a decision category.
- Opening it creates a voting session; the category fixes the electorate, and the profile decides whether every member or only the mandate holders vote (the roll of mandate holders is frozen at open).
- Members vote — one click, or a Bitcoin signed message. One ballot per member
per session, enforced by a unique constraint on
[sessionId, memberId]; voting again before close replaces the earlier ballot. - Closing tallies the result, writes the decision, versions the affected policy, carries out the proposal's effect if it has one (a mandate granted or ended, a structure switched), and appends an audit event.
src/lib/db/schema.ts is the SSOT for governance — 9 models (Drizzle), with
types, validation and API contracts derived from it. Places (every jurisdiction,
official and founded) has its own module, src/lib/db/places-schema.ts; its
tables exist and are empty until the first country is imported
(docs/design/2026-09-places-and-jurisdictions.md).
Organization ── has many ──> Member (HUMAN | AGENT; OrangeCat identity and/or own Bitcoin key)
│ │
├── Proposal ──> VotingSession ──> Vote (signed, unique per session)
├── Policy (versioned; what a decision actually changes)
├── TreasurySource (watch-only address — label + address, nothing else)
├── AuditEvent (append-only)
└── AgentApiKey (how a sibling agent authenticates)
Domain logic lives in src/lib/domain/ (proposals, voting, tally,
decision, treasury, membership, organization, org, canonical,
mandate, effects) and
stays free of HTTP and UI concerns.
Bitcoin message signing and verification is src/lib/bitcoin/message.ts.
Identity is one seat per organization. An OrangeCat identity may sit on
several rosters but holds at most one seat on each
(members_organization_id_oc_actor_id_key). Founding is permissionless.
Any recognized OrangeCat identity may found an organization (a Bitcoin key is
optional), and the organization, the founder's seat and both audit
events land in one transaction (src/lib/domain/organization.ts). An
organization is recorded as governing a Loki project (claimed_project) only
when Loki signed a grant for the founder's own identity (src/lib/loki-grant.ts)
— never because the names happen to match.
Founding chooses how the organization decides: one of the governance
profiles (src/lib/config/governance-profiles.ts; ids in src/lib/db/enums.ts
and CHECKed on the column), bound into the signed text as decides: when the
founder signs. Changing it afterwards is a GOVERNANCE_RULES vote.
| Method | Route | Purpose |
|---|---|---|
GET |
/api/orgs |
Every organization, and the Loki project each governs by consent |
POST |
/api/orgs |
Found an organization (OrangeCat session; Bitcoin signature optional) |
GET |
/api/orgs/{slug} |
Organization and its members |
GET |
/api/orgs/{slug}/audit |
Append-only audit trail |
GET |
/api/orgs/{slug}/policies/{key} |
Current policy version |
GET |
/api/orgs/{slug}/proposals |
Proposals for an organization |
GET |
/api/orgs/{slug}/treasury |
Watch-only treasury sources |
POST |
/api/proposals |
Create a proposal |
POST |
/api/proposals/{id}/open |
Open voting |
GET |
/api/sessions/{id} |
Session state and tally |
POST |
/api/sessions/{id}/votes |
Cast or change a vote (signed-in member, or Bitcoin signature) |
POST |
/api/sessions/{id}/close |
Close and record the decision |
GET |
/api/v1/decisions/{sessionId} |
Self-verifying signed record |
GET |
/api/health |
Liveness |
Public: /, /features, /security, /integration, /about,
/ecosystem (the live governed state), /join, /propose, /proposals,
/governance/voting, /governance/audit, /treasury/bitcoin,
/orgs/{slug} (an organization's roster and record), /orgs/new (found one)
Governance, explained — the teaching section. Every tally on these pages is
produced by the same aggregate() that counts a real session, so a change to
how Solon counts changes the lesson rather than leaving it stale:
| Page | What it demonstrates |
|---|---|
/governance |
The four questions every organization answers; Condorcet's paradox drawn |
/governance/methods |
One room, five ways of counting, two different winners (interactive) |
/governance/thresholds |
Quorum and threshold as two knobs over one vote (interactive) |
/governance/who-decides |
Every category's electorate, threshold and quorum; the humans-only red lines |
/governance/profiles |
Who decides, and how: every shipped profile compared rule by rule |
The worked examples live in src/lib/governance/worked-example.ts and supply
ballots only — never a result. src/lib/__tests__/worked-example.test.ts
pins the conclusions the pages state, so a published lesson cannot quietly
become false.
Authenticated: /dashboard, /dashboard/treasury, /dashboard/voting,
/account
| Layer | Technology |
|---|---|
| Framework | Next.js 16.3 (App Router, output: 'standalone') |
| Language | TypeScript 6.0 (strict) |
| Database | PostgreSQL + Drizzle ORM |
| Auth | NextAuth v5 (beta) |
| Styling | Tailwind CSS 4 — tokens from @fleet/design-tokens |
| Bitcoin | @noble/* + bs58check (signing / verification) |
| Tests | Vitest (unit + integration), Playwright e2e, Puppeteer smoke |
| i18n | English, German, French, Italian |
Design system: see docs/development/ui-guidelines.md.
Tokens are imported from the shared @fleet/design-tokens package (one SSOT
for OrangeCat, Loki and Solon), and Solon is dark-only.
git clone https://github.com/bitbaum/solon.git
cd solon
pnpm install
cp .env.example .env # set DATABASE_URL
pnpm run db:migrate # applies drizzle/ migrations (baseline + seed)
pnpm run dev # http://localhost:3000Add a member or cast an agent vote:
pnpm exec tsx scripts/add-member.ts
pnpm exec tsx scripts/agent-vote.tspnpm run verify is the single gate, and CI runs exactly it:
pnpm run verify # format:check && lint && typecheck && design:check && testpnpm run test:e2e # Playwright (needs a running app)
pnpm run test:puppeteer # smoke against BASE_URLMerging to main deploys to production automatically. Green PRs merge
themselves — the exact policy lives once in the fleet-wide sweep in bitbaum/fleet (.github/workflows/auto-merge-sweep.yml), which this repo calls.
Bitbaum is a platform for the new economy and for creation: building, engineering and researching. It's a community of builders, creators and researchers, and of the people who support them. We work as engineers in the loop: agents do much of the typing, and people own the judgement.
This code is MIT-licensed so that you can take it. Use it, fork it, rebrand it, sell it, or lift a single file. Make it yours and keep improving it the way you like. You don't need to ask, book a call or sign a CLA. Just keep the LICENSE with your copy.
npx degit bitbaum/solon my-solon # a clean copy without our git history
cd my-solon && pnpm install- Needs: Postgres (
DATABASE_URLin.env.example). - Shared pieces: the
@bitbaum/*kits install from public npm or public GitHub, so you need no tokens or private registry.
More to take: orangecat.ch/steal and github.com/bitbaum.
Copying code is free. What stays scarce once agents and robots write most of it is seeing the whole system. Pick one of these, in this repo, and open an issue or a pull request:
- Find the second source of truth. Find a fact this code defines in two places. Show how the copies will drift, and say where the one copy should live.
- Find the silent failure. Pick a promise this system makes. Find where it fails while every check stays green, and propose the check that would catch it.
- Find where the human belongs. Point to a step where an agent acts alone but a person should decide, or the reverse, and say why.
A short, correct answer beats a long one, and so does an answer that admits what it doesn't know.
Governance should be verifiable, not trusted.