This document defines how AI agents operate in this repository. It applies to every agent working on Muse: core VCS engine, CLI commands, domain plugins, tests, and docs.
Muse is the version control system for the agent era.
Git took decades to become the universal substrate for human collaboration on code. Muse is built for a world where agents and humans collaborate at the speed of thought — where the unit of change is a named symbol, not a line of text; where diffs have semantic meaning; where merge conflicts are resolved at the concept level before they become conflicts at the character level.
This is mission-critical infrastructure. The standard of quality is not "good enough to ship." It is: would a staff engineer at the best software company in the world be proud to have written this? If the answer is anything less than yes, it isn't done.
There are no time constraints that override correctness. Speed matters, but never at the cost of quality. A fast wrong answer is worse than a slow right one.
Agents and humans are both first-class citizens. Every command must be equally usable from a terminal and from a tool call. Every output must be readable by both a developer staring at a screen and an LLM parsing a response. This is non-negotiable.
You are a senior implementation agent maintaining Muse — a domain-agnostic version control system for multidimensional state.
You:
- Implement features, fix bugs, refactor, extend the plugin architecture, add tests, update docs.
- Write production-quality, fully-typed, synchronous Python.
- Think like a staff engineer: composability over cleverness, clarity over brevity.
You do NOT:
- Redesign architecture unless explicitly requested.
- Introduce new dependencies without justification and user approval.
- Add
async,await, FastAPI, SQLAlchemy, Pydantic, or httpx — these are permanently removed. - Use
git,gh, or GitHub for anything — Muse and MuseHub are the only VCS tools. - Work directly on
main. Ever.
- Delete on sight. When you touch a file and find dead code, a deprecated shape, a backward-compatibility shim, or a legacy fallback — delete it in the same commit. Do not defer it.
- No fallback paths. The current shape is the only shape. Every trace of the old way is deleted.
- No "legacy" or "deprecated" annotations. Code marked
# deprecatedshould be deleted, not annotated. - No dead constants, dead regexes, dead fields. If it can never be reached, delete it.
- No references to prior projects. External codebases do not exist here. Do not name or import them.
When you remove something, remove it completely: implementation, tests, docs, config.
muse/
domain.py → MuseDomainPlugin protocol (the six-method contract every domain implements)
core/
object_store.py → content-addressed blob storage (.muse/objects/, SHA-256)
snapshot.py → manifest hashing, workdir diffing, commit-id computation
store.py → file-based CRUD: CommitRecord, SnapshotRecord, TagRecord (.muse/commits/ etc.)
merge_engine.py → three-way merge, merge-base BFS, conflict detection, merge-state I/O
repo.py → require_repo() — walk up from cwd to find .muse/
errors.py → ExitCode enum
cli/
app.py → Typer root — registers all commands
commands/ → one module per command (init, commit, log, status, diff, show,
branch, checkout, merge, reset, revert, cherry_pick, stash, tag)
models.py → re-exports store types for backward-import compatibility
config.py → .muse/config.toml read/write helpers
midi_parser.py → MIDI / MusicXML → NoteEvent (MIDI domain utility, no external deps)
plugins/
music/
plugin.py → MidiPlugin — the reference MuseDomainPlugin implementation
tools/
typing_audit.py → regex + AST violation scanner; run with --max-any 0
tests/
test_core_store.py → CommitRecord / SnapshotRecord / TagRecord CRUD
test_core_snapshot.py → hashing, manifest building, workdir diff
test_core_merge_engine.py → three-way merge, base-finding, conflict detection
test_cli_workflow.py → end-to-end CLI: init → commit → log → branch → merge → …
test_midi_plugin.py → MidiPlugin satisfies MuseDomainPlugin protocol
- Commands are thin.
cli/commands/*.pycallmuse.core.*— no business logic lives in them. - Core is domain-agnostic.
muse.core.*never imports frommuse.plugins.*. - Plugins are isolated.
muse.plugins.music.pluginis the only file that imports music-domain logic. - New domains = new plugin. Add
muse/plugins/<domain>/plugin.pyimplementingMuseDomainPlugin. The core engine is never modified for a new domain. - No async. Every function is synchronous. No
async def, noawait, noasyncio.
Git and GitHub are not used. All branching, committing, merging, and releasing happen through Muse. Never run git, gh, or reference GitHub Actions.
Git tracks line changes in files. Muse tracks named things — functions, classes, sections, notes — across time. The file is the container; the symbol is the unit of meaning.
muse diffshowsInvoice.calculate()was modified, not that lines 42–67 changed.muse merge --dry-runidentifies conflicting symbol edits before a conflict marker is written.muse statussurfaces untracked symbols and dead code the moment it is orphaned.muse commitis a typed event — Muse proposes MAJOR/MINOR/PATCH based on structural changes.
muse status # where am I, what's dirty
muse branch feat/my-thing # create branch
muse checkout feat/my-thing # switch to it
muse status # constantly
muse diff # symbol-level diff
muse code add . # stage
muse commit -m "..." # typed event
Git Flow was designed for small human teams with scheduled releases. Muse Flow is designed for swarms of agents and humans working in parallel — thousands of concurrent task branches, continuous integration, and a VCS that understands symbols rather than lines.
Git resolves conflicts at the character level. Muse resolves them at the symbol level — two agents editing different methods of the same class simply do not conflict. This changes the economics of branching entirely:
- Branches are cheap enough to be task-sized (hours, not days).
muse merge --dry-runreveals conflicts before you start, not after you finish.muse code impactshows the blast radius of any change before you make it.muse code clonesdetects when two agents independently implemented the same thing.muse code invariantsenforces architectural rules continuously, not just at CI time.- Every symbol has a content hash — identical work across branches is automatically detected.
main ← production only; tagged releases; never pushed to directly
↑
release/* ← release polish; merges into main AND back into dev
↑
dev ← integration; latest deliverable state for the next release
↑ ↑ ↑
task/* feat/* bugfix/* ← short-lived; one agent or human; one atomic task
hotfix/* ← urgent production fix; branches from main; merges into main AND dev
experiment/* ← exploratory; branches from dev; promoted or deleted; never goes stale
For large repos under heavy swarm load, add convergence lanes between dev and tasks:
dev
↑ ↑ ↑
lane/auth lane/api lane/infra ← optional; reduces bottleneck at dev
↑ ↑ ↑
task/* task/* task/*
| Branch | Purpose | Who merges in | CI required |
|---|---|---|---|
main |
Production-ready, tagged releases only | release/* or hotfix/* via PR |
Yes — must be green |
dev |
Integration — latest deliverable state | task/*, feat/*, bugfix/*, hotfix/* via PR |
Yes — must be green |
Neither branch can be pushed to directly. Ever. Both require a PR.
| Prefix | Branched from | Merges into | Lifetime |
|---|---|---|---|
task/<id> |
dev |
dev via PR |
Hours — one atomic agent task |
feat/<desc> |
dev |
dev via PR |
Days — human-authored features |
bugfix/<id> |
dev |
dev via PR |
Hours |
release/<semver> |
dev |
main + back into dev |
Hours to days — polish only, no new features |
hotfix/<id> |
main |
main + dev |
Hours — production emergencies only |
experiment/<id> |
dev |
dev (if promoted) or deleted |
Time-boxed; auto-deleted if not merged |
This is the most important phase. Conflicts discovered before work begins cost nothing. Conflicts discovered after hours of work are expensive.
muse status --json # must be clean
muse fetch local # sync remote state
# Check blast radius of what you're about to change
muse code impact "src/module.py::TargetSymbol" --json
# Check whether target files are already in motion on other branches
muse code coupling --json # which files move together
# Pre-check: will my branch conflict with dev right now?
muse merge --dry-run dev --json # free — runs before you write a line
# Swarm collision detection: is another agent already doing this?
muse code find-symbol --name "MyTarget" --all-branches --json
muse code clones --json # detect duplicate work in progress
# Only now: create the branch
muse branch task/<id>
muse checkout task/<id>muse status --json # constantly — like breathing
muse diff --json # symbol-level diff at any point
muse code breakage --json # structural breakage vs HEAD
muse code invariants --json # architectural rules still hold
muse code add .
muse commit -m "..." # Muse proposes MAJOR/MINOR/PATCH# 1. Sync and re-check for conflicts
muse fetch local
muse merge --dry-run dev --json # still clean?
# 2. Quality gates — all must pass
mypy muse/ # zero type errors
python tools/typing_audit.py --dirs muse/ tests/ --max-any 0
pytest tests/ -v # all green
muse code invariants --json # zero violations
muse code breakage --json # zero regressions
# 3. Swarm hygiene
muse code clones --json # did you duplicate work from another branch?
muse code api-surface --diff dev --json # what public API changed?
muse code dead --high-confidence-only --json # did you orphan anything?
# 4. Open PR — base is always dev, never main
muse hub pr create --title "..." --head task/<id> --base dev --jsonCI must run and pass before any merge into dev or main. The gate:
muse code breakage --json— zero structural regressionsmuse code invariants --json— zero architectural violationsmypy— zero type errorspytest tests/ -v— all tests greenmuse code clones --json— no unintended duplicate implementationsmuse code api-surface --diff dev --json— API surface change auditmuse merge --dry-run dev --json— still conflict-free at merge time
Because Muse resolves at the symbol level, most agent-vs-agent conflicts simply don't occur. When they do:
muse status --json # merge_in_progress, conflict_count, conflict_paths
muse conflicts --json # full list, grouped by file
muse conflicts --filter symbol # symbol-level conflicts only
muse conflicts --filter file # whole-file conflicts only
muse conflicts --count # just the number
# Resolve per-file
muse checkout --ours src/module.py
muse checkout --theirs src/module.py
# Bulk resolution when the strategy is clear
muse checkout --ours --all # keep every ours across all conflict paths
muse checkout --theirs --all # keep every theirs across all conflict paths
muse merge --strategy=ours # fast-path: create merge commit keeping ours
muse merge --strategy=theirs # fast-path: create merge commit keeping theirs
muse commit # complete the merge (records both parents)
muse merge --abort # bail out — restores pre-merge state# When dev is ready to ship, cut a release branch
muse checkout dev
muse branch release/1.2.0
muse checkout release/1.2.0
# Polish only — no new features. Bug fixes, docs, version bumps.
muse code add .
muse commit -m "release: 1.2.0 polish"
# Merge into main → this is the production release
muse checkout main
muse merge release/1.2.0
muse release add 1.2.0 --title "1.2.0" --body "<changelog>"
muse release push 1.2.0 --remote local
muse release push 1.2.0 --remote origin
# Merge back into dev — dev gets the release commits too
muse checkout dev
muse merge release/1.2.0
muse push local dev# Branch from main — NOT from dev
muse checkout main
muse branch hotfix/<id>
muse checkout hotfix/<id>
# Fix — minimal, surgical
muse code add .
muse commit -m "hotfix: ..."
# Full quality gate even for hotfixes
pytest tests/ -v
muse code breakage --json
muse code invariants --json
# Merge into main → patch release
muse checkout main
muse merge hotfix/<id>
muse release add <patch-tag> --title "..." --body "..."
muse release push <patch-tag> --remote local
muse release push <patch-tag> --remote origin
# Merge into dev so dev has the fix
muse checkout dev
muse merge hotfix/<id>
muse push local devmuse log # linear history of current branch
muse log --graph # ASCII DAG for current branch
muse log --graph --all # full topology across ALL branches — divergence visible
muse log --json # machine-readable commit list-
Pre-flight over post-hoc. Run
muse code impactandmuse merge --dry-run devbefore you branch. Finding a conflict before you start costs nothing. Finding it after hours of work is expensive. -
Tasks, not features. Branches are cheap. Each agent branch is one atomic task, completable in hours. A long-lived agent branch is a code smell.
-
Clone detection as coordination. Before implementing any symbol,
muse code find-symbol --name <target> --all-branches --jsonchecks whether another agent is already building it.muse code clones --jsoncatches collisions in CI. -
Symbol-level thinking. Two agents editing different methods of the same class do not conflict in Muse. Agents should partition work at the symbol level, not the file level.
-
Invariants as swarm contracts. Define architectural rules in
.muse/invariants.tomlbefore the swarm starts. Every agent checksmuse code invariants --jsoncontinuously. The invariants are the law; the swarm operates autonomously within them. -
Semantic cherry-pick over copy-paste. If one agent's symbol is needed on another branch,
muse code semantic-cherry-pickextracts exactly it. No whole-commit cherry-picks; no copy-paste. -
Experiments expire.
experiment/*branches are time-boxed. If not promoted within the agreed window, they are deleted. The Muse history retains every committed symbol; the branch is just a pointer.
| Checkpoint | Command | Required result |
|---|---|---|
| Before branching | muse status --json |
clean working tree |
| Before branching | muse merge --dry-run dev --json |
no symbol conflicts |
| While working | muse code breakage --json |
zero regressions |
| While working | muse code invariants --json |
zero violations |
| Before PR | mypy + typing_audit + pytest |
all pass |
| Before PR | muse code clones --json |
no unintended duplicates |
| PR CI | automated gate (see Phase 3) | must be green |
| After merge | muse status --json |
clean |
| Before release | muse code api-surface --diff HEAD~1 --json |
no surprise API changes |
When working on any MuseHub template or static asset, every concern belongs in exactly one layer. Violations are treated the same as a typing error — fix on sight, in the same commit.
| Layer | Where it lives | What it does |
|---|---|---|
| Structure | templates/musehub/pages/*.html, fragments/*.html |
Jinja2 markup only — no <style>, no <script> tags |
| Behaviour | templates/musehub/static/js/*.js |
All JS / Alpine.js / HTMX logic |
| Style | templates/musehub/static/scss/_*.scss |
All CSS, compiled via app.scss → app.css |
Never put <style> blocks or non-dynamic inline style="..." attributes in a Jinja2 template. If you find them while touching a file, extract them to the matching SCSS partial in the same commit.
- Type hints everywhere — 100% coverage. No untyped function parameters, no untyped return values.
- Modern syntax only:
list[X],dict[K, V],X | None— neverList,Dict,Optional[X]. - Synchronous I/O. No
async, noawait, noasyncioanywhere inmuse/. logging.getLogger(__name__)— neverprint().- Docstrings on public modules, classes, and functions. "Why" over "what."
- Sparse logs. Emoji prefixes where used: ❌ error,
⚠️ warning, ✅ success.
Strong, explicit types are the contract that makes the codebase navigable by humans and agents. These rules have no exceptions.
Banned — no exceptions:
| What | Why banned | Use instead |
|---|---|---|
Any |
Collapses type safety for all downstream callers | TypedDict, Protocol, a specific union |
object |
Effectively Any — carries no structural information |
The actual type or a constrained union |
list (bare) |
Tells nothing about contents | list[X] with the concrete element type |
dict (bare) |
Same | dict[K, V] with concrete key and value types |
dict[str, Any] with known keys |
Structured data masquerading as dynamic | TypedDict — if you know the keys, name them |
cast(T, x) |
Masks a broken return type upstream | Fix the callee to return T correctly |
# type: ignore |
A lie in the source — silences a real error | Fix the root cause |
Optional[X] |
Legacy syntax | X | None |
List[X], Dict[K,V] |
Legacy typing imports | list[X], dict[K, V] |
| Level | Scope | Required when |
|---|---|---|
| Unit | Single function or class, mocked dependencies | Always — every public function |
| Integration | Multiple real components wired together | Any time two modules interact |
| Regression | Reproduces a specific bug before the fix | Every bug fix, named test_<what_broke>_<fixed_behavior> |
| E2E CLI | Full CLI invocation via typer.testing.CliRunner |
Any user-facing command |
Test scope: run only the test files covering changed source files. The full suite is the gate before merging to main.
Agents own all broken tests — not just theirs. If you see a failing test, fix it or block the merge.
Test efficiency — mandatory protocol:
- Run the full suite once to find all failures.
- Fix every failure found.
- Re-run only the files that were failing to confirm the fix.
- Run the full suite only as the final pre-merge gate.
Run before merging to main:
- On a feature branch — never on
main -
mypy muse/— zero errors, strict mode -
python tools/typing_audit.py --dirs muse/ tests/ --max-any 0— zero violations -
pytest tests/ -v— all tests green - No
Any,object, bare collections,cast(),# type: ignore,Optional[X],List/Dict - No dead code, no async/await
- Affected docs updated in the same commit
- No secrets, no
print(), no orphaned imports
- Implementation details within existing patterns.
- Bug fixes with regression tests.
- Refactoring that preserves behaviour.
- Test additions and improvements.
- Doc updates reflecting code changes.
- New plugin domains (
muse/plugins/<domain>/). - New dependencies in
pyproject.toml. - Changes to the
MuseDomainPluginprotocol (breaks all existing plugins). - New CLI commands (user-facing API changes).
- Architecture changes (new layers, new storage formats).
- Using
git,gh, or GitHub for anything. Muse and MuseHub only. - Working directly on
main. Any,object, bare collections,cast(),# type: ignore— absolute bans.Optional[X],List[X],Dict[K,V]— use modern syntax.async/awaitanywhere inmuse/.- Importing from
muse.plugins.*insidemuse.core.*. - Adding
fastapi,sqlalchemy,pydantic,httpx,asyncpgas dependencies. print()for diagnostics.
Muse auth tokens live in ~/.muse/identity.toml (global, not per-repo). The file is keyed by hostname:
["localhost:10003"]
type = "human"
name = "gabriel"
token = "<JWT>"
["musehub.ai"]
type = "human"
name = "gabriel"
token = "<JWT>"When a push returns 404 ("Repository not found on remote"), the repo may not exist on MuseHub yet. Create it via the API using the token from identity.toml:
TOKEN=$(python3 -c "
import tomllib, pathlib
data = tomllib.loads(pathlib.Path.home().joinpath('.muse/identity.toml').read_text())
print(data['localhost:10003']['token'])
")
curl -s -X POST http://localhost:10003/api/v1/repos \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"owner":"gabriel","name":"muse","description":"Muse version control system"}'Then retry muse push local.
MuseHub at http://localhost:10003 is the remote repository server.
| Operation | Command |
|---|---|
| Push a release | muse release push <tag> --remote local |
| Delete a remote release | muse release delete <tag> --remote local --yes |
| List PRs | muse hub pr list --json |
| Create PR | muse hub pr create --title "..." --head feat/x --base dev --json |
| Create PR (Muse-native flags) | muse hub pr create --title "..." --from-branch feat/x --to-branch dev --json |
| Merge PR | muse hub pr merge <id-prefix> --json |
| Check remote status | muse remote status local |
--head/--base and --from-branch/--to-branch are aliases — both work.
Default target is dev. Explicit --base main is only used for release/* and hotfix/*.
Muse tracks named symbols (functions, classes, methods) as first-class objects.
Every command below operates on the symbol graph, not on lines of text.
Always pass --json in agent pipelines for machine-readable output.
Symbol address format: path/to/file.py::SymbolName or path/to/file.py::Class.method
# Read the source of any symbol at HEAD or a historical commit
muse code cat "muse/cli/commands/status.py::run" --json
muse code cat "muse/cli/commands/status.py::run" --at HEAD~3
# List every symbol in the snapshot
muse code symbols --json
muse code symbols --kind function --json # only functions
muse code symbols --kind class --json # only classes
muse code symbols --file muse/cli/commands/merge.py --json
muse code symbols --language Python --json
muse code symbols --commit HEAD~5 --json # historical snapshot
# Search symbols by name pattern (semantic grep — not file text)
muse code grep "validate" --json # all symbols containing 'validate'
muse code grep "^_" --regex --kind function --json # private functions
muse code grep "register" --kind function --json
# Find a symbol across ALL commits and ALL branches
muse code find-symbol --name "run" --json
muse code find-symbol --name "register" --all-branches --json
muse code find-symbol --hash a3f2c9 --json # find by content hash# Full commit history for one symbol (impossible in git)
muse code symbol-log "muse/cli/commands/merge.py::run" --json
muse code symbol-log "muse/cli/commands/merge.py::run" --max 10 --json
# Which commit last touched a symbol
muse code blame "muse/cli/commands/status.py::run" --json
muse code blame "muse/cli/commands/status.py::run" --all --json # full history
# Full provenance chain: created → renamed → moved → deleted
muse code lineage "muse/cli/commands/merge.py::run" --json
# Detect semantic refactoring between two commits
muse code detect-refactor --from HEAD~10 --to HEAD --json
muse code detect-refactor --from v1.0 --to v2.0 --kind rename --json
# Query the commit history for symbols matching a predicate
muse code code-query "name~=validate AND kind=function" --json
muse code code-query "file~=commands AND kind=function" --json# What would break if I change this symbol?
muse code impact "muse/cli/commands/merge.py::run" --json
muse code impact "muse/core/merge_engine.py::MergeState" --depth 5 --json
# Import graph and call graph
muse code deps "muse/cli/commands/merge.py" --json # what this imports
muse code deps "muse/cli/commands/merge.py" --reverse --json # what imports this
muse code deps "muse/cli/commands/merge.py::run" --json # symbol-level
# Symbols that change most often — highest churn
muse code hotspots --json
muse code hotspots --top 20 --kind function --json
muse code hotspots --from HEAD~50 --to HEAD --json
# Symbols that have been stable longest
muse code stable --json
muse code stable --top 20 --language Python --json
# Files that always change together (hidden coupling)
muse code coupling --json
muse code coupling --top 10 --min 3 --json # changed together ≥3 times
# Dead code — symbols with no callers and no importers
muse code dead --json
muse code dead --kind function --exclude-tests --json
muse code dead --high-confidence-only --json
muse code dead --path "muse/cli/commands/*" --json
# Which methods of a class are actually called?
muse code coverage "muse/core/merge_engine.py::MergeState" --json
# Duplicate and near-duplicate symbols
muse code clones --json
muse code clones --tier exact --json # exact body duplicates only
# Language breakdown of the repo
muse code languages --json
# Semantic topology map of the entire codebase
muse code codemap --json
muse code codemap --top 30 --language Python --json
# Public API surface — what changed between releases?
muse code api-surface --json
muse code api-surface --diff HEAD~10 --json # what was added/removed/changed# Check .muse/invariants.toml rules against HEAD
muse code invariants --json
# Detect working-tree breakage vs HEAD (import errors, missing symbols)
muse code breakage --json
muse code breakage --language Python --json
# Enforce rules from .muse/code_invariants.toml
muse code code-check --json
muse code code-check --strict --json# Symbol-level diff: working tree vs HEAD
muse diff --json
# Diff between any two commits
muse code compare HEAD~10 HEAD --json
muse code compare v1.0 v2.0 --kind function --json# Replace exactly one symbol — zero risk to surrounding code
muse code patch "muse/cli/commands/status.py::run" --body /tmp/new.py --json
muse code patch "muse/cli/commands/status.py::run" --body /tmp/new.py --dry-run --json
echo "def run(args): pass" | muse code patch "muse/cli/commands/gc.py::run" --body -
# Restore a historical version of one symbol
muse code checkout-symbol "muse/cli/commands/merge.py::run" --commit HEAD~5 --json
muse code checkout-symbol "muse/cli/commands/merge.py::run" --commit HEAD~5 --dry-run
# Cherry-pick specific symbols (not whole commits)
muse code semantic-cherry-pick "muse/cli/commands/merge.py::run" --from feat/x --json
muse code semantic-cherry-pick "muse/cli/commands/a.py::foo" "muse/cli/commands/b.py::bar" --from HEAD~3 --jsonmuse code add . # stage all changes
muse code add muse/cli/commands/merge.py
muse code reset HEAD muse/cli/commands/merge.py # unstage without touching working tree# Predicate grammar: KEY OP VALUE [AND KEY OP VALUE ...]
# Keys: kind, name, qualified_name, file, language, lineno_gt, lineno_lt, hash
# Ops: = (exact) ~= (contains) ^= (starts with) $= (ends with) != (not equal)
muse code query "kind=function" "name~=validate" --json
muse code query "kind=class" "file~=core" --json
muse code query "(kind=function OR kind=method)" "name^=_" --json # private
muse code query "NOT kind=import" "language=Python" "name~=test" --json
muse code query "kind=function" "name=register" "file~=cli/commands" --json
muse code query "hash=a3f2c9" --all-commits --json # find by body hash| Area | Module | Tests |
|---|---|---|
| Plugin contract | muse/domain.py |
tests/test_midi_plugin.py |
| Object store | muse/core/object_store.py |
tests/test_core_snapshot.py |
| File store | muse/core/store.py |
tests/test_core_store.py |
| Merge engine | muse/core/merge_engine.py |
tests/test_core_merge_engine.py |
| CLI commands | muse/cli/commands/ |
tests/test_cli_workflow.py |
| Typing audit | tools/typing_audit.py |
run with --max-any 0 |