InfrastructureHorizon 3 · Enable emergent governance
Local Assembly
Each party interprets its own material and answers questions from other parties’ agents. No central interpreter.
Overview
- Status
- Designed, partly prototyped
How Local Assembly works
- Edge
- Holds a vault plus an agent
- Visibility first
- A manifest hand-off exposes a scoped projection
- Pass 1, self
- The edge interprets its own contribution
- Pass 2, presence
- The edge notes that others contributed
- Pass 3, meaning
- A cross-edge query to the edge that can interpret
- Scoped report
- The answer returns as one; the relational space composes the views
Settled
- Meaning resolves where it lives
- Not in an aggregate
- No new store
- It reads existing spec files
- Neither fabricates
- The witness holds the occurrence; the contributor holds the meaning
- Self pass alone
- The minimum, and runs without federation
- No reachable agent
- Assembly stops at presence
Start here
The build guide for the first version of Local Assembly. Nothing below is built yet. Find your role, then work down its checklist.
- Host · runs the community server
- The Host checklist. Also read Before you start and Build order.
- Community Owner · shares their work
- The Community Owner checklist.
- Member · reads what was shared
- The Member checklist, then Client setup.
- Everyone
- What done looks like is the test the whole build has to pass.
What done looks like
One tagged file, through five parts of the spec.
- See spec
Manifest mapping
Your scanner writes each tagged file down as a number and a date. The file stays home.
- See spec
Cuts and publishing
To share the work itself, cut a page for someone. A separate act from the manifest.
- No spec yet
Visibility system
One
visibilitylist per file decides which manifest and which cut it lands in. - Scroll to section
Community Data Commons MCP
An always-on server answers a member’s AI with only the ids and dates they may see.
- See spec
Activity log data
Your record and the community’s overlap. The overlap proves you took part.
Prompt for your agent
I'm building Local Assembly. Write me an end-to-end acceptance test for it.
The test:
1. A file in the Community Owner's vault gets member-b added to the visibility list in its frontmatter.
2. Their scanner writes member-b-manifest.json and syncs it to the community server.
3. The Member's AI calls list_changes over MCP, sending the community token and the identity member-b.
4. The answer contains that file's file_number and last_updated, and no names, paths or content.
Give me the steps as a script I can run, and tell me what each step prints when it passes.Before you start
Install these first. Each one has a check.
- Tailscale · every machine
- The community server and each member’s machine run Tailscale and are signed in. Check: the command below lists the machine as connected.
tailscale status - Node.js 22.19 or newer · the host
- The MCP Inspector, used to test the server, needs it. Check: the version prints 22.19 or higher.
node --version - Claude Desktop · each member
- The AI client used in Client setup. Another MCP client works if it can send headers.
- A port · the host
- Pick one port for the MCP server and use it everywhere this page says
<port>.
Prompt for your agent
Check that my machine is ready for Local Assembly. I am the <host, Community Owner or Member>.
Confirm:
1. Tailscale is installed and signed in (tailscale status).
2. Node.js is 22.19 or newer (node --version), if I am the host.
3. Claude Desktop is installed, if I am a member.
Tell me exactly what to install for anything that fails.Build order
Four phases. Each one is a working system on its own.
- Phase 1 · Local
- Scanner and MCP server on the Community Owner’s machine, over stdio. Done when they query it from their own AI as if they were the Member.
- Phase 2 · On the server
- Host checklist steps 1 to 4. Done when the Community Owner queries the server over their own network.
- Phase 3 · Shared
- Host checklist steps 5 and 6. Done when the Member gets the Community Owner’s ids and dates back.
- Phase 4 · The Member’s half
- The Member runs a scanner on their own vault and shares their node back. Two manifests, two tailnets, never merged. The cross-reference is the dual ledger.
find_overlaplands here.
Prompt for your agent
Plan Local Assembly in four phases. Each phase is a working system on its own:
1. Scanner and MCP server, local, over stdio.
2. MCP server on the community server, with manifest sync.
3. Tailscale share and the first remote query.
4. A second member's scanner, and a find_overlap tool.
For the phase I name, list the tasks, who does each (host, the Community Owner or the Member), and the check that proves the phase is done.
Phase: <number>What runs where
Seven pieces. Each one runs in one place.
- Scanner · each member’s machine
- Walks the vault, reads frontmatter, keeps the files tagged for the community, and writes the redacted manifest and the full manifest. The only part that touches real files.
- Community server · always on
- Holds each member’s redacted and full manifest, plus the two cuts folders. Needs a long-running Node.js or Python 3 process and a path for manifests to land.
- MCP server · on the community server
- Reads the manifest on each request and answers. Never reads a member’s vault. Runs as a service that survives reboots.
- Community agent · on the community server
- Answers a member’s agent within the visibility rules, and can hand over tools and cuts that member may use. Part of the first build; its contract is still open. See The community agent, below.
- Cuts folders · on the community server
- One drop folder each way: the Community Owner’s cuts for the Member, the Member’s cuts for the Community Owner. A cut is how readable work travels; see Cuts and lenses.
- Tailscale node share · between networks
- Shares the server with the Member as a single node. They reach the MCP endpoint and nothing else on the machine or the network.
- AI client · each member’s machine
claude_desktop_config.jsonpoints at the server’s MCP endpoint and carries the community token and the identity.
Prompt for your agent
Here are the six parts of Local Assembly and where each runs:
- Scanner: each member's machine
- Community server: always on
- MCP server: on the community server
- Cuts folders: on the community server
- Tailscale node share: between networks
- AI client: each member's machine
For the part I name, list what it needs installed, what it reads, what it writes, and how I confirm it is running.
Part: <name>Host checklist
Do these in order. Each step ends in a check. Angle brackets mark values you choose.
- 1 · Runtime and folder
- Confirm the server can run a long-lived Node.js or Python 3 process. Make one folder for manifests to land in. Check: one of these prints a version.
node --version python3 --version - 2 · The MCP server
- Write a server that exposes the three tools under The MCP tools and reads the manifest file fresh on every call. Serve it over Streamable HTTP so other machines can reach it. Reject any call that is missing the token or the identity. Check: the MCP Inspector connects and lists three tools.
npx @modelcontextprotocol/inspector --server-url http://<server>:<port>/mcp --transport http - 3 · Run it as a service
- It has to survive reboots. On Linux that is a systemd unit. Check: after a reboot, the status shows it active.
[Unit] Description=Community MCP server After=network-online.target [Service] ExecStart=<command that starts the server> User=<service user> Restart=always [Install] WantedBy=multi-user.target - 4 · Sync target
- Give the Community Owner a way to push their manifest in. The v1 default is rsync over ssh: add their ssh key for a user that can write only to the manifest folder. Check: after they save a tagged file, the manifest’s
emitted_atchanges.rsync -av member-b-manifest.json <user>@<server>:<manifest folder>/ - 5 · Share to the Member
- In the Tailscale admin console, open Machines, pick the server, choose Share and invite the Member. A shared machine is quarantined: it takes incoming connections and cannot start them. Limit shared users to the MCP port in the policy file. Check: from the Member’s machine, the MCP port answers and nothing else does.
"grants": [ { "src": ["autogroup:shared"], "dst": ["*"], "ip": ["<port>"] } ] - 6 · First query
- The Member adds the endpoint (see Client setup) and asks what the Community Owner changed. Check: the answer lists ids and dates, and nothing else.
Prompt for your agent
Walk me through standing up the community server for Local Assembly, one step at a time. Wait for my result before moving to the next step.
1. Confirm Node.js or Python 3 is installed, and make one folder for manifests.
2. Write an MCP server that serves three tools (list_changes, list_principles, get_principle_activity) over Streamable HTTP. It reads the manifest JSON fresh on every call and rejects any call missing the community token or the member identity.
3. Run it as a systemd service that survives reboots.
4. Make an ssh user that can write only to the manifest folder, for rsync.
5. Share the machine to the Member in Tailscale, and limit shared users to the MCP port with an autogroup:shared grant.
6. Run the first query.
Before step 1, ask me which OS the server runs and which language I want.Community Owner checklist
Do these in order, after the host has finished step 4.
- 1 · Tag a file
- Add
member-bto thevisibilitylist in a file’s frontmatter. Check: the file’s frontmatter shows the tag.--- visibility: [local, member-b] --- - 2 · Build the scanner
- Use the prompt under The manifest file. Check: it writes
member-b-manifest.jsonwithversion,emitted_at,source,filterandfiles. - 3 · Run it
- Run the scanner after you save a tagged file. Check:
emitted_atin the manifest is the time you ran it. - 4 · Push it to the server
- Send the manifest with the ssh key the host set up. Check: the server’s copy has the same
emitted_atas yours.rsync -av member-b-manifest.json <user>@<server>:<manifest folder>/
Prompt for your agent
I am the Community Owner for Local Assembly. Walk me through my half one step at a time, and wait for my result before the next step.
1. Tag a file: add member-b to the visibility list in its frontmatter.
2. Build the manifest scanner. Ask me which language first.
3. Run it and check member-b-manifest.json has version, emitted_at, source, filter and files.
4. Push it to the community server with rsync over ssh, using the key the host set up.
5. Confirm the server copy has the same emitted_at as mine.Member checklist
Do these in order, after the host has finished step 5.
- 1 · Accept the share
- Accept the Tailscale machine share the host sent. Check:
tailscale statuslists the community server. - 2 · Get the token
- Ask the host for the community token and your member name. How tokens are handed out is still open.
- 3 · Connect your AI client
- Follow Client setup, then restart the client. Check: three tools appear:
list_changes,list_principlesandget_principle_activity. - 4 · Ask what changed
- Ask your AI what the Community Owner changed in the last 7 days. Check: the answer has ids and dates, and nothing else.
Prompt for your agent
I am a Member joining a Local Assembly community server. Walk me through it one step at a time, and wait for my result before the next step.
1. Accept the Tailscale machine share I was sent, and confirm the server shows in tailscale status.
2. Add the server to Claude Desktop with mcp-remote. I will give you the URL, the token and my member name.
3. Restart, and confirm three tools appear: list_changes, list_principles, get_principle_activity.
4. Ask what changed in the last 7 days, and check the answer has only ids and dates.The community agent
Part of the first build. Its contract is still open.
- Where it runs
- On the community server, behind the MCP door.
- What it does
- Answers a member’s agent within the visibility rules. Reads the redacted manifests, the cuts folders and the commons pool. Hands over tools and cuts the member may use.
- What it never does
- Open a member’s vault, or interpret a member’s meaning.
- When it needs meaning
- It asks that member’s own agent: a cross-edge query, Local Assembly’s third pass. Designed, not built.
Prompt for your agent
Help me design the community agent for a Local Assembly server.
It runs on the community server, behind the MCP endpoint. It answers a member's AI within the visibility rules, reads only the redacted manifests, the cuts folders and the commons pool, and can hand the member tools or cuts they are allowed to use. It never opens a member's vault and never interprets a member's meaning. When it needs meaning, it asks that member's own agent.
Draft its contract: the requests it accepts, what it reads, what it may hand over, and what it must refuse. Mark every assumption you make.The manifest file
One JSON file per visibility tag. Format v0.1.
- File name
member-b-manifest.jsonfor themember-btag. The server reads it from the folder the host made in step 1.version- Format version, starting at "0.1".
emitted_at- When the scanner wrote the file, as an ISO 8601 timestamp.
source- Whose vault it describes, for example "owner".
filter- The visibility tag used to select files, for example "member-b". The same scanner writes one file per tag.
files[].file_number- SHA-256 of the file’s vault-relative path, first 6 hex characters, prefixed
0x. v1 computes it every run and writes nothing back. A rename makes a new number; v2 stores afingerprint-idin frontmatter once. files[].last_updated- The frontmatter
last-updatedfield, or the file’s modified time when that is missing. files[].design_principles- The
design-principlesfrontmatter field, joined with any design principles found intags. - What the Member reads by default
- The redacted manifest:
file_numberandlast_updated. Anything deeper is granted per relationship.
Prompt for your agent
Write a manifest scanner for Local Assembly in <language>.
It walks a folder of markdown files, parses the YAML frontmatter, and keeps files whose visibility list contains the tag I pass, for example member-b. It writes <tag>-manifest.json with:
- version: "0.1"
- emitted_at: when it ran, ISO 8601
- source: whose vault, for example "owner"
- filter: the tag
- files: one entry per kept file, with
- file_number: SHA-256 of the vault-relative path, first 6 hex characters, prefixed 0x
- last_updated: the frontmatter last-updated field, else the file's modified time, ISO 8601
- design_principles: the design-principles field, joined with any principle slugs found in tags (I'll give you the list)
It never writes back to the files. Include a test with three sample files.The MCP tools
Three tools. Each one checks the token and the identity first, answers only from the manifest file and never opens a vault.
list_changes- What changed since a date. Input:
since(string, ISO 8601). Returns:file_number,last_updatedanddesign_principlesfor each file whoselast_updatedis aftersince. Whetherdesign_principlesis returned by default is still open; until it is decided, return onlyfile_numberandlast_updated. list_principles- The shape of the work without naming any of it. Input: none. Returns: each principle in the manifest, with its file count.
get_principle_activity- One principle over time. Inputs:
principle(string) andwindow(optional, 30 days by default). Returns:last_updatedandfile_numberfor each file with that principle, sorted. find_overlap- Later, in phase 4. Compares two members’ manifests once the Member’s exists.
Prompt for your agent
Write an MCP server in <language> with the official MCP SDK, served over Streamable HTTP.
Three tools. Each reads <tag>-manifest.json fresh on every call and reads nothing else:
- list_changes(since: ISO 8601 string): file_number, last_updated and design_principles for files whose last_updated is after since.
- list_principles(): each principle in the manifest, with its file count.
- get_principle_activity(principle: string, window: optional, 30 days by default): last_updated and file_number for files with that principle inside the window, sorted.
Before any tool runs, check the community token and the member identity, and reject the call if either is missing or wrong. Show me how to test it with the MCP Inspector.Every request
Two checks at the door. Both ride on every call, so there is no session to keep.
- Community token
- Says the caller is in the community. Set in the AI client config.
- Identity
- Says which member: “I’m member-b”. The server serves only that member’s slice.
- Why both
- A shared token alone would show everyone everything. The identity does the scoping. Tailscale is the network gate; token and identity are the content gate.
- How they travel
- Proposed: two HTTP headers on every call,
X-Community-TokenandX-Member. The names are a suggestion until the host picks them. - Where the token comes from
- From the host. How it is made and handed out is still open.
Prompt for your agent
Add request checks to my Local Assembly server.
Every call carries two headers: X-Community-Token (shared by members) and X-Member (which member). Reject a call that is missing either one, or whose token does not match. Use the member to choose which slice to serve. There are no sessions: each call stands on its own. Keep the token out of the logs.Client setup
How a member points an AI client at the server.
- Claude Desktop
- Its config file reaches a remote server through the
mcp-remotebridge. Keep header values inenv: Claude Desktop on Windows mangles spaces insideargs.{ "mcpServers": { "community": { "command": "npx", "args": [ "mcp-remote", "http://<server>:<port>/mcp", "--allow-http", "--header", "X-Community-Token:${COMMUNITY_TOKEN}", "--header", "X-Member:${MEMBER}" ], "env": { "COMMUNITY_TOKEN": "<token>", "MEMBER": "member-b" } } } } - Why
--allow-http mcp-remoterefuses plain HTTP to anything but the local machine unless this flag is set. It is meant for private networks, and the tailnet is one.- Where the file lives
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json. Windows:%APPDATA%\Claude\claude_desktop_config.json. Restart Claude Desktop after editing it.
Prompt for your agent
Help me connect Claude Desktop to an MCP server at http://<server>:<port>/mcp on my tailnet.
Use mcp-remote with --allow-http, and send two headers, X-Community-Token and X-Member. Keep the header values in env, not inside args. Tell me where claude_desktop_config.json lives on my OS, give me the exact block to paste, and tell me how to confirm the three tools show up after I restart.The round trip in the diagram
The numbered steps in the first diagram, in words.
- Scan
- The Community Owner’s scanner sends records for tagged files to the server
- Sync
- Their live vault syncs to a private copy on the server, outside the MCP zone
- Ask
- The Member’s AI asks what changed, sending a token and “I’m member-b”
- Compare and filter
- The server compares the two manifests, then narrows the result to the Member
- Answer
- The scoped answer returns to the Member’s AI over MCP
- Baseline
- The redacted manifest becomes the new baseline
- Cut
- The Community Owner drops a cut in their folder for the Member
- Mirror
- The reverse runs through the Member’s scanner and their cuts folder
- Commons
- Either person can add a shape to the commons; nothing else leaves
Troubleshooting
What breaks first, and where to look.
- The local stdio server breaks
- Something wrote to stdout. In a stdio server, logs go to stderr; anything on stdout corrupts the messages.
- The Member cannot connect
- Check the share was accepted, the port is in the policy grant, and the URL path matches the one the server serves.
- Every answer is empty
- No files carry the member’s tag, or the sync has not run. Look at the manifest’s
emitted_at. - Answers look old
- The server answers from the last manifest it has. The Community Owner’s machine was asleep, or the sync failed.
Prompt for your agent
My Local Assembly setup isn't working. Here is what I see: <describe it>.
The setup: a scanner writes <tag>-manifest.json and rsyncs it to the community server. An MCP server there serves three tools over Streamable HTTP. The Member reaches it through a Tailscale machine share with an autogroup:shared port grant, using Claude Desktop and mcp-remote.
Ask me one question at a time to find which link is broken: the scanner, the sync, the server, the share or the client.Decided for the build
Decided in the working notes.
- The contributor decides
- Contributing a shape and granting access to the work are separate acts
- Shapes, not content
- The work stays on its owner’s machine or in published cuts on the server
- Reading takes a second step
- MCP, per relationship, through the dual-ledger gate
- A token on every arrow
- Every arrow into the server carries a community token
- Events
- Insights drop in with the name stripped; the contributor’s vault keeps the named receipt
Still open for the build
Not decided yet. Each one changes what gets built.
- Community agent
- Its contract: what it accepts, what it reads, what it may hand over, and how it is built
- Community token
- Who makes it, how members receive it, and how it is replaced
- Full manifest
- Content or metadata only
- The server
- Whose hardware, whose network, who administers
- The Member’s view
- What they see beyond the redacted table, including whether design principles show by default
- The Member’s half
- Their setup is undecided
- Last active
- The frontmatter field, the file’s modified time, or the changelog
- Sync cadence
- On save, debounced, or batched
- Freshness
- Whether answers carry an “as of last sync” stamp
- Access or sync
- Whether the Member reads on demand only, or files also copy to them
- Failure modes
- What the Member’s client shows when the server, the share or the manifest is down
- Header names
- Proposed on this page, not agreed
- Commons contribution
- How a pair’s manifest becomes one is not specified
- Status
- The technical spec says in progress, the validation map says designed
Also
- Out of scope for the paper
- The live surface, and the drawing and insight agents.
