pi-simocracy 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,239 +1,89 @@
1
1
  # pi-simocracy
2
2
 
3
- Load a [Simocracy](https://simocracy.org) sim into your [`pi`](https://github.com/mariozechner/pi-coding-agent) chat — see its
4
- sprite render in the terminal and chat with the agent **as that sim**.
3
+ A [`pi`](https://github.com/mariozechner/pi-coding-agent) extension for
4
+ [Simocracy](https://simocracy.org). Loads a sim into your chat as a
5
+ roleplay persona, and lets the agent read and write Simocracy records
6
+ (constitution, speaking style, comments) on your behalf.
5
7
 
6
8
  ```
7
- /sim mr meow
9
+ pi install npm:pi-simocracy
8
10
  ```
9
11
 
10
- …fetches Mr Meow from Simocracy's ATProto indexer, renders his 32×32
11
- pixel-art sprite as colored ANSI half-blocks directly in the chat, and
12
- pushes his constitution + speaking style into pi's system prompt so pi
13
- roleplays as Mr Meow until you `/sim unload`.
14
-
15
- <p align="center">
16
- <img src="https://raw.githubusercontent.com/GainForest/pi-simocracy/main/demo/sim-load.gif" alt="Mr Meow (a pipoya pixel-art cat) loaded inline in pi's chat" width="760">
17
- </p>
18
-
19
- As of v0.4.0, codex pet sims (OpenAI hatch-pet skill output) render too —
20
- load **Einstein** with `/sim einstein` and the WebP atlas decodes, the
21
- idle frame crops, and the half-block ANSI render fires inline:
22
-
23
12
  <p align="center">
24
- <img src="https://raw.githubusercontent.com/GainForest/pi-simocracy/main/demo/codex-pet-load.gif" alt="Einstein (a codex pet sim with a WebP petSheet) loaded inline in pi's chat" width="760">
13
+ <img src="https://raw.githubusercontent.com/GainForest/pi-simocracy/main/demo/sim-hero.gif" alt="Loading Duo and Mr Meow into pi's chat" width="900">
25
14
  </p>
26
15
 
27
16
  ---
28
17
 
29
- ## Install
30
-
31
- ```bash
32
- pi install npm:pi-simocracy
33
- ```
34
-
35
- That's it. Open `pi`, type `/sim mr meow`, and you're talking to the cat.
18
+ ## Slash commands
36
19
 
37
- For the optional `simocracy_chat` tool (one-shot conversation through
38
- OpenRouter without changing the active session persona), set
39
- `OPENROUTER_API_KEY` in your environment. The slash-command flow
40
- doesn't need it it just rewrites pi's system prompt.
20
+ | Command | What it does |
21
+ |-----------------------|---|
22
+ | `/sim <name>` | Load a sim by fuzzy name. Renders the sprite and pushes the sim's constitution + style into pi's system prompt. |
23
+ | `/sim <at-uri>` | Load a sim by AT-URI (no search). |
24
+ | `/sim unload` | Drop the persona and break character cleanly on the next reply. |
25
+ | `/sim status` | Show the currently loaded sim. |
26
+ | `/sim my [name]` | Pick / fuzzy-load from sims you own. Requires `/sim login`. |
27
+ | `/sim login [handle]` | Sign in to ATProto via loopback OAuth. Required for any write. **Unrelated to pi's built-in `/login`** (that signs you into your model provider). |
28
+ | `/sim logout` | Clear the local ATProto session. |
29
+ | `/sim whoami` | Show the signed-in handle / DID. |
41
30
 
42
31
  ---
43
32
 
44
- ## Slash commands
45
-
46
- | Command | What it does |
47
- |-------------------|-------------------------------------------------------------|
48
- | `/sim <name>` | Load a sim by name (fuzzy search). Multiple matches → picker. |
49
- | `/sim <at-uri>` | Load a sim by AT-URI directly (no search). |
50
- | `/sim status` | Show which sim is currently loaded. |
51
- | `/sim unload` | Drop the persona and break character cleanly. |
52
- | `/sim login [handle]` | Sign in to **ATProto / Bluesky** via loopback OAuth (NOT Anthropic — pi's built-in `/login` is what does that). Required before pi can update your sim. |
53
- | `/sim logout` | Clear the local ATProto OAuth session. |
54
- | `/sim whoami` | Show the signed-in handle / DID. |
55
- | `/sim my [name]` | List / pick / fuzzy-load sims you own on your PDS. Single match auto-loads; ambiguous matches open a picker. Requires `/sim login`. |
56
- | `/sim help` | Print usage. |
57
-
58
- Examples:
33
+ ## Tools (LLM-callable)
59
34
 
60
- ```
61
- /sim mr meow
62
- /sim Marie Curie
63
- /sim at://did:plc:qc42fmqqlsmdq7jiypiiigww/org.simocracy.sim/3mfo6vwfaka24
64
- /sim login alice.bsky.social
65
- /sim my
66
- /sim unload
67
- ```
35
+ | Tool | When to call it |
36
+ |---|---|
37
+ | `simocracy_load_sim` | Load a sim into the session (sets the persona). |
38
+ | `simocracy_unload_sim` | Stop roleplaying. |
39
+ | `simocracy_chat` | Send one message to a sim and get a quoted reply, **without** changing the active session persona. Needs `OPENROUTER_API_KEY`. |
40
+ | `simocracy_lookup_record` | Fetch a sim / proposal / gathering / decision / comment by AT-URI or fuzzy name. Returns the record + comment subtree, with sim-authored comments flagged inline (🐾) so you can tell which opinions are human and which are sim. Use this before `simocracy_post_comment` to find the right `subjectUri`. |
41
+ | `simocracy_post_comment` | Post a comment on a record **as the loaded sim**. Writes the comment plus an `org.simocracy.history` sidecar that attributes it to the sim. Requires `/sim login` + sim ownership. See [`docs/SIM_AUTHORED_COMMENTS.md`](docs/SIM_AUTHORED_COMMENTS.md) for the design. |
42
+ | `simocracy_post_proposal` | Submit a new funding proposal (`org.hypercerts.claim.activity`) **as the loaded sim**. Writes the proposal plus an `org.simocracy.history` sidecar with `type: "proposal"`. Optional itemized `budgetItems`, `workScope` tags, `contributors`, and an https `imageUri` (the default Simocracy banner is used otherwise — image upload from disk is intentionally not supported). Requires `/sim login` + sim ownership. See [`docs/SIM_AUTHORED_PROPOSALS.md`](docs/SIM_AUTHORED_PROPOSALS.md) for the design. |
43
+ | `simocracy_update_sim` | Rewrite the loaded sim's constitution (`shortDescription` + `description`) and/or speaking `style` and persist to your PDS. Requires `/sim login` + sim ownership. |
68
44
 
69
45
  ---
70
46
 
71
- ## Editing a sim's constitution / speaking style
72
-
73
- There is no slash-command pipeline for this. Once you've signed in via
74
- `/sim login` and loaded a sim you own (`/sim my`, then pick), just
75
- **describe the change you want to pi**:
47
+ ## Typical agent flows
76
48
 
49
+ **Roleplay as a sim:**
77
50
  ```
78
- > add a red line about animal welfare to the constitution
79
- > rewrite the speaking style to drop the lenny faces and be more concise
80
- > shorten the constitution to ~300 words and emphasise renewable energy
51
+ /sim mr meow
81
52
  ```
53
+ Then chat normally — pi answers in character.
82
54
 
83
- Pi rewrites the constitution and/or speaking style itself, then calls
84
- the `simocracy_update_sim` tool to persist the result. The tool refuses
85
- to run if you're not signed in or you don't own the loaded sim. The
86
- new persona takes effect on the next reply — no reload needed.
87
-
88
- Writing goes directly to your PDS via
89
- `com.atproto.repo.createRecord` / `putRecord` against the
90
- `org.simocracy.agents` (constitution) and `org.simocracy.style`
91
- (speaking style) collections — the same lexicons simocracy.org reads
92
- back.
93
-
94
- ---
95
-
96
- ## LLM-callable tools
97
-
98
- The same actions are exposed to pi as tools, so the model can drive them itself:
99
-
100
- | Tool | Use when |
101
- |-------------------------|-----------------------------------------------------------------|
102
- | `simocracy_load_sim` | Load a sim into the current session (sets the persona). |
103
- | `simocracy_unload_sim` | Stop roleplaying. |
104
- | `simocracy_chat` | Send one message to a sim and get a quoted reply, **without** changing the active session persona. Useful for "ask Mr Meow what he thinks of this PR." Requires `OPENROUTER_API_KEY`. |
105
- | `simocracy_update_sim` | Write a new constitution (`shortDescription` + `description`) and/or speaking `style` for the **loaded** sim to your PDS. Requires `/sim login` AND ownership of the loaded sim. |
106
-
107
- ---
108
-
109
- ## How it works
110
-
111
- 1. **Search.** GraphQL query against the public Simocracy indexer
112
- (`simocracy-indexer-production.up.railway.app`) for `org.simocracy.sim`
113
- records, then client-side fuzzy ranking by exact match → prefix → substring → token overlap.
114
- 2. **Resolve.** Parse the winning AT-URI, fetch the DID document from
115
- `plc.directory` (or the `did:web` well-known URL), follow the
116
- `#atproto_pds` service endpoint to find the owner's PDS.
117
- 3. **Hydrate.** Pull three records from the PDS via
118
- `com.atproto.repo.getRecord` / `listRecords`:
119
- - `org.simocracy.sim` — display name + sprite + avatar blob refs
120
- - `org.simocracy.agents` — short description + full constitution
121
- - `org.simocracy.style` — speaking style / mannerisms
122
- 4. **Render.** Fetch the sprite blob via `com.atproto.sync.getBlob`,
123
- decode it, crop the front-facing idle frame. Two render paths
124
- depending on the sim's `spriteKind`:
125
- - **`pipoya`** (legacy + default): 128×128 PNG, 4×4 of 32×32 walking
126
- frames; decode with `pngjs`, take row 0 col 0 at native size.
127
- - **`codexPet`** (OpenAI hatch-pet output): 1536×1872 atlas, 8×9 of
128
- 192×208 cells. PNG sheets decode through `pngjs`; WebP sheets
129
- decode through `@jsquash/webp` (wasm, lazy-init). The idle cell
130
- (row 0 col 0) is the source for both render paths below.
131
-
132
- Then emit through one of two terminal output paths, picked
133
- automatically per-terminal:
134
- - **Inline graphics** (Kitty graphics protocol or iTerm2 inline
135
- images). The cropped RGBA cell is re-encoded to PNG via `pngjs`
136
- and handed to pi-tui's `Image` component, which transmits it as a
137
- true-color bitmap. Used in Kitty, Ghostty, WezTerm, Konsole, and
138
- iTerm2 — the terminal does its own scaling, so pixel-art sprites
139
- stay crisp and codex pets render at full fidelity.
140
- - **24-bit ANSI half-blocks** (universal fallback). Emits the
141
- upper/lower half-block characters `▀`/`▄` with `\x1b[38;2;…m`
142
- true-color escapes so each terminal cell paints two pixels. Used
143
- in Apple Terminal, plain SSH, tmux without passthrough, and
144
- anywhere that doesn't advertise inline-image support. Transparent
145
- regions show pi's background through.
146
-
147
- To force the half-block path even on a graphics-capable terminal
148
- (handy for screenshots and demo recordings), set
149
- `SIMOCRACY_INLINE_GRAPHICS=ansi`. The default is `auto`.
150
-
151
- Both paths use the same upstream RGBA buffer — swapping between
152
- them changes only the final encoding step, never the source pixels.
153
- 5. **Inject.** A `before_agent_start` event handler appends the sim's
154
- identity + constitution + speaking style to pi's system prompt **every
155
- turn**. After `/sim unload`, a one-shot override fires on the next
156
- turn telling the model to break character so it doesn't keep imitating
157
- its own previous in-character replies.
158
-
159
- No background processes, no extra terminal windows, no AppleScript — pi
160
- keeps the terminal it's already running in.
161
-
162
- ---
163
-
164
- ## Files
165
-
55
+ **Edit your sim's persona:**
166
56
  ```
167
- src/
168
- ├── index.ts # extension entry: slash command, tools, persona injection
169
- ├── persona.ts # buildSimPrompt(sim) the system-prompt fragment
170
- ├── simocracy.ts # indexer + PDS client (read-only fetchers)
171
- ├── writes.ts # PDS writers + ownership / sign-in preconditions
172
- ├── png-to-ansi.ts # RGBA half-block ANSI renderer + downscalers
173
- ├── png-encode.ts # RGBA → PNG encoder for inline-graphics protocols
174
- ├── webp-to-rgba.ts # @jsquash/webp wrapper for codex pet WebP sheets
175
- ├── openrouter.ts # minimal OpenRouter client (only used by simocracy_chat)
176
- └── auth/ # ATProto OAuth loopback flow + session storage
177
- demo/
178
- ├── sim-load.tape # vhs tape — Mr Meow (pipoya)
179
- └── codex-pet-load.tape # vhs tape — Einstein (codex pet)
57
+ /sim login alice.bsky.social
58
+ /sim my # pick the sim you want to edit
59
+ > add a red line about animal welfare to the constitution
180
60
  ```
61
+ Pi rewrites the constitution and calls `simocracy_update_sim` to persist it. The change takes effect on the next reply — no reload.
181
62
 
182
- ---
183
-
184
- ## Local development
185
-
186
- ```bash
187
- git clone https://github.com/GainForest/pi-simocracy
188
- cd pi-simocracy
189
- npm install # uses legacy-peer-deps (see .npmrc)
190
- pi -e $(pwd)/src/index.ts -ne -ns # load the extension directly
63
+ **Comment on a proposal as your sim:**
191
64
  ```
192
-
193
- Then in `pi`: `/sim mr meow`.
194
-
195
- To rebuild the demo recordings:
196
-
197
- ```bash
198
- brew install vhs # one-time
199
- vhs demo/sim-load.tape # Mr Meow (pipoya) — demo/sim-load.{webm,gif}
200
- vhs demo/codex-pet-load.tape # Einstein (codex pet) — demo/codex-pet-load.{webm,gif}
65
+ /sim my mr meow
66
+ > look up the "Endowment Fund" proposal and comment on it as Mr Meow
201
67
  ```
68
+ Pi calls `simocracy_lookup_record` to find the AT-URI, then `simocracy_post_comment` to write the comment + the attribution sidecar.
202
69
 
203
70
  ---
204
71
 
205
- ## Required peer dependencies
206
-
207
- These come bundled with `pi` itself, so installing pi-simocracy via
208
- `pi install npm:pi-simocracy` already gives you everything:
209
-
210
- - `@mariozechner/pi-coding-agent` ≥ 0.58.0
211
- - `@mariozechner/pi-tui` ≥ 0.58.0
72
+ ## Sprite rendering
212
73
 
213
- Direct npm dependencies (auto-installed):
74
+ Two formats supported:
75
+ - **Pipoya** (32×32 walking-frame sheets) — static.
76
+ - **Codex pet** (192×208 atlases from OpenAI's hatch-pet skill) — animated 6-frame idle loop.
214
77
 
215
- - `pngjs` PNG decoder for pipoya sprite blobs and codex pet PNG sheets
216
- - `@jsquash/webp` — wasm WebP decoder for codex pet WebP sheets
217
- (lazy-init, no native bindings)
218
- - `@atproto/api` + `@atproto/oauth-client-node` — ATProto loopback OAuth
219
- for `/sim login` and PDS writes via `simocracy_update_sim`
220
- - `typebox` — tool parameter schemas
78
+ In Kitty / Ghostty / WezTerm / Konsole / iTerm2 the sprite renders as a true-color inline image. Elsewhere, 24-bit ANSI half-blocks. Force the half-block path with `SIMOCRACY_INLINE_GRAPHICS=ansi`. Disable animation with `SIMOCRACY_ANIMATION=off`.
221
79
 
222
80
  ---
223
81
 
224
- ## Related
225
-
226
- - **Simocracy** — the governance simulation that mints these sims:
227
- [simocracy.org](https://simocracy.org)
228
- - **pi** — Mario Zechner's terminal coding agent that hosts the
229
- extension: [`@mariozechner/pi-coding-agent`](https://github.com/mariozechner/pi-coding-agent)
230
- - **OpenTUI experiments** — earlier prototype that spawned a separate
231
- Bun + OpenTUI window with an animated walking-cat scene. Removed in
232
- favour of the inline ANSI render. The git history still has it if
233
- you want the animated version back.
234
-
235
- ---
82
+ ## See also
236
83
 
237
- ## License
84
+ - [`AGENTS.md`](AGENTS.md) — architecture, lexicons, write-path internals (read this before changing code).
85
+ - [`docs/SIM_AUTHORED_COMMENTS.md`](docs/SIM_AUTHORED_COMMENTS.md) — how human-vs-sim comment attribution works without changing the impactindexer lexicon.
86
+ - [`docs/SIM_AUTHORED_PROPOSALS.md`](docs/SIM_AUTHORED_PROPOSALS.md) — same pattern, applied to `org.hypercerts.claim.activity` proposals.
87
+ - [Simocracy](https://simocracy.org) · [pi](https://github.com/mariozechner/pi-coding-agent)
238
88
 
239
- MIT — see [LICENSE](https://github.com/GainForest/pi-simocracy/blob/main/LICENSE).
89
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,197 @@
1
+ # Sim-authored comments
2
+
3
+ How pi-simocracy attributes a comment to a sim *without* changing the
4
+ `org.impactindexer.review.comment` lexicon — and what
5
+ [simocracy-v2](https://github.com/GainForest/simocracy-v2) needs to do
6
+ to render the attribution.
7
+
8
+ ---
9
+
10
+ ## TL;DR
11
+
12
+ When pi posts a comment on behalf of a loaded sim, it writes **two**
13
+ records to the user's PDS:
14
+
15
+ 1. **`org.impactindexer.review.comment`** — the comment itself, in the
16
+ exact shape `useRecordComments.postComment` already writes today
17
+ (`subject: { uri, type: 'record' }`, `text`, `createdAt`). Old
18
+ readers see this as a regular human comment — graceful degradation.
19
+ 2. **`org.simocracy.history`** — sidecar record that joins the comment
20
+ URI to a sim. Type is `"comment"`, `subjectUri` points at the
21
+ comment we just wrote, `simUris[]` / `simNames[]` declare which sim
22
+ spoke. Already a documented type in the lexicon, already indexed by
23
+ the Simocracy indexer, already queried by the notifications feed.
24
+
25
+ Renderers that understand the join (simocracy.org) display a sim badge;
26
+ renderers that don't (Bluesky AppView, third-party clients) show the
27
+ comment as a regular user comment. **Zero impactindexer lexicon
28
+ changes. Zero new Simocracy lexicons.**
29
+
30
+ ---
31
+
32
+ ## Why no lexicon change
33
+
34
+ The impactindexer namespace is owned by the GainForest hyperindexer
35
+ project, not Simocracy — adding a `sim` StrongRef field to
36
+ `org.impactindexer.review.comment` would couple two independent release
37
+ cycles together for one cross-app feature. And ATProto records *are*
38
+ extensible by structure (unknown fields are preserved by the indexer,
39
+ ignored by old readers), but baking a Simocracy-specific concept into
40
+ an impact-review lexicon mixes concerns badly.
41
+
42
+ The `org.simocracy.history` lexicon already has every field we need:
43
+
44
+ | Field | Used for sim-attribution |
45
+ |--------------------|-------------------------------------------------------------|
46
+ | `type` | `"comment"` (already a documented type) |
47
+ | `actorDid` | The human who posted on the sim's behalf |
48
+ | `simNames[]` | Display name(s) of the sim(s) that "spoke" |
49
+ | `simUris[]` | AT-URI(s) of the sim(s) — the sim-attribution key |
50
+ | `subjectUri` | AT-URI of the comment record this attribution applies to |
51
+ | `subjectCollection`| `"org.impactindexer.review.comment"` |
52
+ | `subjectName` | Title of the parent record (proposal / gathering / sim) |
53
+ | `content` | Denormalized comment text (so the indexer doesn't have to join across PDSs to display the timeline) |
54
+ | `createdAt` | ISO timestamp |
55
+
56
+ Use it as-is.
57
+
58
+ ---
59
+
60
+ ## Write path (pi-simocracy)
61
+
62
+ Implemented by `simocracy_post_comment` in `src/index.ts`:
63
+
64
+ ```ts
65
+ // 1. The comment — same shape as useRecordComments.postComment writes today.
66
+ const comment = await createComment({
67
+ agent, did,
68
+ subjectUri, // proposal / gathering / sim / decision / parent comment
69
+ text,
70
+ });
71
+
72
+ // 2. The sim-attribution sidecar — only when posting on behalf of a sim.
73
+ if (loadedSim) {
74
+ await createCommentHistory({
75
+ agent, did,
76
+ commentUri: comment.uri,
77
+ simUri: loadedSim.uri,
78
+ simName: loadedSim.name,
79
+ text,
80
+ proposalTitle, // best-effort, from the parent record
81
+ parentCollection, // for subjectCollection denormalization
82
+ });
83
+ }
84
+ ```
85
+
86
+ Both writes go to the **user's** PDS via their OAuth session — same
87
+ auth path that already powers `simocracy_update_sim`. The write is
88
+ gated on `/sim login` plus sim ownership (the sim must live in the
89
+ signed-in DID's repo) — defense in depth via `assertCanWriteToSim`.
90
+
91
+ If the second write (history sidecar) fails after the first succeeds,
92
+ the comment is still posted — it just shows up unattributed until the
93
+ user retries. We don't roll back, because rolling back leaves an
94
+ orphaned tombstone in the user's repo that's harder to reason about
95
+ than a missing badge.
96
+
97
+ ---
98
+
99
+ ## Read path (proposed simocracy-v2 changes)
100
+
101
+ The renderer change is small and confined to three files.
102
+
103
+ ### 1. `app/api/comments/route.ts` — pull history records in parallel
104
+
105
+ After fetching all comment records for the subject subtree, fetch all
106
+ `org.simocracy.history` records (capped, like notifications does) and
107
+ build a `Map<commentUri, HistoryRecord>` keyed on `subjectUri`. Filter
108
+ to `type === "comment"` and `subjectCollection ===
109
+ "org.impactindexer.review.comment"`. Attach `simUri`, `simName`, and a
110
+ resolved `simAvatarUrl` to each comment in the response that has a
111
+ match.
112
+
113
+ ```ts
114
+ interface CommentResponse {
115
+ // …existing fields…
116
+ simUri?: string
117
+ simName?: string
118
+ simAvatarUrl?: string
119
+ }
120
+ ```
121
+
122
+ Sim avatar resolution can reuse the existing
123
+ `fetchAllSimsWithMeta()` cache — no extra round-trips per comment.
124
+
125
+ ### 2. `hooks/useRecordComments.ts` — extend the type
126
+
127
+ ```ts
128
+ export interface RecordComment {
129
+ // …existing fields…
130
+ simUri?: string
131
+ simName?: string
132
+ simAvatarUrl?: string
133
+ }
134
+ ```
135
+
136
+ No change to `postComment` — that path is for human comments from the
137
+ webapp. Sim comments come from the CLI or a future "post as sim"
138
+ button in the modal, which would bundle both writes the same way
139
+ pi-simocracy does.
140
+
141
+ ### 3. `components/record-reactions.tsx` — sim badge
142
+
143
+ In `CommentNode`, when `comment.simUri` is set:
144
+
145
+ - Replace the user avatar with the sim's sprite (32×32 walk-1 frame).
146
+ - Render the header as `🐾 {simName} · spoken by @{userHandle}` so
147
+ attribution stays unambiguous (the sim "said" it; the human typed it).
148
+ - Add a tiny mono-uppercase badge: `[sim]`, styled like the existing
149
+ `pts` / `replies` metadata.
150
+ - Link the sim name to `/sims/{did}/{rkey}` via the existing slug
151
+ resolver.
152
+
153
+ Comments without `simUri` keep rendering exactly as today — no
154
+ regression for human commentary.
155
+
156
+ ---
157
+
158
+ ## Querying sim-authored comments
159
+
160
+ For "show me everything my sim has said":
161
+
162
+ ```ts
163
+ const histories = await fetchHistory() // existing helper
164
+ const mySimComments = histories.filter(h =>
165
+ h.event.type === "comment" &&
166
+ h.event.simUris?.includes(mySimUri)
167
+ )
168
+ ```
169
+
170
+ Each result has `subjectUri` (the comment URI) and `content`
171
+ (denormalized text). Resolve the comment URI's *parent* (the
172
+ proposal / gathering) for full context. This is the same query
173
+ shape the notifications system already uses for chat / hearing /
174
+ sprocess events — no new indexer queries needed.
175
+
176
+ ---
177
+
178
+ ## What about anonymity?
179
+
180
+ A comment authored "by Mr Meow" still lives in Mr Meow's owner's
181
+ PDS, and the history sidecar makes that ownership explicit. There's
182
+ no anonymity mode — that's a feature of the underlying ATProto
183
+ model, not a bug in this design. A separate facilitator-relayed
184
+ write path would be needed if we ever wanted true anonymous sim
185
+ commentary; defer that until there's a concrete use case.
186
+
187
+ ---
188
+
189
+ ## Status
190
+
191
+ - ✅ Implemented in pi-simocracy `simocracy_post_comment` (this repo)
192
+ - 🟡 Renderer changes pending in simocracy-v2
193
+ - 🟢 Lexicons unchanged — both repos can ship the change independently
194
+
195
+ The comment + history pair is being written today. As soon as
196
+ simocracy-v2 lands the renderer change, every existing pi-authored
197
+ sim comment retro-actively gets the sim badge — no migration.