pi-simocracy · post-skill-smoke-test
A new tool was added to pi-simocracy that lets a loaded sim publish an Anthropic-style agent skill (org.simocracy.skill) plus a sim-attribution history sidecar. This run drives the tool through a real pi session, verifies the skill renders on simocracy.org, then cleans up so production is left exactly as it was found.
Headline. The tool works end-to-end. Pi loaded a sim, called simocracy_post_skill, and the resulting org.simocracy.skill record landed on the user's PDS, was picked up by the Simocracy indexer within seconds, rendered as a card on simocracy.org/skills, served the reconstructed SKILL.md at the canonical /skill.md route, and was paired with an org.simocracy.history sim-attribution sidecar exactly as designed. After the verification screenshots were captured, both records were deleted via the same OAuth session; the PDS reports RecordNotFound, the indexer returns zero matches, the skill page is now HTTP 404, and the public gallery reads 0 skills · No skills found.
The new tool registered in pi-simocracy@0.7.0:
simocracy_post_skill
Records writtenorg.simocracy.skill + org.simocracy.history (type="skill")
Auth gating/sim login + sim ownership (same as simocracy_post_comment / _post_proposal)
Wire shapeidentical to simocracy.org's SkillFormDialog — no new lexicon
Design docdocs/SIM_AUTHORED_SKILLS.md
Pi was driven non-interactively with a tool allowlist of just simocracy_load_sim + simocracy_post_skill:
pi --no-extensions -e $(pwd)/src/index.ts --print --no-session \
--no-context-files --no-skills \
-t "simocracy_load_sim,simocracy_post_skill" \
"$(cat /tmp/pi-skill-smoke/prompt.txt)"
The prompt asked Habermas to publish a real, content-bearing SKILL.md for evaluating the quality of deliberation on a Simocracy proposal — chosen because Habermas is the obvious sim to opine on what counts as healthy debate. Pi returned DONE after both tool calls completed.
simocracy.org/skills gallery — the new skill renders as a regular card identical to one written through the webapp's SkillFormDialog. No "posted by sim" badge yet (renderer change is pending in simocracy-v2 — see design doc), but the underlying org.simocracy.history sidecar is already on the user's PDS waiting for it.
/skills/<did>/<rkey> — title, description, and the rendered SKILL.md reconstructed from the lexicon's separate name + description + body fields. The Copy SKILL.md button works.
/skills/<did>/<rkey>/skill.md route returns the file as text/markdown, ready to be loaded into any agent harness (Anthropic skills CLI, custom runtimes, …) — the whole point of the lexicon split. Frontmatter name + description are rebuilt from the structured fields; everything below the closing --- is the verbatim body.
HTTP 404 Page not found — the com.atproto.repo.deleteRecord call propagated through the indexer to the public renderer.
Six independent signals were checked across the create + cleanup phases. All twelve cells matched expectation:
| Signal | Endpoint | Pre-cleanup | Post-cleanup |
|---|---|---|---|
| Skill record on PDS | com.atproto.repo.getRecord |
200, full record | RecordNotFound |
| History sidecar on PDS | com.atproto.repo.getRecord |
200, type="skill" |
RecordNotFound |
| Skill on Simocracy indexer | GraphQL records(collection: "org.simocracy.skill") |
1 match | 0 matches |
| History sidecar on indexer | GraphQL records(collection: "org.simocracy.history") |
1 match (type="skill") |
0 matches |
| Public skill page | GET /skills/<did>/<rkey> |
HTTP 200, ~41 KB | HTTP 404 |
| Reconstructed SKILL.md | GET /skills/<did>/<rkey>/skill.md |
HTTP 200, 1879 B markdown | HTTP 404 |
Snapshot of the live skill record from the user's PDS during the run:
$ curl "$PDS/xrpc/com.atproto.repo.getRecord?repo=$DID&collection=org.simocracy.skill&rkey=3mldd5xl3w225" | jq
{
"uri": "at://did:plc:qc42fmqqlsmdq7jiypiiigww/org.simocracy.skill/3mldd5xl3w225",
"cid": "bafyreig...",
"value": {
"$type": "org.simocracy.skill",
"name": "deliberation-quality-check",
"description": "Evaluate whether a Simocracy proposal discussion shows healthy deliberation. …",
"body": "# Deliberation quality check\n\nRun this skill when you need to assess …",
"createdAt": "2026-05-08T08:42:…Z"
}
}
And the matching history sidecar — note type: "skill" and subjectCollection: "org.simocracy.skill", which is what a future renderer change in simocracy-v2 needs to join sim attribution onto skill cards:
{
"value": {
"$type": "org.simocracy.history",
"type": "skill",
"actorDid": "did:plc:qc42fmqqlsmdq7jiypiiigww",
"simNames": ["Habermas"],
"simUris": ["at://did:plc:qc42fmqqlsmdq7jiypiiigww/org.simocracy.sim/3mkwvrnm5it2d"],
"subjectUri": "at://did:plc:qc42fmqqlsmdq7jiypiiigww/org.simocracy.skill/3mldd5xl3w225",
"subjectCollection": "org.simocracy.skill",
"subjectName": "deliberation-quality-check",
"content": "Evaluate whether a Simocracy proposal discussion shows healthy deliberation. …",
"createdAt": "2026-05-08T08:42:…Z"
}
}
Pi loaded the Habermas sim, called simocracy_post_skill with the LLM-authored skill body, and both writes succeeded with no warnings. The sidecar attribution path matches the existing comment / proposal patterns line-for-line — same org.simocracy.history lexicon, same actorDid + simUris[] + subjectUri shape, just type: "skill".
The lexicon stores frontmatter (name, description) and body as separate fields so the indexer can filter on them cheaply. simocracy.org's /skill.md route reconstructs the canonical Anthropic SKILL.md with YAML frontmatter at serve time. Verified the output in screenshot 03 — frontmatter is intact, body is verbatim, ready to be loaded into any agent harness.
The skill card in screenshot 01 renders identically to a human-authored skill — there is no "🐾 written by Habermas" badge yet. That's by design: the org.simocracy.history sidecar carries the attribution, but the skills gallery's fetchSkills query in simocracy-v2 doesn't yet join history records onto skill records. Same gap exists today for sim-authored proposals (the proposal page does join, the skill page does not). The design doc SIM_AUTHORED_SKILLS.md §Read path spells out the renderer change. Pi-simocracy is already writing the data the renderer needs — graceful degradation until the join lands.
type: "skill" on the history lexicon without changesThe history.json lexicon comments that type is free-form string and new event types are appended over time. Confirmed: the indexer ingested the new type: "skill" history record and returned it via GraphQL with no error, no warning, and no schema change required. chat, sprocess, delete, comment, proposal, and now skill all coexist in the same collection.
Both writes were rolled back via com.atproto.repo.deleteRecord using the same OAuth session pi-simocracy uses to write. All seven post-cleanup checks (PDS getRecord ×2, PDS listRecords ×2, indexer GraphQL, public skill page, public gallery search) pass. Production is left exactly as it was found, satisfying the §0 rule of the simocracy-smoke-test skill.
The smoke test ran against daviddao.org's real production PDS — the existing pi-simocracy OAuth session at ~/.config/pi-simocracy/auth.json was reused, no new account was created. Both records were deleted in the same agent session immediately after verification.
pi-simocracy @ working tree, post-tool-add commit
Tool entrysrc/index.ts → simocracy_post_skill
Writerssrc/writes.ts → createSkill, createSkillHistory
Sim usedHabermas (at://…/org.simocracy.sim/3mkwvrnm5it2d)
Skill rkey3mldd5xl3w225 (deleted)
History rkey3mldd5xtrjs2f (deleted)
Cleanup scriptcleanup.ts — deletes both rkeys via the same OAuth session
Reconstructed SKILL.mdskill.md — captured from the live /skill.md route
Run command:
cd ~/Projects/pi-simocracy
pi --no-extensions -e $(pwd)/src/index.ts --print --no-session \
--no-context-files --no-skills \
-t "simocracy_load_sim,simocracy_post_skill" \
"$(cat /tmp/pi-skill-smoke/prompt.txt)"
Cleanup:
node --experimental-strip-types /tmp/pi-skill-smoke/cleanup.ts