This is the daily-use guide. For architecture, see CLAUDE.md. For phase sequencing and what's next, see docs/roadmap.md.
One command:
uv run harness chat --model mlx --persona --memories 3 --facts 5
Optional: drop a wrapper at ~/.local/bin/harness so the CLI is callable as bare harness from any cwd:
cat > ~/.local/bin/harness <<'EOF'
#!/bin/sh
exec uv run --directory /Users/m7s/dev/harness harness "$@"
EOF
chmod +x ~/.local/bin/harnessThe wrapper still uses the project's venv (no second mlx copy) and picks up code edits live. After this, every uv run harness … example below works as bare harness ….
What the flags do:
--model mlx— use Qwen 2.5 7B on MLX (default for local dev speed; pass--model-repo mlx-community/Qwen2.5-32B-Instruct-4bitfor the bigger model). Omit--model mlxto use the echo adapter (no model, just wiring — useful if MLX is down).--persona— run the two-pass voice rewriter. Pass 1 generates substance; pass 2 rewrites in Airton's register. Without it, Airton will drift toward generic-assistant prose.--memories 3— retrieve up to 3 episodic memories per turn that clear the 0.5 similarity floor.--facts 5— retrieve up to 5 semantic facts per turn that clear the 0.45 floor.
Other useful options:
--session NAME— name the session. Defaults to a freshcli-YYYY-MM-DD-HHMMSSUTC id per launch so a new chat invocation never inherits the prior run's compaction summary or scribed memory. Pass an explicit name (e.g.--session local) to resume a prior session, or to make scribe / voice capture target a known id later.--speaker NAME— who you are. Defaults tomark. Determines whose relationship memory this session belongs to.--chain-rewrites— add a second concrete-substitution rewrite pass. More Airton, 1.5× latency.--memories-threshold 0.5/--facts-threshold 0.45— tune the similarity floors. Lower = more permissive.--draft-repo mlx-community/Qwen2.5-0.5B-Instruct-4bit— turn on MLX speculative decoding. ~1.5–2× tok/s, bit-identical output, +~350 MB RAM. Draft must share the target's tokenizer vocab; mismatched drafts fall back with a warning. Also viaHARNESS_MLX_DRAFT_MODEL_REPO.--summarize-tool-results— when tools are on, compress grep / list_dir / search_web / fetch_url output above ~1 KB before the main model reads it. Paths and identifiers are preserved verbatim. Big win on 7B-with-noisy-tool-output turns.--no-harvest-skills— opt out of the session-start bd thought-graph harvest. By default, closedthought:decision/thought:observationbeads are pulled into episodic-procedural memory so past decisions can surface on future turns.
In-chat slash commands:
/edit(alias/capture) — reopen Airton's last reply in$EDITORto capture a voice correction./clear— pin a cutoff for the current process: further turns don't see earlier transcript, voice retrieval, episodic memory, or semantic facts. Process-ephemeral — restart to re-enable retrieval. Stored data is untouched. Use this when you want a fresh conversation that doesn't bleed in from the last one./retro— run ab's retrospective tool on demand. Also fires automatically on/exit,:q, or Ctrl-C.
- Your message is logged to the transcript.
- Retrieval picks the 6 voice samples most similar to your message and puts them in the system prompt as few-shot examples.
- Episodic memory is searched; hits that clear the floor are appended to the system prompt as "things I remember."
- Semantic facts are searched; hits that clear the floor are appended as "relevant facts I know."
- Qwen produces a draft (pass 1).
- Qwen rewrites the draft in Airton's register (pass 2).
- The final reply is logged to the transcript and shown.
If Airton doesn't know anything relevant, those floors keep the prompt clean — nothing irrelevant gets injected. That's by design.
Two paths.
You've just landed in voice-coverage territory: the prompt is near no existing voice sample, so Qwen is falling back to generic register.
Fix it by teaching Airton what it should have said. Two paths:
In-chat (easiest): type /edit at the you › prompt. Your $EDITOR opens with Airton's last reply pre-loaded — rewrite it in the voice you want, save, and exit. The edited text becomes a new voice sample paired with the preceding user message. Saving without changes is a no-op.
From the shell:
uv run harness voice capture \
--session <same session id you just chatted in> \
--gold "What Airton should have said, in Airton's voice."
Either way writes a new sample to character/airton/voice/captured.yaml. On your next chat startup, retrieval will pick it up for similar future prompts.
To confirm the capture landed:
uv run harness voice list-captured
That's a factual error, not a voice drift. The scribe will eventually record whatever happened, but if you want to correct the record:
uv run harness memory fact-add SUBJECT PREDICATE OBJECT --confidence 0.95
Shared fact (no --user) if it's general; scoped (--user mark) if it's about you.
To supersede an existing fact, run fact-add with the new version then let the consolidator merge them next time you consolidate — the newer, higher-confidence one wins.
Memory extraction is a deliberate step during normal chat — you run the scribe when you want the transcript turned into episodic / semantic rows:
uv run harness memory scribe --session <session> --user mark --model mlx
The scribe reads unprocessed transcript turns for that session, asks Qwen to extract episodic summaries and atomic facts, writes them to the working tier. Watermark-tracked — reruns are incremental.
One exception: compaction now auto-scribes. When the context meter crosses --compact-at (default 80 %) and memory/semantic stores are wired, unprocessed turns are scribed before the summarizer folds them — so search_memory can still answer "what did we talk about" after the transcript compresses. Disable with --no-auto-scribe.
After a few scribe runs, the working tier accumulates. To clean it up (merge near-duplicates, pick canonical versions):
uv run harness memory consolidate
Consolidator clusters similar episodes and merges fact triples, promotes the winners to consolidated tier, retires the losers.
Suggested cadence (manual for now; scheduled consolidation is a pending phase):
- Scribe: after each meaningful chat session.
- Consolidate: weekly or when the working tier feels cluttered.
Inspect what's in memory any time:
uv run harness memory list # episodic
uv run harness memory list --tier consolidated # only promoted
uv run harness memory fact-list # semantic facts
uv run harness memory search "some question" # semantic search
uv run harness memory fact-search "some question"
When the character is airton_b and the ops tool profile is loaded, ab uses bd as its own scratchpad — not just for tasks you capture, but for its own in-flight thoughts, hypotheses, and questions. Ab-owned beads carry assignee=airton_b; yours carry your username. By default the ops views hide ab's scratchpad so your work isn't buried.
What you'll see:
- Session start + post-compaction, the console prints a short resume summary: current focus bead, other in-progress ab-beads, recent bd memories, and any drifting ab-beads. If you're not interested, ignore it — it's meta-context, not a reply.
- Plan (ab's
plantool) prepends aFocus: [...]banner when ab has an active focus bead. Tier buckets below unchanged. - Drift (ab's
drifttool) surfaces stale work. User-owned beads use bd's native 14-day horizon; ab-owned beads have a tighter 7-day window because ab's thoughts rot faster. /retroat theyou ›prompt runs an on-demand retrospective. Summary first, then you get one line to record an insight (blank to skip). Also fires automatically on/exit,:q, or Ctrl-C.
Budgets (tunable via HARNESS_AB_* env vars):
- Max 3 ab-owned captures per turn (
HARNESS_AB_TURN_CAP). Refreshes every user turn. - Max 10 open ab-owned beads in flight (
HARNESS_AB_INFLIGHT_CAP). At the cap, ab must close one before capturing another — the hint lists low-priority / oldest candidates. - 3 defers on the same bead trigger a stall escalation (
HARNESS_AB_STALL_DEFERS): ab spawns athought:questionchild asking "still relevant?" to surface the stall for triage. - 7-day drift horizon for ab-owned beads (
HARNESS_AB_DRIFT_DAYS).
Flags:
--include-internal— show ab-owned beads in plan/list/drift/search views.--devimplies this.- The ab bd repo lives at
HARNESS_AB_BD_DIR(defaults under the harness root).bd → <path>prints on session start so misconfiguration is visible.
Common failure modes:
- "turn-cap reached" when ab tries to capture a 4th bead in one turn. Expected — either close one first or wait for your next message.
- "in-flight cap reached" with candidates listed. Close / defer one of them before the new capture.
- No focus set when using
statuswithout an id. Ab needs an in-progress bead to anchor. Capture one or manually promote an existing open bead.
- Is
--personaon? Without it, you'll get raw Qwen output. - Is
--model mlxon? Without it, you're running the echo adapter. - Did you accidentally leave
--memories 0 --facts 0? Memory can help anchor voice. - Check whether relevant voice samples exist:
uv run harness eval voice --model mlx --persona— the suite runs against known prompts. If gold for your prompt shape doesn't exist, capture it.
- Check what facts are retrieved for that prompt:
uv run harness memory fact-search "your prompt". If wrong ones are ranking above right ones, raise--facts-thresholdin chat (e.g., 0.55 instead of 0.45). - Consider whether the scribe wrote noisy facts you should manually retire: find them with
fact-listand supersede with afact-add+ consolidate.
- Did you run the scribe? Transcripts aren't automatically memory-ized.
- If scribed but not retrieved: check similarity with
memory search "query". Might be below the 0.5 floor. Lower--memories-thresholdin chat.
- Run
uv run harness memory rebuild-embeddingsto re-vectorize all active rows with the current embedder. - Verify with
memory search— retrieval should work immediately after.
data/harness.sqlite— transcripts, episodic, semantic, scribe watermarks. Back this up.character/airton/voice/canonical.yaml— the curated voice suite. Treat as source of truth; edit with care.character/airton/voice/captured.yaml— your captured corrections. Appends-only in practice; safe to edit.character/airton/seed_memories/*.md— Airton's formative narratives. Editable; reloaded on next character load.character/airton/core.yaml— identity: pronouns, premise, values, taboos. Changes are structural.character/airton/constitution.md— principles the critic enforces. Plain prose.character/airton/session_resume_eval.yaml— fixtures that pinbuild_resume_summary's contract. Edit when adding resume-protocol sections.airton_b/(defaultHARNESS_AB_BD_DIR) — ab's isolated beads DB..beads/subdir + Dolt metadata. Back up alongsidedata/harness.sqlite.
- Chat is interactive-only. No web, Slack, or Matrix yet.
- Memory scribe is still user-triggered during normal chat (
memory scribe, orscribe_sessionas a tool with--tool-set memory). Compaction is the one path that auto-scribes — see above. - Voice only covers prompts near existing samples. Off-piste prompts regress to Qwen's default register until you capture them.
- No backup destination set — local only, no off-box copies yet.
fetch_url/search_webcross the Tailscale trust boundary. They're in theresearchandcodingprofiles only, never incore— air-gapped sessions stay air-gapped by default.
uv run harness chat --model mlx --persona --memories 3 --facts 5— note the auto-coined session id Airton prints on launch (e.g.cli-2026-04-26-143205); reuse it with--sessionif you want to extend this conversation later.- Talk to Airton about something real you're working on — actual engineering, not a test prompt.
- When a reply lands wrong, capture the correction before moving on:
uv run harness voice capture --session <auto-coined-id> --gold "…"in another terminal. Useharness session listif you forgot the id. - After the session, scribe it:
uv run harness memory scribe --session <auto-coined-id> --user mark --model mlx. - Inspect what it wrote:
uv run harness memory list --tier workingandfact-list --tier working. - Optionally consolidate:
uv run harness memory consolidate.
Repeat. The corpus compounds.