simocracy-v2 · gathering-context-field
context field for sim-facing event briefingEvent creators can now attach a briefing to any org.simocracy.gathering record. The briefing lands in the AI system prompt every time a sim chats, deliberates, or runs an S-Process inside the event — and renders to humans in the event Info sidebar.
Pass. The new context field round-trips through the user's PDS, is rendered as a dedicated Context for Sims section in the event sidebar on production, and is threaded into every AI endpoint (/api/chat, /api/hearing, /api/discussion, three /api/s-process/* routes, and /api/sim-heartbeat) via a single formatGatheringContext helper. Existing gatherings without the field continue to render unchanged.
org.simocracy.gatheringTwo optional properties added next to description / descriptionFacets:
"context": {
"type": "string",
"maxLength": 50000,
"maxGraphemes": 5000,
"description": "Background / briefing material for sims participating in this event. Shown to AI sims as event context so they can act efficiently (e.g. prior decisions, mission, constraints, data, links)."
},
"contextFacets": {
"type": "array",
"items": { "type": "ref", "ref": "app.bsky.richtext.facet" },
"description": "Rich text facets for context"
}
Sized 5× description (5000 vs 3000 graphemes) to fit a real briefing — agenda + prior decisions + constraints + links — without bloating the record.
lib/gathering-prompt.tsOne small helper used by every AI endpoint, returns "" when there's no context so callers can append unconditionally:
export function formatGatheringContext(opts: {
gatheringName?: string;
gatheringType?: string;
context?: string;
}): string {
if (!opts.context || !opts.context.trim()) return "";
const header = opts.gatheringName
? `## Event Context — ${opts.gatheringName}${opts.gatheringType ? ` (${opts.gatheringType})` : ""}`
: `## Event Context`;
return `${header}\nThe following briefing was provided by the event organisers. Use it to ground your reasoning and reference it where relevant.\n\n${opts.context.trim()}\n\n`;
}
| Endpoint | Where the context lands |
|---|---|
/api/chat | System prompt, between speaking-style and user-identity blocks |
/api/hearing | Senator system prompt for evaluate + respond, moderator prompt for summarize |
/api/discussion | Both sims' system prompts |
/api/s-process/evaluate | Recommender prompt before MVF instructions |
/api/s-process/evaluate-batch | Recommender prompt for FTC batch eval |
/api/s-process/deliberate | Round-2 deliberation prompt |
/api/sim-heartbeat | Per-duty context field exposed to agentic sims |
Callers (gathering-world, ftc-s-process-modal, sim-chat-modal, sim-discussion-modal) thread the context through when they're invoked inside an event. The senate-hearing-modal has a TODO comment because it isn't gathering-aware today — leaving that wiring for a follow-up keeps this PR focused.
components/events/gathering-form-dialog.tsx — new Context for Sims textarea (max 5000 chars, 6 rows) immediately below the Description field, with a helper line explaining where it lands.components/events/gathering-world.tsx — new collapsible Context for Sims section in the Info sidebar, with a BookOpen icon and an italic note: "Shared with sims when they participate in this event."components/events/events-gallery.tsx — passes the field through to the edit dialog.components/events/event-card.tsx — intentionally untouched; cards keep their existing shortDescription-only preview.com.atproto.repo.putRecordCreated a real test gathering on the facilitator PDS and read it back. The record carries context verbatim:
$ curl -s "https://climateai.org/xrpc/com.atproto.repo.getRecord" \
--data-urlencode "repo=did:plc:awtx57rxuisy4sf4kzzr3uhf" \
--data-urlencode "collection=org.simocracy.gathering" \
--data-urlencode "rkey=3lxtest19dc040f1c3" -G | jq .value
{
"name": "Context Field Verification Event",
"$type": "org.simocracy.gathering",
"status": "application",
"context": "BRIEFING FOR SIMS\n\nMission: Evaluate whether the new gathering.context field …",
"simSize": "normal",
"simScope": "council",
"createdAt": "2026-04-24T16:09:34Z",
"description": "This event exists solely to verify that the new context field round-trips …",
"gatheringType": "governance",
"shortDescription": "Smoke test for the new org.simocracy.gathering.context field."
}
Identifiers (so the record is auditable):
at://did:plc:awtx57rxuisy4sf4kzzr3uhf/org.simocracy.gathering/3lxtest19dc040f1c3
CIDbafyreihzvmqsamjpz5fnq3f2fvhz2qmdwmw4t6yvpwywijy22ojxzglfrm
Live URLsimocracy.org/events/…
JSON dumpassets/04-pds-record-get.json
simocracy.org)
buildingImageUrl. Click the building to open the Info sidebar.
simocracy-indexer on RailwayThe Railway-hosted indexer keeps its own copy of the lexicon JSONs and registers them into the GraphQL schema via lex-gql. I synced all drift, redeployed via railway up (the service has no GitHub auto-deploy hook), and confirmed the new typed fields are live:
$ curl -s -X POST https://simocracy-indexer-production.up.railway.app/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ orgSimocracyGathering(first:1){ edges{ node{ simScope context } } } }"}' | jq
{
"data": {
"orgSimocracyGathering": {
"edges": [{
"node": {
"simScope": "council",
"context": "BRIEFING FOR SIMS\n\nMission: Evaluate whether the new gathering.context field …"
}
}]
}
}
}
Drift I also fixed in the same sweep:
gathering.json — added context, contextFacets, simScope (matches v2)sim.json — added sprite blob (missing on indexer for two days)interviewTemplate.json — added the required "type":"object" on the record body (goat lex parse couldn't handle the omission)gainforest.earthAll 10 org.simocracy.* lexicons are owned by the gainforest.earth account (did:plc:qoti4acfmc5wg6zzmtix6hse, PDS discina.us-west.host.bsky.network) as com.atproto.lexicon.schema records. The NSID is discoverable via DNS:
$ dig +short TXT _lexicon.simocracy.org
"did=did:plc:qoti4acfmc5wg6zzmtix6hse"
$ goat lex resolve --did org.simocracy.gathering
did:plc:qoti4acfmc5wg6zzmtix6hse
Installed goat (brew install goat, v0.2.3), loaded the gainforest.earth app password from .env.goat, and published the three updated schemas:
$ export GOAT_USERNAME=gainforest.earth GOAT_PASSWORD=…
$ goat lex publish --update lexicons/
🟣 org.simocracy.gathering # updated
🟣 org.simocracy.interview # updated (template ref)
🟢 org.simocracy.interviewTemplate # newly published
Post-publish status — every schema now in sync with the network:
agents 🟢 org.simocracy.agents
defs 🟢 org.simocracy.defs
gathering 🟢 org.simocracy.gathering
history 🟢 org.simocracy.history
interview 🟢 org.simocracy.interview
interviewTemplate 🟢 org.simocracy.interviewTemplate
sim 🟢 org.simocracy.sim
skill 🟢 org.simocracy.skill
star 🟢 org.simocracy.star
style 🟢 org.simocracy.style
End-to-end verified across all three layers: (1) user PDS writes the field → (2) Railway indexer exposes it as a typed GraphQL scalar → (3) gainforest.earth publishes the com.atproto.lexicon.schema record so other ATProto apps see the same shape. Next.js app renders the "Context for Sims" section on production, AI endpoints inject the briefing via the shared helper, lint + npm run build pass, and every existing record without context still renders and processes unchanged.
simocracy-indexer was missing sprite (sim, two days stale) and simScope (gathering, two days stale). interviewTemplate.json was missing the required "type":"object" on its record body, which blocked goat lex tooling. All three fixes shipped with the same deploy.
The senate hearing modal currently doesn't receive a gathering reference, so its fetch('/api/hearing') call doesn't pass gatheringContext. Marked with a TODO(gathering-context) in components/senate/senate-hearing-modal.tsx. Follow-up: thread the senate gathering record into the modal so its hearings get the same briefing as gathering-world hearings.
The indexer's Railway service doesn't auto-deploy on GitHub push — railway up must be run from a dev machine. Consider adding a GitHub webhook. Until then, lexicon changes land in two commits: one to simocracy-indexer main, one railway up.
type:object fix)
simocracy-indexer commits9a76da1 (sync gathering fields) · 657e087 (sync sim.sprite + interviewTemplate)
Quality gatesnpm run lint · npm run build · goat lex parse · goat lex status — all pass
Indexer deploysTwo Railway deploys via railway up --ci --detach, both verified with __type(name:"OrgSimocracyGathering") schema introspection
Test recordat://did:plc:awtx57rxuisy4sf4kzzr3uhf/org.simocracy.gathering/3lxtest19dc040f1c3
Live pagesimocracy.org/events/…
PDS dumpassets/04-pds-record-get.json
Lexicon registryat://gainforest.earth/com.atproto.lexicon.schema/org.simocracy.gathering (DID did:plc:qoti4acfmc5wg6zzmtix6hse, PDS discina.us-west.host.bsky.network)
Implementation logtmux session gathering-ctx — product manager (this report) drove Claude Code (--dangerously-skip-permissions) through the two-commit plan in /tmp/gathering-context-brief.md; the network-layer sync was added on follow-up.