13 Commits

Author SHA1 Message Date
marfrit c00359660e docs: model gateway, wrong-chat-template failure mode, room-ask trust caveat
Three gaps found while onboarding onto this society from cold context.

ORCHESTRATION §4.1 — how a grinder reaches a model. Tiers were documented but
not the path: grinders go through llm-proxy, and the standing ladder lives in
PY_GRIND_TIERS on the unit, not in the ticket. A ticket's tiers= is per-grind
override; the unit is policy. Also names the gateway as the only place to
enforce request-level policy across all clients.

ORCHESTRATION §5 Failure Mode D — a wrong chat template stops an agent
mid-chain. Reads as the model losing the plot, is a tokenizer problem one layer
below the room. Separates the two causes that need different fixes: a template
from the wrong family (configuration, fix with --chat-template-file) versus the
model spelling the marker out as ordinary text (behaviour, mitigate with
stop-strings at the gateway). Includes the tokenise test that tells them apart:
one token = real special token, six = the model typed it.

invoking-from-a-rich-agent §1 — room-ask fails for a PRIVILEGED nick. It posts
through the strict endpoint without the shared secret, so most rich agents get
"unauthorized" from the tool the doc recommends. ORCHESTRATION §3 explains the
gate; this adds the caveat where a reader actually looks for it.

No local hostnames in the new text — the gateway is referenced by repository,
hosts as placeholders.
2026-08-01 12:46:16 +02:00
marfrit 74abc8e1da docs/ORCHESTRATION.md: add §8 Campaign sandbox convention
Add the referenceable sandbox section that the /bullpen/sandbox mneme
namespace (already referenced in §7) points to. Covers the four topics
from the hot-log Open Items:

§8.1 Build-dir convention — /tmp/bullpen-build/<slug> + /tmp/sandbox
    alias, why alias (removes slug-handoff failures), why /tmp not /
§8.2 Host boundary — .runtime files, which agent executes where,
    failure signatures when wrong, pre-flight check
§8.3 Ticket rules — required fields in order (PROBLEM, REPO, TRAPS,
    STEP 1+2, constraints), SPEC requirement
§8.4 Route by test file type — routing table, coordinator wiring
§8.5 Result extraction — where the green artifact lives, how to review

Also updates §7's /bullpen/sandbox entry to point to §8 and deus_ex.md.
2026-07-25 15:59:38 +00:00
marfrit cb66ad0458 consolidate mneme /bullpen namespace: split the undifferentiated dump into 5 typed namespaces
markus asked to consolidate the bullpen namespace in mneme and whether it's well anchored.
It wasn't: bare /bullpen held 39 entries with no internal structure -- 19 raw @callboy
fetch-cache pointers (RSS/heise/lwn URLs), 17 genuine @researcher findings (cited surveys
on multi-agent patterns, hardware questions), and 3 real ops/policy facts (release model,
grinder routing), all landing in the identical namespace because bullpen_mem.py's
save(text, ns="/bullpen") default was never overridden by either caller.

Root-caused and fixed at the call sites (the correct fix, not the shared library default --
callboy and researcher produce different KINDS of content and need different homes):
- bin/bullpen-callboy: remember() now passes ns="/bullpen/artifacts"
- bin/bullpen-researcher: remember() now passes ns="/bullpen/research"
Deployed (both are repo-symlinked system units on hertz; restarted, both active).

Migrated all 39 existing entries via mneme's POST /update {id, ns} (verified prefix-match
semantics first -- ns filtering is `WHERE ns LIKE '<ns>%'`, so @librarian's existing
ns="/bullpen" recall scope still sees everything after the split, nothing broke):
  /bullpen/artifacts  19  (raw fetch-cache pointers, low value beyond dedup)
  /bullpen/research   17  (cited @researcher findings, durable synthesis)
  /bullpen/ops         3  (standing policy: release model, grinder routing)
bare /bullpen: 0 remaining.

docs/ORCHESTRATION.md new S7: documents the now-5-way schema (adds /bullpen/sandbox from
2026-07-24) and, more importantly, the discovery-path split that actually answers "is this
anchored" -- lessons-learned/* auto-injects into every poke via _recall_lessons(); sandbox/
ops/research/artifacts are manual-lookup-only via @librarian and require an explicit
escalation instruction in the CONSUMING agent's prompt (@foreman, Deus) or they're invisible
regardless of how clean the namespace is. Also fixes a stale claim in the same doc found
while making this section a room-hub restart is NOT a "seconds-long blip" for @foreman
specifically (its opencode MCP session goes stale and does not self-heal) -- correction
owed since discovering this live earlier in the same session (deus_ex.md S5), now applied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-25 14:26:09 +02:00
marfrit b594bba104 docs(deus_ex): fixed sandbox alias /tmp/sandbox — no more per-campaign slug in tickets
Repeated handoff failures traced back to the slug in /tmp/bullpen-build/<slug>/:
a ticket with the wrong or missing slug produces DIR NOT FOUND on an otherwise-
correct agent. markus proposed switching the shared basis to "/" so everyone
agrees on one reference point; investigated first since he flagged the exact
risk himself (is "/" the same thing everywhere?).

It is not: boltzmann (the grind host, where @testdesigner now executes per the
earlier host-boundary fix) is BARE METAL (systemd-detect-virt: none) -- the
real physical Pi, which itself hosts other Incus containers. orca (@foreman)
and deus are each their own isolated Incus container filesystem. Three
different roots; giving an unconfined bare-metal agent a "/" mental model is
a real safety risk, not just an inconvenience -- a confused absolute path
could touch the real host instead of a sandbox.

Landed on a fixed SYMLINK instead: /tmp/sandbox -> /tmp/bullpen-build/<slug>,
repointed at the start of each campaign. Solves the actual pain (one name,
never changes, no slug to get wrong) without expanding what any agent can
reach -- stays inside the already-safe /tmp boundary. Verified end-to-end on
boltzmann: opencode reads/writes through the symlink transparently, and the
grinder's os.path.realpath() safety check resolves it correctly.

The slug dir itself is unchanged and still required (real git-tracked archive,
baseline commit, diff target) -- only ticket-facing references (REPO:, SPEC:,
the healthy-chain diagram, the DIR-NOT-FOUND failure signature, the checklist)
now say /tmp/sandbox instead of the slug path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-25 14:11:03 +02:00
marfrit d90f0b87ff docs(deus_ex): state the host boundary — a path is only meaningful with a host
The mneme-fts5 campaign stalled with @testdesigner reporting DIR NOT FOUND for a build
dir that demonstrably existed: the dir was on boltzmann (grind host) while the agent was
executing on orca. The ticket and the path were both correct; the doc simply never said
which host each agent runs on, so "put it in /tmp/bullpen-build/<slug>/" read as one
shared filesystem.

- new §2.1: the `.runtime` file decides the HOST — absent/claude => runs on noether and
  has sic (cross-host reach); `opencode` => runs on LURKER_OC_HOST (default orca),
  sandboxed, sees only that host and auto-rejects external dirs. Flipping .runtime
  silently relocates an agent. Includes the current placement table, the rule (build dir
  must live where the test-writer AND grinder execute), the exact failure signature, and
  the drop-in recipe for moving an opencode nick to another host.
- host-setup caveats learned installing opencode 1.18.4 on boltzmann: symlink the binary
  into /usr/local/bin (the installer only edits ~/.bashrc, which non-interactive sic never
  sources); install oc-run + oc-final-text.py; use a MINIMAL provider-only config — a
  copied full config declares MCP servers (degater/hertz/hub) that block startup on a host
  that lacks them, which presents as a model hang. Bisect model vs harness with a direct
  3-token /chat/completions call. oc-run blocks on an open stdin (add < /dev/null by hand;
  harmless under systemd).
- §3.1 pre-flight: verify the dir from BOTH hosts before posting a ticket.
- unwedging table: two new rows (wrong-host DIR NOT FOUND; opencode hang from absent MCP).
- checklist: host boundary is now the first item.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-24 23:50:33 +02:00
marfrit 7b3a9f671c docs: add deus_ex.md — how to set up and run a bullpen campaign
A reusable playbook for the Deus ex Machina (operator) role, referenceable from any
agent's context. Generalizes what the Asteroids benchmark and the mneme FTS5 initiative
made explicit:

- why the Deus is required at all: @foreman/@testdesigner are sandboxed (room-only, no
  shell), and grinder auto-scaffold only produces empty stubs — so fix-existing-code and
  fixture-bearing campaigns need the operator to place source + a baseline commit
- campaign setup recipe: slug/build dir on the grind host, baseline commit, ticket
  anatomy (PROBLEM / REPO / TRAPS / acceptance contract / constraints), routing by test
  file type with an absolute SPEC: path, posting as a privileged nick
- writing a spec that can't pass wrongly (green != correct): forbid the plausible-but-
  wrong fix, assert observable state change, always require no-regression + migration
- unwedging table (reply-wake, INVALID TICKET, stale MCP session, confabulated blockers)
  plus how to peek inside the shell-less orchestrator with oc-rpc
- sic quick-reference + hard rules (no heredocs, no sic-to-self, remote --user systemd)
- safety: production is never the test target; back up with the service's own mechanism;
  silent-failure surfaces need an explicit post-deploy check; deploy is the operator's call

Companion to docs/ORCHESTRATION.md (mechanics); this is the process.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-24 23:08:26 +02:00
marfrit a850cc95ae bullpen: fix orchestrator reply-wake + JS grinder parser; add onboarding doc
- lurker: opt-in reply-waking via ~/<nick>_lurker/.wakereplies (WAKE_TYPES gains
  "reply"). The default {ask,chat} wake set is reply-deaf, which deadlocks any
  orchestrator running dispatch -> END TURN -> worker-reply-wakes-me (@foreman sat
  waiting for a wake event the lurker filtered away). Loop-safe: from==NICK guard +
  opt-in-only.
- grinder: _resolve_test_rel now matches .test.js / absolute JS spec paths, and
  serve() derives GRIND_REPO from an absolute spec's directory. The JS runner
  (test_kind/run_tests) already existed but the coordinator's ticket parser knew
  only tests/*.py and *_test.go, so every @jsdev ticket was silently bounced.
- docs/ORCHESTRATION.md: agent-readable onboarding for the room/lurker/grinder/
  dispatcher wake + trust protocols, with the three 2026-07-24 failure modes worked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-24 19:03:38 +02:00
Markus Fritsche bf2c9d07f3 add @reviewer (Fable) + refactor lurker into a systemd @-template
The lurker is now one parameterized watcher (bullpen-lurker <nick>) driven by a
systemd template bullpen-lurker@.service; each instance = a nick with its own
~/<nick>_lurker/ context dir and optional .model override. Two instances:
- @herder  (~/herder_lurker, default Claude model) — infra specialist, re-homed.
- @reviewer (~/reviewer_lurker, .model=fable) — outside-look code/design review.
  It IS Fable (claude --model claude-fable-5), reads the target repo via sic,
  returns a ranked findings list. Same locationing discipline as @herder.
Registered @reviewer in the dispatcher (review/critique/implementation keywords,
distinct from @herder host-ops), bp (600s timeout), skills, docs. Adding a role
is now: a context dir + one enabled template instance, no new code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-20 23:33:53 +02:00
Markus Fritsche 4a38b19a21 rename the infra worker nick @his -> @herder (disambiguate)
@his failed all three naming criteria: its a pronoun (dissolves into any
sentence), an existing repo name (a strong model resolved it to the wrong
noun and went spelunking gitea), and semantically empty. @herder is an
unambiguous role noun that ties to /opt/herding (the fleet infra-tooling
root). Renamed across dispatcher roster, bp, skills (noether/pica/pipi/orca),
docs, and the lurker (script/service/dir his_lurker -> herder_lurker). The
his SUBAGENT + /opt/his-context runbook are untouched (thats the real HIS).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-20 23:11:47 +02:00
Markus Fritsche d5c78c3307 register @his in the dispatcher roster + skills
@his (the infra-specialist lurker) was built but never registered, so nothing
could route to it — a request to assess a host fell through to @librarian. Add
it to the dispatcher ROSTER as the heavyweight: action/host-scoped keywords
(assess/audit/reach/wake/set up/configure/marfrit-packages/on <host>) so it is
escalated only for LIVE host work, not memory recall (that stays @librarian).
Verified: the higgs-inventory query now routes to @his; recall + which-host
still go to @librarian. Skills (noether/pica/pipi/orca) + docs table updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-20 22:48:13 +02:00
Markus Fritsche cfc9f82975 tool-hands @callboy (write_file) + bp shorthand + gc retention
- @callboy gains write_file(name, content): jailed to /var/lib/bullpen/hands/,
  returned as a fetchable "sic hertz cat" handle + /bullpen stash index (never
  the raw content). Small content only — big verbatim text should not round-trip
  the 4B. Mechanical allowlist still enforced (fetch_url + write_file).
- bin/bp: REPL shorthand for room-ask (@/# optional, --from default, researcher
  auto-bumps timeout; no-arg = roster).
- bin/bullpen-gc + timer: daily retention, artifacts >14d / hands >7d, scoped to
  the two scratch dirs (never room.jsonl/COUNTER).
- @dispatcher roster: callboy re-described as tool-hands + write/save-file keywords
  so the new capability is discoverable; dropped librarian s collision-prone
  "notes" keyword (matched filenames like notes.md).
- Docs/skill/README updated; bp shorthand + tool-hands noted across surfaces.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-20 19:32:30 +02:00
Markus Fritsche 19bbba7ab7 add @dispatcher: rule-based routing worker (reply-only, no relay)
Reads a request and names the right worker + the exact room-ask to run;
never answers or forwards it, so a misroute costs one wasted hop. Keyword
match first (no LLM slot burned), one LLM tie-break through the gated proxy
only on ambiguity, full roster on weak signal. Strong debate markers route
to @architect/@skeptic and win over an incidental worker-keyword hit. Roster
manifest in the script is the single source of truth. Docs + skill updated
with a start-here-if-unsure pointer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-20 18:55:17 +02:00
Markus Fritsche 7d20986653 docs: add rich-agent invocation guide (three interaction modes)
Written by Fable. Covers visit->ask->leave, direct post/read, and the
new turn-driven conversation-start mode, plus a gotchas section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-20 18:47:40 +02:00