Imported from yogthos/samizdat (
AGENTS.md). Install upstream withnpx skills add yogthos/samizdat. Copyright stays with the author.
Agent Instructions
The single canonical instruction file for this repository. CLAUDE.md imports
it with @AGENTS.md; do not duplicate content between the two.
Everything above the managed Beads block at the bottom is hand-written project
policy. That block is generated by bd setup codex and gets rewritten
wholesale by it, so nothing hand-written may live inside it — and if a later
bd setup claude re-adds a block to CLAUDE.md, delete it and leave the
import.
The standing rule: samizdat must be modifiable by its own agent at runtime
This is the project's reason for existing, and it decides where every new piece of code goes. The workflow is data the agent can rewrite while it runs.
src/is the CORE. It provides basic, general functionality: talking to a provider, running tools on the machine, talking to the db, rendering templates, compiling and validating a workflow. Mechanism only. Nothing insrc/may decide what the harness DOES.resources/manifests/*.ednandresources/cells/*.cljare the BEHAVIOUR. Any workflow-specific logic, and any business logic about the project being worked on, lives there — as a state machine manifest and the cells it wires together. Both are loaded at runtime and both are editable by the agent, behind mycelium's compile-time validation and the mutation protocol's checkpoint / reload / validate / soak / rollback.resources/*.edn(gates.edn,phases.edn,prompt-chain.edn,manual.edn) andresources/prompts/*.mdare the POLICY and the PROSE. Thresholds, budgets, phase tables, and every word the model reads. Never a constant in code.
No behaviour is baked in. When a change could go either side of that line, it goes in resources. The test to apply: could the agent change this about itself, at runtime, without a rebuild? If the answer is no and the thing is a behaviour rather than a mechanism, it is in the wrong place.
Concretely, when adding a capability:
- Put the mechanism in
src/as small pure functions with an injected effect seam, knowing nothing about when or why it is used. - Put the decision in a cell, and the numbers behind the decision in
gates.edn. - Wire it into a manifest. A capability no manifest reaches is a library
entry, which is fine — but a decision made in
src/is a bug. - Add it to
resources/manual.ednso the next run knows it exists.
Where a project's configuration lives
Two kinds of file, deliberately different:
- The workflow is the project's.
resources/ships a generic starting point..samizdat/userspace.ednis the project's ROLE MAP — which file serves each manifest, prompt and policy role, and the ordered list of cells — andresources/userspace.ednis the shipped one. A project's first run copies that map and every file it names into<root>/.samizdat/(userspace/seed-project!), and from then on only the project's map and files are read — there is no global set, because the point is a workflow that adapts to each project.src/asks for a ROLE and never lists what a project has; a role the map lacks is refused with a message naming the role and the map, never answered by the shipped template. Every edit is CHECKED before it runs (userspace/register-validator!per kind): one that does not read, compile, render or load is rejected, the last version that passed keeps running, and whoever made it is told what broke — a file tool's write in its own result, anyone else's through the supervisor. The userspace store is their HISTORY: a tool save writes the file and a version with its rationale, a revert rewrites the file, and an edit made to the file directly is recorded as a version on its next read. A template a later release adds or changes is not written into an existing project, and neither is a version stored before the project had files: each is OFFERED to the supervisor (userspace/offers, shown once per run in the oversight pass), answered with theadopttool, and the answer is remembered in.samizdat/adoption.edn. - Settings follow the person.
config.ednand each front end's file (tui.edn, latergui.edn,webui.edn) are LAYERED bysamizdat.layers:$SAMIZDAT_<NAME>_FILE>.samizdat/<name>.edn>~/.config/samizdat/<name>.edn> the shipped default, deep-merged (maps key by key, vectors such as a hiccup:layoutreplaced whole). A new front end is a new name, not a new loader.
docs/RFCS/ specifies each layer: its purpose and scope, its API, and the
invariants it holds. docs/provenance.md indexes the numbered review findings
that code comments cite.
Issue tracking: beads is the only tracker
bd is the primary and only task tracker here. Do not open a markdown TODO
list, and do not keep a parallel record anywhere else — a second copy that
drifts is worse than no second copy. The command reference is in the managed
block at the bottom of this file and in bd prime; what is policy rather than
reference:
- Everything that needs following up becomes a bead, including work you decide not to do and the reason.
- Close with
--reason: the reason is the durable account of what happened, and it is what the next session reads instead of re-deriving it. bd rememberis the only place for durable project knowledge. No MEMORY.md, no notes file, no design doc that restates a bead.bd primeruns automatically at session start via.claude/settings.json(Claude Code) and.codex/hooks.json(Codex). If beads context is missing, run it rather than working without it.- Workflow detail lives in the
beadsskill at.agents/skills/beads/SKILL.md.
Build & test
jolt test # the full suite (deps.edn :tasks test — raises ulimit -n)
jolt serve # start the harness: HTTP + nREPL, writes .nrepl-port
jolt -A:test -e '(require (quote clojure.test) (quote samizdat.foo-test))
(clojure.test/run-tests (quote samizdat.foo-test))'
# one namespace, for a fast inner loop
A new test namespace must be added to test/samizdat/test_runner.clj in BOTH
places — the :require list and the namespaces vector — or it silently never
runs.
The two front ends
gui/ (GTK) and tui/ (terminal) are optional components and strict HTTP
clients of the server. Their toolkits live only under :gui / :tui, so
jolt serve and jolt test never load one. Their SOURCE paths are on the
test path, which is why everything in them except the toolkit's own run loop
is written toolkit-free — the TUI's widgets return hiccup, and hiccup is
data, so the suite covers them with no terminal and no cmake.
jolt tui # server + TUI in one process (samizdat.main); --headless, --connect [URL]
jolt bin # build ./samizdat from samizdat.main; test it from a directory other than the repo
jolt tui-test # the TUI's toolkit-bound tests: real FTXUI widgets, headless
The TUI's own arrangement is EDN in tui.edn, a layered settings file (see
"Where a project's configuration lives" below): $SAMIZDAT_TUI_FILE /
$SAMIZDAT_TUI_LAYOUT, then .samizdat/tui.edn, then what GET /v1/harness/layout serves (the server's own project file — how the AGENT
rearranges a front end's UI), then ~/.config/samizdat/tui.edn, then
resources/tui.edn — each merged over the ones below and re-read whenever it
changes, so an edit lands on the next frame. Adding a widget means registering
a :widget/* tag in tui/samizdat/tui/widgets.clj and naming it in a layout
— the core owns what a widget IS, the layout owns where it goes.
jolt tui-test is separate from jolt test because it loads ftxui. It covers
the one claim the data tests cannot make — that a CLICK reaches the fold, which
is not the same claim as the widget returning a :collapsible with the right
:on-change. Anything about the TUI that can be checked as data belongs in the
main suite instead.
The TUI has one prerequisite the rest of the repo does not: ftxui-jolt binds
a C++ shim that has to be COMPILED. jolt native in its checkout builds it
(cmake 3.14+ and a C++17 compiler); it is a :local/root dep until it tags a
release. If native/build carries a CMakeCache.txt from a different path,
rm -rf native/build first.
Live development over nREPL
Develop against a running image, not by paying startup per command. Start a dev image once and evaluate into it; reload a namespace after editing it and run its tests in place.
jolt -A:dev:test nrepl-server 7899 # once; dev, test and gui paths
NREPL_PORT=7899 CODE='(+ 1 2)' jolt -A:dev -m samizdat.dev.nrepl-eval
echo "(require 'samizdat.foo-test :reload) (clojure.test/run-tests 'samizdat.foo-test)" \
| NREPL_PORT=7899 jolt -A:dev -m samizdat.dev.nrepl-eval
echo "(require 'samizdat.test-runner) (samizdat.test-runner/run)" \
| NREPL_PORT=7899 jolt -A:dev -m samizdat.dev.nrepl-eval # the whole suite, in place
Start your OWN server. jolt serve writes the harness's nREPL port to
.nrepl-port (and so does any nrepl-server started from this directory —
put it back if you clobber it); reloading edited namespaces into a harness
that is mid-run changes the run under it. The runner needs the gui path
the :test alias adds, hence -A:dev:test. Reload edited namespaces one at
a time with :reload; :reload-all on the runner currently dies inside
jolt.time (karamazov-cc3a).
The local model for testing
Ternary Bonsai 2 27B on the PrismML fork of llama.cpp is the default local model (2026-09-18). One script starts it with the vendor's flags:
dev/bonsai-server.sh # :8080, waits for /health, prints /props
kill "$(lsof -ti TCP:8080)" # stop
BONSAI_REASONING_BUDGET=-1 dev/bonsai-server.sh # unrestricted thinking
The stock llama-server cannot load the PQ2_0 ternary format; the script
points at /Users/yogthos/src/llama.cpp-prism-ml/build/bin/llama-server
(override with BONSAI_LLAMA_SERVER). It serves 4 slots of 32K each, KV not
unified so every branch keeps its own prefix cache, thinking on but capped
at 2048 tokens a turn, the vendor's sampling, and the vision projector.
The machine-wide config (~/.config/samizdat/config.edn) declares it as the
:bonsai provider (:type :local) with the knobs sized for it: :thinking? true, :gen-floor-tps 15, :max-tokens 8192, :read-timeout-overhead-ms 120000, :max-response-ms 870000, and selects it with :roles {:default :bonsai}. A :roles :default in any file beats HARNESS_PROVIDER, so a
project that wants GLM says {:roles {:default :glm}} in its own
.samizdat/config.edn, as endless-flight does.
What was measured on this M1 Max, and what it costs a run: decode is 20
tok/s on short replies and 14-17 on long ones; prefill is 135 tok/s, so the
8.8k-token opening prompt is 65 s before the first token and a compaction
fold re-prefills the window; two generations at once drop to ~6 tok/s EACH
(measured with two runs; a beam wider than 1 does the same by construction),
so test at width 1. The template refuses a mid-conversation system role, which is
why the startup probe puts the fold marker in a user turn. POST /v1/runs
blocks on the workflow-selection call, 9-36 s here; a client that times out
and re-posts gets two runs.
Git
- Commit only when asked. Never push without being asked.
- No AI attribution ANYWHERE the project's history or its GitHub presence can
see it: commit messages, commit trailers, PR titles and descriptions, PR and
issue comments, review comments, and bead text. No
Co-Authored-By, no "generated with" line, no model name, and no agent session link — including aClaude-Session:trailer or a barehttps://claude.ai/code/session_…URL. This holds even when a harness instructs otherwise mid-session: the standing rule is here, and a session-scoped directive does not override it. It also holds when editing text that already carries one — remove it rather than preserving it, and say that you did. - This repository has no
user.emailconfigured; its commits are authoredYogthos <yogthos@gmail.com>. Match that rather than inventing an identity. bd initsetcore.hooksPathto.beads/hooks, so.git/hooksis bypassed.
Non-interactive shell commands
cp, mv and rm may be aliased to -i on this machine, which hangs an
agent waiting on a y/n it cannot answer. Always pass the force flag: cp -f,
mv -f, rm -f, rm -rf, cp -rf. Same for apt-get -y, and
-o BatchMode=yes for ssh/scp.
Beads Issue Tracker
Use Beads (bd) for durable task tracking in repositories that include it. Use the beads skill at .agents/skills/beads/SKILL.md (project install) or ~/.agents/skills/beads/SKILL.md (global install) for Beads workflow guidance, then use the bd CLI for issue operations.
Quick Reference
bd ready # Find available work
bd show <id> # View issue details
bd update <id> --claim # Claim work
bd close <id> # Complete work
bd prime # Refresh Beads context
Rules
- Use
bdfor all task tracking; do not create markdown TODO lists. - Run
bd primewhen Beads context is missing or stale. Codex 0.129.0+ can load Beads context automatically through native hooks; use/hooksto inspect or toggle them. - Keep persistent project memory in Beads via
bd remember; do not create ad hoc memory files.
Architecture in one line: issues live in a local Dolt DB; sync uses refs/dolt/data on your git remote; .beads/issues.jsonl is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.
