Skip to content

Repository files navigation

brain

The orchestration control plane and persistent knowledge base for a fleet of projects driven through Solo under ~/Herd/. This is a metaproject: from here, agents coordinate work across every other project and record what happened so it can be recalled months or years later.

This repository is a template. It ships every workflow — the operating brief, playbooks, skills, standing orders, role bindings, templates, scripts — and none of the data. Clone it into ~/Herd/brain, make your copy private, replace the your-org / example-app seeds in projects.json and clients.json, and start working. Everything under projects/, ideas/, inbox/, deliverables/, and knowledge/ fills up as you go.

Two stores, do not confuse them

  • brain's own Solo data stores (scratchpads / todos / KV scoped to the brain Solo project) = the orchestrator's live working memory — in-flight coordination.
  • This directory on disk = the durable archive and knowledge base. Plain files, version-controllable, outlives any Solo project record. Everything that must survive lives here, never only in Solo's database.

We proved Solo deletes all project-scoped scratchpads/todos/KV when a project is deleted, and that project_id changes on re-add. So Solo state is treated as disposable; disk is the source of truth.

Operating principles

  1. Record early and often. Storage is cheap; forgotten context is expensive. When real work or a real decision happens in any project, write it down here. It is brain's job to remember what the user will forget.
  2. History goes back to the beginning. Logs are append-only. We must be able to answer questions like "9 months ago, why did we decide not to use the default pest timing creation command on project X?" — so capture decisions with their rationale, not just outcomes.
  3. Client filtering must be complete and accurate. Some projects are client work. Client-scoped recall and client-facing proposals depend on an authoritative mapping (see clients.json). Never guess which project belongs to whom.
  4. Disk first, then Solo. For lifecycle operations, do the Solo-side work, confirm it, then update the disk records last — so files never claim a state that did not happen.
  5. Never reference projects by raw ID. IDs change on re-add. Resolve by name via projects.json at action time.
  6. Commit on every meaningful change, straight to main, and push. Lifecycle events, logged work, decision records, and convention changes each get their own commit and an immediate git push. Single user, single machine — keep the remote backup current with no batching or branching ceremony.

Data handling & secrets

Your working copy of this repository should be backed up to a private remote so the knowledge base survives loss of the local machine. Everything written under brain/ is therefore destined for that remote. The rules that follow are non-negotiable:

  1. Never write secrets to the filesystem. No tokens, API keys, passwords, private keys, connection strings, or credentials — ever. There is no legitimate reason to.
  2. Redact on export. When a park exports a project's scratchpads/todos/KV, scan the content for secret-like values first and replace them with [REDACTED:<reason>] before writing the snapshot. Note any redactions in the park log. A secret must never reach disk, even in an archive.
  3. Client data is private. Discovery, notes, and proposals for client projects live in your private repo. Keep the repo private; do not surface client data across clients.
  4. .gitignore is a backstop, not the policy. It excludes common secret file patterns as a safety net, but rule 1 is the real control.

Whether a team centralizes one shared brain or gives each person their own with a sharing bridge is an open question; this template works either way.

Layout

brain/
├── README.md              # this file
├── CLAUDE.md              # operating brief auto-loaded by agents working in brain
├── AGENTS.md              # -> CLAUDE.md (symlink, for harnesses that read AGENTS.md)
├── STANDING-ORDERS.md     # constraints pasted VERBATIM into every worker spawn and resume
├── agents.json            # fleet-wide role -> ordered runtime bindings
├── projects.json          # fleet registry — SOURCE OF TRUTH for every project
├── clients.json           # authoritative client registry (project -> client lives in projects.json)
├── solo.yml               # Solo project config for brain itself (optional processes)
├── playbooks/             # procedures the orchestrator runs (see CLAUDE.md for the index)
├── skills/
│   ├── multi-agent-build/   # canonical feature-build method; symlink into ~/.claude/skills
│   ├── bug-fix-build/       # the bug-fix counterpart (reproduction test, mutation-verified floor)
│   ├── mutation-sweep/      # idle-time maintenance that pays down verification debt
│   └── laravel-cloud-deploy/
├── templates/
│   ├── brief.md           # the brief form every spawned worker gets
│   ├── workflow.md        # the .solo/workflow.md profile template projects copy
│   └── wizard.sh          # bash library for human-in-the-loop wizards (never hand-edited)
├── scripts/
│   ├── log-decision.sh        # append a row to a decision trail (TSV)
│   ├── log-mutation-debt.sh   # append a row to a project's mutation-debt ledger (TSV)
│   ├── pgdb.sh                # deterministic per-worktree Postgres databases
│   └── wizards/               # generated wizards (see playbooks/generate-wizard.md)
├── knowledge/
│   ├── preference-registry/   # confirmed rulings the orchestrator may act on without asking
│   └── remedy-registry/       # confirmed remedies for known failure fingerprints
├── inbox/                 # open failure items awaiting triage (playbooks/drain-inbox.md)
├── ideas/                 # experiment + spike notes, especially the FAILED ones
├── deliverables/          # client-facing reports (playbooks/prepare-client-deliverable.md)
└── projects/
    └── <name>/            # permanent per-project home (exists active OR parked)
        ├── log.md                 # append-only, dated work journal (warm history)
        ├── decisions.md           # decision records: what we chose, what we rejected, WHY
        ├── decisions-<task>.tsv   # per-task decision trails written by workers
        ├── mutation-debt.tsv      # verification-debt ledger (append-only, supersession)
        ├── artifacts/             # evidence workers hand back (review/judge reports, captures)
        └── snapshots/
            └── <YYYY-MM-DD>/      # Solo-state export written on each park (cold history)
                ├── manifest.json
                ├── scratchpads/*.md
                ├── todos.json
                ├── kv.json
                └── prompt-templates.json

projects/<name>/ directories are created lazily — the first time we log work, record a decision, or park the project. Not every project has one yet.

projects.json — the registry

Single source of truth. Every Solo project is always present here (including brain itself), so nothing ever has to ask "is this project defined?". Keep it truthful at every lifecycle event:

Event Update
Create Add entry: status: active, current_id, id_history: [id], client (default = clients.json default_client)
Park status -> parked, remove current_id, set parked_at (ID stays in id_history)
Restore status -> active, set current_id to the NEW id, append it to id_history, set restored_at
Rename / move Update the key and/or path
Permanent delete status -> deleted (tombstone — do NOT remove the key, so historical IDs still resolve)

No-null convention: a field is either present with a real value or omitted — we never store null. So parked_at/restored_at/current_id are absent until the event that sets them occurs. client is never null; it defaults to the registry's default_client. needs is always present as an array (empty by default).

status meaning: active = currently loaded in Solo (has current_id); parked = not currently loaded. A parked entry with an empty id_history and no parked_at has simply never been loaded (e.g. a dependency folder we track but only mount on demand, like a shared package). parked_at is present only when the project was parked via the park playbook (i.e. it had Solo state to archive).

path and repo: path is the project folder relative to ~/Herd (almost always just the folder name) — keeping it relative makes renames and host moves clean. Resolve the absolute path as ~/Herd/<path> wherever a tool needs it (e.g. create_project). repo is the canonical https clone URL, kept for provenance and client re-derivation. Note the registry key (project name) can differ from both the folder (path) and the repo name — e.g. key client-hiring ← folder client-hiring ← repo hiring.client.com.

phase (separate from status): a project's lifecycle stage — pre-launch | launched | maintenance. Set per-project as we engage with it (not guessed in bulk); absent until set. It biases the default feature-build mode (pre-launch → autonomous).

Because IDs are globally unique and monotonic, the union of all id_history arrays gives a reverse id -> project lookup, so any stale ID reference in old archived content still resolves.

brain is in the registry but is never a park target (policy, not a schema flag).

Dependencies (needs)

Each projects.json entry has a needs array: the names of other projects it depends on. Directional — A.needs = ["B"] means "A depends on B". Empty by default; populated as dependencies are revealed during work. Dependencies may point at projects that are not normally loaded (e.g. shared packages mounted only on demand).

Loading (session start). When the user says "let's work on X", ensure X AND its full transitive needs graph are loaded in Solo (load any that are parked), then report what was loaded. See playbooks/work-on.md.

Parking. When asked to park P, after the active-agent gate, resolve dependencies and ASK — never assume:

  • Dependencies of P (P.needs) that are currently active may have been loaded only to support P — ask whether to park them too.
  • Dependents of P (other active projects whose needs include P) would lose their dependency — warn the user and ask how to proceed before parking P.

Clients

clients.json is the client registry: each client has a display_name, an internal flag, and the source_control orgs (host + org) that identify its repos. The default_client is your own internal org. Project→client membership is the client field on each projects.json entry — the single source of truth; derive client→projects by scanning it.

Inferring the client for a new project from its repo:

  1. Read the remote URL (handle both git@host:org/... and https://host/org/...).
  2. Parse host + org; match case-insensitively against clients.json source_control.
  3. On a match, assign that client. On no match, ask the user — do not guess.
  4. A project with no remote defaults to the internal client; flag it to the user.

Do not assume github.com — matching is host-aware (a GitLab client is prospective).

brain itself is explicitly assigned the internal client, independent of where its own repo lives (a personal account is not a client-matching org).

Adding a project from a GitHub URL

Projects are checked out under ~/Herd/. See playbooks/new-project.md for the full procedure. The one rule that is easy to get wrong:

Replace every . in the repo name with _ for the local folder name (outofthe.us -> outofthe_us). If your machine serves each folder as a subdomain under a single-level wildcard cert, a dot in the folder name breaks it; keep the convention even if yours does not, so the fleet stays uniform. The original URL is kept in the entry's repo field; client is inferred from the repo org (ask if unknown).

Briefs and standing orders

Every spawned worker gets a brief written from templates/brief.mdGOAL / SCOPE / CONTEXT / ACCEPTANCE / VERIFY / TIMEBOX / FORBIDDEN / REPORT / STANDING. A field you cannot fill is a task you have not scoped. The brief ends with STANDING-ORDERS.md pasted verbatim, on every spawn and every resume: directives decay across resumes, and a worker cannot ask you a question mid-run. STANDING-ORDERS.md carries only what is true everywhere; project-specific rules live in that project's .solo/workflow.md. The maintenance rule is one line: when you catch yourself restating an instruction in a brief, add it to the register before you act.

Workers keep their decision trail in brain, never in the repo they are working on: projects/<project>/decisions-<task-slug>.tsv via scripts/log-decision.sh. One row per decision or checkpoint, evidence as a pointer (SHA, PR, file:line), append-only.

Feature builds (multi-agent orchestration)

How we build substantial, plan-driven features (and occasionally greenfield MVPs from a PRD). Three layers, by audience:

  1. The method — the multi-agent-build skill (canonical source in brain/skills/, symlinked into ~/.claude/skills/ so every project's agents inherit it). A coordinator agent decomposes the plan and runs a per-PR loop: implement → hard gate → independent quality review + acceptance judge (decorrelated harnesses) → coordinator arbitration → ship, with a 3-attempt bail that hands to the brain orchestrator for assessment before it ever reaches a human.
  2. Project specifics — each project's .solo/workflow.md (template in brain/templates/) carries the gate command, dependency install, role constraints/overrides, merge method, and plan location. Agents read it directly from the repo.
  3. Orchestrationplaybooks/coordinate-feature.md is my role: load the project, ensure the profile exists, confirm the mode, spawn the coordinator, hand off the brief, monitor via idle timers, relay only when the human is tagged in, and harvest the result into the project log.

Two modes (chosen at spawn; I confirm with the user, defaulting per phase):

  • A — autonomous (default): the coordinator drives the whole feature, merging each PR when CI is green. Most common.
  • B — review-each-PR: the coordinator opens each PR and stops; the user merges, then it continues.

Control default: unless the user says they're taking over, I own the agents I spawn and act on the user's behalf (e.g. merge on green CI where that's the policy). Bugs go through the bug-fix-build skill; maintenance goes through mutation-sweep.

Verification debt

Mutation testing is deliberately out of the feature-build loop (it cost too much cadence). Behavioural work floors at test-verified; every criterion that ships below mutation-verified opens a row in projects/<project>/mutation-debt.tsv with an honest risk, and the mutation-sweep skill works that ledger highest-risk-first whenever nothing else is in flight. Bug fixes are exempt: reverting the fix must turn the reproduction test red. CLAUDE.md carries the operating rules; the point is that the trade only holds if the gap is tracked.

Agent roles and runtime binding

agents.json is the fleet-wide source of truth for spawned-agent roles. Each role declares one default runtime and an exact ordered fallback list; each runtime separates harness, provider, lineage, model, and per-launch arguments. Solo agent_tool_id values never appear in the file because they are resolved live from the configured tool name and type. playbooks/resolve-agent-role.md is the mandatory resolution procedure.

The session operating in brain is the brain orchestrator, not a project-worker. Ordinary one-off workers dispatched into another project are not yet a centrally bound role; the first bound roles cover feature builds, mutation maintenance, operational review (manager), and intern-class busywork.

The intern role is permanently multimodal: text and image input are required, and the entire model+harness path must pass a real image probe. Audio and video are recorded as future requirements but are not yet claimed or enforced.

transcriber is a deliberately unbound placeholder for future audio intake. It requires audio input and an actual transcription capability; role resolution stops until a candidate is explicitly selected and verified.

Always pass model AND effort explicitly at spawn. A spawn that relies on a CLI default can come up on a different model than the role names, which makes role resolution meaningless and can turn the reviewer/judge provider diversity into an illusion. Confirming the model from the agent's own banner is a cheap safety net, not the control — and never read it from the command string in list_processes, which shows what was requested, not what started.

Solo workspaces (verified against Solo 0.10.0, 2026-08-30)

Solo groups projects into workspaces. The recommended split is two:

  • the default workspace (id 1) — every fleet project. All dispatch targets live here.
  • a second workspacebrain alone. This is the seat the orchestrator operates from; nothing else belongs in it.

A workspace is a grouping layer for the UI, not an orchestration boundary. Verified by probe, not by reading the docs — the published docs do not answer either question:

  • Cross-workspace reads work: get_project/list_processes with a project_id in workspace 1 answer normally from a session seated in workspace 2.
  • Cross-workspace spawning works: spawn_agent(agent_tool_id, project_id=<workspace-1 project>) from a brain session created a running agent in a workspace-1 project and returned its agent_instructions.

So fan-out dispatch is unaffected. Nothing about the workspace split changes how projects are resolved: list_projects() with no argument still returns the global list across every workspace, which is what projects.json name-resolution relies on. Pass workspace_id only when you deliberately want one workspace.

Never create a project in brain's workspace. create_project with workspace_id omitted imports into the default workspace (1) — not the caller's workspace — so the common path is already safe; verified by creating and deleting a throwaway project and watching workspace 1's count move while workspace 2's held at 1. Do not rely on that default anyway: playbooks/new-project.md passes workspace_id: 1 explicitly, because "the default workspace" is a setting that can be repointed, and a project misfiled into the orchestrator's own seat is exactly the clutter the split exists to prevent.

The Solo message-length ceiling (measured 2026-08-30, Solo 0.10.0)

send_input silently clips the FRONT of a long message into an agent PTY. The tail always survives, so the worker receives something that begins mid-sentence and still reads as a coherent instruction. Nothing reports it: the tool result returns the full byte count as sent. A success response is not evidence of delivery.

Measured against a Claude Code agent, each probe carrying a distinct marker at the front and the back and asking the agent to echo both — only the pair distinguishes a clean delivery from a tail-only survivor:

bytes result
726 intact
1004 intact
1207 clipped — arrived starting mid-sentence, head marker gone
1522 clipped — same signature

Working rule: keep any send_input or timer body under ~1000 bytes. Above that, write the detail to a file and send a pointer.

Two things this is not:

  • Not the PTY write path. The identical oversized payload sent to a plain terminal process delivered every byte (verified on disk, not on screen). The clipping is in how input is fed to an agent TUI's input widget.
  • Not fixed. A short probe passing is exactly how you'd wrongly conclude it was. Anything that claims this ceiling has moved must be re-measured with a front marker; a tail marker alone always looks like success.

Reported to the Solo team via submit_solo_feedback on 2026-08-30 with the bracket above.

Cleanup (keeping Solo tidy)

Solo accretes clutter that buries signal. playbooks/cleanup.md (typically run at the start of the day) tidies the active projects: prunes stale worktrees, closes finished agents, archives old completed scratchpads, and clears old completed todos — leaving a "needs your eyeball" digest of open todos, active scratchpads, and running agents. It is reversible-first and proposes before destroying: worktree prune is automatic; everything destructive is presented for approval; worktrees with uncommitted or unpushed work (and open todos) are never auto-touched; old completed todos are harvested into the project log.md before deletion (Solo has no todo archive). A worktree is removable only when its work is fully in main and the tree is clean — see cleanup.md for the full decision matrix.

Protect tag: scratchpads tagged keep, reference, or plan are never archived by cleanup, regardless of age — use these to pin standing docs (build plans, PRDs).

Decision records

projects/<name>/decisions.md entries should each capture: the date, the decision, the options considered, and the reasoning — especially why the rejected option was rejected. This is the format optimized for the long-horizon "why did we…" question.

Future

When volume justifies it, this file-based store can be backed by Postgres with embeddings for semantic recall. The on-disk markdown/JSON remains the durable source; embeddings are a derived index.

About

A zero-data template for an agent orchestration control plane + persistent knowledge base over a Solo project fleet. All workflows, none of the data.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages