pi-simocracy · training-lab-integration
Three open PRs port Simocracy's web-app Training Lab and Interview flows into the pi-simocracy terminal extension and add a loopback ATProto OAuth login so users can write derived constitutions, training profiles, and merged updates from the CLI.
Headline. All four PRs merged into main. The extension type-checks clean, boots without warnings, and the loopback OAuth flow now runs end-to-end against a real ATProto PDS — captured live in 06-oauth-login.webm below: terminal prints the auth URL → browser grants → /callback hits the loopback server → token exchange → DPoP-bound session persists to ~/.config/pi-simocracy/auth.json. Both findings the original report flagged (auth-command collision, stale help text) shipped as PR #4 and were verified post-merge.
lib/training/prompts.tsRefactor that pulls the five Training-Lab prompt builders out of the Next.js API routes (derive-from-interview, extract-profile, merge-constitution, next-question, alignment-test) into one shared module. 189 + / 174 − across 6 files. This is what the pi extension imports parity from — it lets PR 2 reuse the exact same wording instead of forking it.
Branch: feat/extract-training-prompts · Merge tip: 9015b2022ac7
/sim interview + /sim train (read-only)Adds the full Training-Lab subcommand tree to the pi extension: /sim train baseline · chat · profile · feedback · alignment · apply · status · reset, plus a dedicated /sim interview flow. State persists to ~/.config/pi-simocracy/training-lab.json. 2,573 + / 50 − across 17 files; new src/training/ directory of 13 modules. --apply in this PR is clipboard-only — actual PDS writes land in PR 3.
Branch: feat/training-prompts-shared · Merge tip: eb30cfc28d32
--apply writesAdds /login, /logout, /whoami, a localhost :53682/callback server, DPoP-bound session storage in ~/.config/pi-simocracy/auth.json, and the actual com.atproto.repo.putRecord calls behind /sim train apply --apply and /sim interview --apply. 3,991 + / 50 − across 25 files; new src/auth/ module + src/writes.ts. Uses the same loopback pattern as atproto.com/guides/oauth-cli-tutorial.
Branch: feat/atproto-oauth (branched off PR 2) · Merge tip: 8bffffb8a058
/sim, clarify it's not AnthropicFollow-up that fixes both findings this report originally flagged. Top-level /login / /logout / /whoami moved under the /sim dispatcher (/sim login, etc.) so pi-mono's built-in Anthropic /login no longer collides. The extension's top-of-file docstring + /sim help output rewritten to enumerate every command, grouped (Sim / Training / Sign-in), with an explicit “not Anthropic” note next to the auth section. 155 + / 61 − across 5 files; no new modules.
Merged: 2026-05-02 15:10 UTC · See the Findings section below for the boot-output and /sim help screenshots that verify both fixes landed.
/sim my: list and load sims owned by the signed-in DIDOnce you're signed in via /sim login, /sim my mirrors simocracy.org's My Sims page in the terminal. Three calling conventions: bare /sim my prints a numbered list (newest first, name + createdAt + at://…/<rkey>); /sim my <n> loads the n-th sim; /sim my <name> fuzzy-matches within your own sims using the same scoring as the global indexer search. New fetchSimsForDid() helper in src/simocracy.ts; new dispatcher branch + helpers in src/index.ts. 218 + / 1 − across 2 files; no new modules. Captured live below against @satyam2.climateai.org.
Merged: 2026-05-02 15:26 UTC
One short VHS tape: boot pi -e $(pwd)/src/index.ts -ne -ns, then exercise the new commands' built-in help paths (/sim help, /sim train, /login with no args). The boot output is the most informative frame — it lists the loaded extension and originally surfaced two warnings worth flagging (since fixed; see findings).
tape: assets/01-help-and-tools.tape · render: vhs assets/01-help-and-tools.tape
Live capture of /sim login satyam2.climateai.org against a real PDS, recorded after PR #4 merged into main. The 75-second middle section is the user granting the OAuth request in their browser; pi's terminal sits idle on the loopback server while the browser-side handshake runs, then the callback fires and the success message lands instantly:
tape: assets/06-oauth-login.tape · render: vhs assets/06-oauth-login.tape (with a real browser available to grant the consent)
t ≈ 78s: post-callback success message. The handle resolved to did:plc:cpoagodpqrgs4t7thi5z37uf and the DPoP-bound session was persisted to ~/.config/pi-simocracy/auth.json. The shell prompt confirms we're on main — i.e. running with all four PRs merged in.Verified: ~/.config/pi-simocracy/auth.json contained {"did": "did:plc:cpoagodpqrgs4t7thi5z37uf", "handle": "satyam2.climateai.org", "lastLogin": "2026-05-02T15:13:45.349Z"} immediately after the recording — i.e. the session is live and the next /sim interview --apply or /sim train apply --apply would write straight to that account's PDS.
/sim my: browse your own simsSame auth.json carries straight into the next session. /sim my reads the signed-in DID's org.simocracy.sim records via com.atproto.repo.listRecords and prints the numbered list directly in the terminal — the analogue of simocracy.org/my-sims:
tape: assets/07-my-sims.tape
/sim my output frame. Two sims surface from satyam2's repo — Nyaori Katalog (at://…/3mjv35oolh22e) and Nyariko (at://…/3mjv32a5iwc2e) — sorted by createdAt descending. From here, /sim my 1 loads Nyaori Katalog with the same hydrate path the global /sim <name> uses; /sim my nya would prefix-match both, so the picker prompts.The end-to-end OAuth flow can't be cleanly captured in CI without a real test handle and a real browser-grant click, so we document the wiring instead. The CLI side mirrors the same DPoP-bound session machinery the simocracy.org web app uses; it follows the canonical CLI tutorial at atproto.com/guides/oauth-cli-tutorial.
pi (CLI · ~/.config/pi-simocracy) Bluesky / user PDS
──────────────────────────── ──────────────────
│
1. /login alice.bsky.social │
2. start localhost server on 127.0.0.1:53682 │
3. open authorize URL ────────────────────────► │
│
4. user grants in browser │
◄─── redirect ?code=… ───────────┤
│
5. POST /callback?code=… (loopback hits self) │
6. exchange code → DPoP-bound access + refresh │
7. save session → ~/.config/pi-simocracy/auth.json │
│
8. /sim train apply --apply │
9. com.atproto.repo.putRecord ──────────────────────►│
│ collection: org.simocracy.agents │
│ rkey: self │
│ value: { merged constitution, … } │
│
10. /sim interview --apply │
11. com.atproto.repo.putRecord ──────────────────────►│
│ collection: org.simocracy.interview │
│ rkey: tid │
│ value: { openAnswers, derivedAt, … } │
app/api/derive-from-interview/route.ts | 33 +---- app/api/training/alignment-test/route.ts | 7 +- app/api/training/extract-profile/route.ts | 25 +--- app/api/training/merge-constitution/route.ts | 61 +-------- app/api/training/next-question/route.ts | 58 +-------- lib/training/prompts.ts | 179 +++++++++++++++++++++++++++ 6 files changed, 189 insertions(+), 174 deletions(-)
src/index.ts | 392 +++++++++++++++++++++++++++++++++++------ src/interview.ts | 358 +++++++++++++++++++++++++++++++++++++ src/openrouter.ts | 16 ++ src/persona.ts | 60 +++++++ src/simocracy.ts | 90 ++++++++++ src/training/alignment.ts | 259 +++++++++++++++++++++++++++ src/training/apply.ts | 171 ++++++++++++++++++ src/training/baseline.ts | 159 +++++++++++++++++ src/training/chat.ts | 131 ++++++++++++++ src/training/feedback.ts | 81 +++++++++ src/training/index.ts | 140 +++++++++++++++ src/training/profile.ts | 229 ++++++++++++++++++++++++ src/training/prompt-helpers.ts | 70 ++++++++ src/training/prompts.ts | 131 ++++++++++++++ src/training/question-set.ts | 134 ++++++++++++++ src/training/storage.ts | 81 +++++++++ src/training/types.ts | 121 +++++++++++++ 17 files changed, 2573 insertions(+), 50 deletions(-)
package-lock.json | 373 +++++++++++++++++++++++++++++ package.json | 2 + src/auth/callback-server.ts | 93 ++++++++ src/auth/commands.ts | 142 ++++++++++++ src/auth/oauth.ts | 55 +++++ src/auth/pages.ts | 100 ++++++++ src/auth/storage.ts | 121 ++++++++++ src/index.ts | 419 +++++++++++++++++++++++++++++---- src/interview.ts | 516 +++++++++++++++++++++++++++++++++++++++++ src/openrouter.ts | 16 ++ src/persona.ts | 60 +++++ src/simocracy.ts | 90 +++++++ src/training/alignment.ts | 259 +++++++++++++++++++++ src/training/apply.ts | 271 ++++++++++++++++++++++ src/training/baseline.ts | 159 +++++++++++++ src/training/chat.ts | 131 +++++++++++ src/training/feedback.ts | 81 +++++++ src/training/index.ts | 142 ++++++++++++ src/training/profile.ts | 229 ++++++++++++++++++ src/training/prompt-helpers.ts | 70 ++++++ src/training/prompts.ts | 131 +++++++++++ src/training/question-set.ts | 134 +++++++++++ src/training/storage.ts | 81 +++++++ src/training/types.ts | 121 ++++++++++ src/writes.ts | 245 +++++++++++++++++++ 25 files changed, 3991 insertions(+), 50 deletions(-)
npx tsc --noEmit passes clean on feat/atproto-oauth with all three PRs stacked. Each PR is open on GitHub against main with non-empty diffs and a sensible title; PR 3 explicitly branches off PR 2 (feat/atproto-oauth ← feat/training-prompts-shared) so the dependency is reflected in the git graph rather than just in commit messages.
src/auth/oauth.ts calls into buildAtprotoLoopbackClientMetadata and uses the same DPoP-bound session machinery the simocracy.org web app already ships. The CLI's contribution is the localhost callback server (src/auth/callback-server.ts, 93 lines) and a small token storage layer (src/auth/storage.ts, 121 lines) — no protocol divergence, just a CLI surface over the same primitives.
The pre-existing /sim <name> load flow (which is the foundation PR 2 and PR 3 build on) was already verified live in the prior simocracy-v2/sim-load-pi-extension run. PR 2's read-only Training Lab subcommands (/sim train baseline, /sim train status, /sim train chat) reuse the same indexer client, so the read path is contiguous with that earlier evidence.
/login / /logout collision — namespaced under /simOriginally flagged here as a panel-warn caveat. PR #3 ports the three handlers (runLogin, runLogout, runWhoami) into the existing /sim dispatcher and removes the colliding top-level registrations. Boot output is now clean — no more [Extension issues] block:
feat/atproto-login-rename. The two-line Extension command '/login' conflicts … warning is gone.Beyond fixing the autocomplete warning, the rename also makes it unambiguous to users that they're signing in with ATProto / Bluesky, not Anthropic. Pi-mono itself ships a /login command that runs the Anthropic Claude OAuth flow (the same one the pi-mono README documents) — so a top-level /login registered by an extension was always going to be confusing even if the conflict warning hadn't existed. New surface:
/login → /sim login [handle] /logout → /sim logout /whoami → /sim whoami
UI strings updated to match: the prompt now reads "Sign in with ATProto / Bluesky — your handle", the success message reads "🔐 Signed in to ATProto as @alice (did:plc:…). You can now use /sim interview --apply and /sim train apply --apply to write to your PDS.", and every error message that used to say "Run /login" now says "Run `/sim login <handle>` first (e.g. `/sim login alice.bsky.social`). This is separate from pi's built-in `/login` (Anthropic)."
The top-of-file JSDoc that pi surfaces to the model when asked "what does this extension do?" was rewritten to enumerate every command grouped by intent (Sim / Constitution training / Sign-in), plus the four new LLM-callable tools. The interactive /sim help output mirrors the same grouping in plaintext. Captured frame:
/sim help on the new branch — every command, grouped by intent, with an explicit “not Anthropic” note next to the auth section.The original report flagged “end-to-end OAuth flow not verified in CI — no test handle.” After PR #4 merged we re-ran with satyam2.climateai.org and captured the full flow in 06-oauth-login.webm above. The VHS “deadlocks on network-bound pi sessions” limitation only applies to tapes that try to drive a network call inside the recording with no human in the loop — the OAuth tape works because the 75-second sleep gives the user time to grant in their actual browser, and the callback resolves the loopback server's waitForParams() promise instantly. 02-sim-load.tape remains in assets/ for a developer who wants to repro the indexer fetch interactively, but it is not a CI artifact.
Documented in the PR description: the CLI's --apply path does not yet round-trip the markdown-with-facets formatting that the web app's editor produces. Records land at org.simocracy.agents/self and org.simocracy.interview/<tid> with the right structure, but rich-text annotations are flattened to plain markdown source. Tracked for a follow-up; not a regression.
/sim train alignment fans out 8 LLM calls (one per dimension of the 8-axis alignment test) on every run. We did not record a live alignment session in this report — both for cost reasons and because the spread of latencies makes it a poor candidate for a deterministic VHS tape. The wiring is verified via tsc + diff-stat; the LLM behaviour is identical to the web app since both now import from the same lib/training/prompts.ts (PR 1).
gainforest/simocracy-v2 @ feat/extract-training-prompts (9015b2022ac7)
pi-simocracy (PR 2)gainforest/pi-simocracy @ feat/training-prompts-shared (eb30cfc28d32)
pi-simocracy (PR 3)gainforest/pi-simocracy @ feat/atproto-oauth (8bffffb8a058) — branched off PR 2
Build (pi-simocracy)npm install && npx tsc --noEmit
Run extensionpi -e $(pwd)/src/index.ts -ne -ns
First commands/sim mr meow · /sim train baseline · /sim interview · /login <handle>.bsky.social
Tapeassets/01-help-and-tools.tape · render with vhs
Manual repro (skipped tape)assets/02-sim-load.tape — run interactively, not via vhs
PR URLs
simocracy-v2#1 ·
pi-simocracy#1 ·
pi-simocracy#2