Councils
A council is a small set of agents brought together to converge on a question over multiple rounds. The state lives under .harnery/councils/<id>/: a manifest file plus per-round contributions, each a markdown file written by one agent.
Single-agent decisions are fast but narrow. A council brings two to six agents into a round-robin, lets each one read the previous round’s contributions before writing their own, and converges naturally when no one has anything substantive to add for two rounds.
Use it to review the design of a hot-path migration, pick between two refactor approaches, or audit a security-sensitive boundary: anywhere a single agent’s first instinct would benefit from being argued against before it gets committed.
Lifecycle
Section titled “Lifecycle”# Create a council with an objective + named members.harn council create "Pick the strangler-fig order" \ --member Alpha --member Beta --member Gamma \ --target-doc docs/migration-plan.md
# Each member reads the invite + prior rounds, then writes their contribution:# .harnery/councils/<id>/round-N/<their-name>.md
# Check who's contributed this round:harn council show <id>
# Once all members have contributed, advance to the next round.# (Or auto-advance by passing --auto-advance to create.)harn council advance <id>
# When converged: close it. The status flips to "closed"; the manifest +# body stay on disk for the historical record.harn council close <id>
# Or archive (manifest + body moved to .harnery/councils/archive/) to keep# the active list lean.harn council archive <id>Manifest shape
Section titled “Manifest shape”The manifest is JSON at .harnery/councils/<id>.json. Key fields:
{ "schema_version": 2, "council_id": "...", "objective": "the question we're answering", "status": "active", // active | closed | archived "created_at": "...", "created_by": "Alpha", "steward": "Beta", // optional ongoing process-tender "members": [ { "name": "Alpha", "id": "01H..." }, { "name": "Beta", "id": "01H..." } ], "current_round": 2, "target_doc": "docs/migration-plan.md", "rounds": [ { "round": 1, "status": "collected", "contributors": ["Alpha", "Beta"] }, { "round": 2, "status": "open" } ]}Running effective rounds
Section titled “Running effective rounds”Councils converge on what the prompts ask for, so the steward’s round prompts do most of the quality work. Three practices that consistently pay off:
- Charge a completeness critic in round 1. Give at least one member (or add it as a second charge to one member’s lens) the explicit question: “What important thing is NOT in this document at all (a missing dimension, not a flaw in what’s written)?” Lens-scoped reviewers reliably improve what exists and reliably miss whole absent dimensions; a council can unanimously approve a plan whose biggest problem is a chapter that was never written. Name the charge in the prompt; don’t assume a security or ops lens covers it implicitly.
- Spell out the status marker. Convergence detection parses a
<trivial>/<substantive>tag on each contribution (bare words on the final line work as a fallback, but the angle-bracket form is the contract). A council whose members write free-form “nothing major” endings reads as untagged, and the exit criterion never fires. - Keep phrasing consistent across members. Contributions are only comparable when every member answered the same question through their own lens. Draft one round prompt, then specialize per member, rather than writing each from journal.
Web UI
Section titled “Web UI”The standalone dashboard renders all of this for browsing:
/councils: active / closed / archived list with member chips + round count/councils/<id>: manifest + invite + per-round contributions rendered inline
See concepts / web UI.
Design notes
Section titled “Design notes”- File-based, not message-passing. Each contribution is a markdown file. Reads are flat
readdirSync+readFileSync. There’s no broker or queue process behind it. - One write per (round, member). Re-writing the same contribution is allowed (and tracked via the file mtime), but the canonical state is “whatever’s on disk right now.”
- Convergence is convention. Two consecutive rounds where no member submitted a “Substantive” entry = converged. The convention is encoded in the prompts each member is given by the steward, not enforced by the tool.
- Archival is permanent. Archived councils stay on disk under
.harnery/councils/archive/so future sessions can mine the conversation. Usecouncil delete --archivedto remove permanently; it refuses to operate on the active set.
For the design rationale, see the initial-architecture ADR.