ADR 0001: Initial architecture
Date: 2026-05-27 Status: Accepted
Context
Section titled “Context”After 4+ months of building multi-agent tooling inside a multi-project monorepo (with separate agent-coord/ + agent-hooks/ packages plus dozens of generic CLI commands), and finding the same code being manually copy-pasted into sibling project CLIs, the obvious next move was to extract everything generic into a published npm package consumable by all of them.
Decisions
Section titled “Decisions”- Package: single npm package
harnery, binaryharn, MIT-licensed, public repo atgithub.com/ryanjkelly/harnery. Domain atharnery.com. - Composition: downstream CLIs import
createHarneryProgram()fromharnery/commander, register their domain-specific commands via Commander’s.addCommand(), and run.mycli agents statusandharn agents statusresolve to the same code loaded as library. - State directory:
.harnery/is the single hidden root per repo, holding sibling dirsactive/(heartbeats),councils/,journal/,identities/,pid-map/, plus theevents.ndjsonstream. - Web UI: standalone Next.js app, boots via
harn web up, file-based (no DB), localhost-only with opt-in tunnel exposure (gated IP allowlist + dangerously-override flag). - Settings: JSONC + published JSON Schema.
~/.config/harnery/config.jsonc(user) +.harnery/config.jsonc(project) with project overriding user. - Backup: shell out to restic. Google Drive default backend; rclone-style remote URLs configurable.
- Sync: shell out to rclone (Google Drive is the typical remote; OAuth happens via
rclone config), selective subset only (config + name-history + councils, but NOT heartbeats, which are machine-specific by design). - Lint/format: Biome, not ESLint + Prettier.
- Releases:
@changesets/cli+ auto-publish onmainvia GitHub Actions. - Docs: Astro Starlight at harnery.com via Cloudflare Pages.
- Runtime: Bun-first for daily dev (zero-build TS execution); Node 20+ as the published-package target via
dist/build.
Rejected alternatives
Section titled “Rejected alternatives”- Docker as primary distribution: rejected. Adds onboarding cost and slow cold start, and Playwright + ffmpeg + Python all have npm-friendly alternatives (
ffmpeg-static, Playwright’s own postinstall,uvfor Python deps). Docker remains an opt-in artifact for the “kitchen sink” path. - Plugin/extension architecture: rejected for v1. Keep consumer CLIs as separate programs that compose Harnery as a library. A plugin loader is YAGNI until a third party wants to extend without forking.
- Multi-package monorepo (
@harnery/cli+@harnery/core+@harnery/web): rejected. A single package keeps versioning simple. Internal organization viasrc/cli/+src/core/+src/web/gives clean module boundaries without the monorepo tax. - YAML for settings: rejected. Too many footguns (Norway problem, indentation sensitivity, spec version ambiguity) for a file that’s edited by both CLI and Web UI. JSON Schema tooling around YAML is also weaker than around JSON. JSONC strikes the right balance.
- Provider-agnostic command surface (e.g.
harn imagewith--provider openai|anthropic): rejected. Provider-specific commands stay in consumer CLIs, since the right provider mix is a per-project decision that Harnery shouldn’t pre-empt.
Migration plan
Section titled “Migration plan”Six-commit strangler-fig executed inside the originating monorepo:
- Scaffold the empty Harnery package
- Wire the root
package.jsonworkspaces git mvthe legacyagent-coord/→harnery/src/core/agents/+ rewrite importsgit mvthe legacyagent-hooks/→harnery/src/core/hooks/+ rewrite imports- Port the portable commands (
tokens,eml,env, etc.) into Harnery; the originating CLI then composes viacreateHarneryProgram() - Update root docs + close out the migration
After validation: split Harnery out of the originating monorepo into its own repo via git filter-repo --path harnery/, publish v0.1.0 to npm, then migrate the remaining consumer CLIs.
Consequences
Section titled “Consequences”- Consumer CLIs become thin domain wrappers. Generic command logic lives in one place.
- New generic commands land in Harnery and are immediately available to every consumer on next dep bump.
- Schema changes to
.harnery/are coordinated across consumers via semver; major bumps require deliberate adoption. - The coord layer gets a broader audience, including potential external users beyond the originating project.