Skip to content

Architecture ​

This page is where you find out where a change goes before you write it: which layer of src/hmz/ it belongs in, what that layer may import, and, for the two changes that touch many layers at once, a backend and a command, every file to visit in order.

Before you start

How the layers work ​

src/hmz/ is a stack of layers, and code in each layer may import only the layers the table below gives it. Pick a layer to see what it may import and open its SPEC. A test holds the same table, so an import that breaks it fails the run.

Pick a layer to see what its code may import. Drawn from the table in tests/integration/test_core_layering.py
ways in
holding a run
front door
running a flow
what a run leaves
driving agents
remembered
hmz.runtime.runner

The hmz exec line: reading it, finding the flow, checking it, refusing it, running it.

may import

imported by

picked may import only some of its modules inside one of those imports it may not

Where a change goes ​

You are changingPut it in
how a coding agent CLI is driven, or its accounts, models, fallbacks or pricescoganchor/
the machine an agent's turns land oncoganchor/machines/, per Machines
a type, mixin, hook or error a flow importsflows/, then check the flows humanfia/flowverse lists still work
how a flow runs: the engine, budgets, resuming, the harness and environment driversruntime/flowing/, then check those flows too
the hmz exec line: a flag, a refusalruntime/runner.py
what a run writes downruntime/epic.py
reading a backend's logs back as a traceruntime/tracing/
what a run looks like as it happens -- who is working, what it has cost, what a side question is told -- for every interfaceruntime/watching/, once
anything two of cli, tui, web and daemon would each needruntime/doing/, once
who is outside a run: claims, away, questions, lines said to agents, asidesruntime/doing/hosting.py
a slash command, a key, a menu; what the interface draws of a messagetui/
a view of the web interface, or a route it answersweb/: the page under web/static/, plain ES modules, and its routes beside the server
runs kept going after the terminal closes, and the socket frontends reach them overdaemon/
a new commandcli/
the Python API another tool callssdk/
a flow Humanize shipsflows/builtin/, for chat and the six loops FlowBench scores. Any other flow lives in a repository of its own, listed in humanfia/flowverse: see Flowverses

Nothing sits at the top of src/hmz/ but __init__.py and __main__.py. Name a module for what it holds, in the words hmz and these docs use.

The rules the test checks ​

tests/integration/test_core_layering.py holds the table the diagram draws. The run fails when:

  • A module imports what its layer may not. Relative imports count the same as absolute ones. from hmz.runtime import telemetry names telemetry, not the whole of runtime.
  • Two layers name each other. One pair may: flows calls into runtime/flowing from inside flow, load and Outworlder.new, and never at import. Importing hmz.flows must load nothing else of Humanize but hmz.coganchor.faking, which holds FakeTool, FakeCli and FakeCommand, and that is checked too.
  • A top-level package is missing from the table. cli is the one exemption.
  • The anchor's target half loads more than it may. coganchor/serve/ runs on the target machine, which may be any architecture, so it may import coganchor.proto, and coganchor.fence with the coganchor.linux bindings it is put up with, and nothing else.

To add a layer or an edge, edit ALLOWED in that test. The diagram above copies it, so change docs/.vitepress/theme/components/HmzStack.vue in the same commit.

One rule no test checks: import another layer inside the function that needs it, not at the top of the module, so hmz exec pays only for what it uses. Ruff's PLC0415 is off for this.

Adding a backend ​

A backend is one coding agent CLI Humanize drives. Adding one touches most layers, so do it in the order below, with a test run after each group. The walk-through follows how MiniMax Code, mcode, was added, in one commit: git show --stat c3c1e6a lists every file it touched.

An ACP CLI needs no code

A CLI that speaks the Agent Client Protocol can be added from the interface, on the Accounts page of /settings.

You need: the CLI installed and signed in, so a system test can drive it; its --help and a turn or two of its own logs, since several steps copy what it really writes; and which kind of driver it needs, which specs/features/agents.md says.

1. Describe it, in coganchor/ ​

  1. A Profile in coganchor/backends.py: its name and aliases, its home (home_var, home_dir), the logs one session writes as globs taking {ident}, its efforts, the sessions paths a turn keeps, skills, creds, the ways in, the hosts it must reach, and the line that installs it. Add it to PROFILES.

    python
    Profile(
        name="mcode",
        aliases=("mcode", "minimax", "minimax-code"),
        home_var="MINIMAX_DATA_DIR",
        home_dir=".minimax",
        logs=("v2/sessions/*/*/*/*-session_{ident}/messages.jsonl",),
        encodes=True,  
        ...
    )

    Set encodes=True when the CLI names a session's files by its id in URL-safe base64 rather than by the id itself, as MiniMax Code does. Whatever looks for a session's logs asks profile.logged(ident), which answers the globs with the id spelled the way the CLI spells it, and never formats logs itself.

  2. A driver in coganchor/agents/<name>.py. Subclass CommandSessionBase when a turn is one run of a command (mcode exec is), or StreamSessionBase when it is one long-lived process spoken to a line at a time. Declare on the agent class, as counts, the kinds of token it reports, named as KINDS in coganchor/agents/event.py names them.

  3. A row of DRIVEN in coganchor/agents/__init__.py, and the driver's classes in its __all__:

    python
    "mcode": (MiniMaxCodeAgent, MiniMaxCodeAgentConfig),
  4. A way of asking which models it runs: a function in _READING in coganchor/models.py. mcode's reads mcode provider list --json.

  5. Its state paths: an AgentProfile in coganchor/statepaths.py.

    python
    AgentProfile(
        name="mcode",
        state_paths=("~/.minimax", "~/.mavis"),
    ),

2. Offer it to flows, in flows/ ​

  1. A HarnessKind, a protocol and a row of HARNESS_AGENTS in flows/agents.py. The protocol carries exactly the mixins the CLI serves, and is exported from flows/agents.py and flows/__init__.py:

    python
    class HarnessKind(StrEnum):
        ...
        MCODE = "mcode"
    
    
    class MiniMaxCodeAgent(
        Agent, SubagentStartHookAgentMixin, SubagentStopHookAgentMixin, Protocol
    ):
        """MiniMax Code, with everything it can do."""

    Give it a row in the tests/unit/flows/ tests of HARNESS_AGENTS. A change here also checks the flows humanfia/flowverse lists still work.

3. Read it back, in runtime/tracing/ and runtime/watching/ ​

  1. A trace reader in runtime/tracing/readers/<name>.py, and its row in runtime/tracing/collector.py.
  2. The live tally: what its log calls each kind of token, as a row of _KINDS in runtime/watching/tally.py, and a branch in _spent there, so the tally moves during a turn. Cover its kinds in the tests/unit/runtime/watching/ tests of the tally.

4. Test it ​

  1. Unit tests of the driver in tests/unit/coganchor/, through its public names, with the CLI's process mocked.
  2. A stand-in for the integration tests: a fake of the CLI, its flags read off the real CLI's --help, and tests/integration/test_agents_<name>.py driving it.
  3. A system test: its cheapest model in CHEAPEST in tests/system/test_harness_task.py, which sets every installed CLI the same real task.

5. Say so ​

  1. The docs pages and components that list or count the backends. Search for an existing backend's name and add yours beside it:

    sh
    grep -rln mimo docs --include='*.md' --include='*.vue' --exclude-dir=node_modules

    Where a SPEC lists the backends too, propose the change: do not edit a SPEC unless you were asked to.

Check it worked ​

sh
uv run pytest tests/unit/coganchor tests/unit/flows tests/unit/runtime/watching \
    tests/integration/test_agents_<name>.py tests/integration/test_core_layering.py

tests/integration/test_core_layering.py is the one that fails when a step imported across a layer it may not. Then the real thing, which takes minutes and spends tokens:

sh
uv run pytest -n0 tests/system/test_harness_task.py -k <name>

Adding a command ​

  1. A module under cli/ if it takes a parser of its own, and a thin wrapper in cli/__init__.py.
  2. An entry in COMMANDS. A line Humanize runs for itself, rather than one a person types, goes in INTERNAL instead, which hmz internal routes.
  3. Its output through cli/output.py where the command has a --json, so a stray print never lands in the stream a program reads.
  4. A terse help. Every flag's help, command summary, description and epilog is thirty words or fewer; the detail goes in CLI reference. A unit test in tests/unit/cli/ reaches every parser through main and fails on anything longer. A subcommand a command routes by hand, as hmz internal anchor routes serve, needs a line in its ROUTES.

Check it with uv run hmz --help, which lists every entry of COMMANDS, and with uv run hmz <command> --help.

SPECs ​

specs/ states everything Humanize shows to the outside, in MUST, MUST NOT and MAY terms, and nothing about how it is built. specs/api/ holds the interfaces: the flow API, the SDK, the daemon protocol, the HTTP API, the command line, settings.yaml, and every file Humanize may leave on disk. specs/features/ holds one file per feature. specs/README.md holds the rules every SPEC is written by and the terms they share. Pick a layer in the diagram to open the SPEC that governs what it shows. Change the code to match the SPEC, and do not edit a SPEC unless you were asked to. Propose a SPEC change separately. How the layers are built and may name one another is this page's business, not a SPEC's.

Every SPEC
specs/README.mdHow a SPEC is written, numbered and cited; the terms every SPEC shares
specs/api/flows.mdThe flow API: what a flow imports, declares and is handed
specs/api/sdk.mdhmz.sdk: how a program that is not Humanize drives it
specs/api/daemon.mdThe daemon's protocol, as a frontend sees it
specs/api/http.mdEvery HTTP endpoint hmz web serves
specs/api/cli.mdEvery command line
specs/api/settings.mdEvery key of settings.yaml, and every environment variable read
specs/api/files/home.md~/.hmz
specs/api/files/workspace.mdWhat Humanize may leave in a workspace, and what never
specs/api/files/epics.mdAn epic's directory, its records, and .hmzepic archives
specs/api/files/machine.mdPer-user temporary and cache directories
specs/api/files/harness-homes.mdWhat Humanize keeps or builds for each CLI's home
specs/api/files/desktop.mdThe macOS app's files
specs/api/files/targets.mdWhat Humanize may leave on another machine, container or job
specs/features/epics.mdStarting, stopping, budgeting and picking up an epic
specs/features/agents.mdDriving coding agent CLIs: configuration, permissions, faults
specs/features/accounts.mdWhich account an agent runs as
specs/features/placements.mdWhere a CLI runs against where its work is, and mirrors
specs/features/environments.mdLocal, ssh, docker, swarm, Apple container and Slurm envs
specs/features/chat.mdThe chat flow, in every frontend
specs/features/btw.mdSide questions
specs/features/monitoring.mdThe live monitor, cost, notices, traces and profiles
specs/features/backups.mdGit backups, export and import
specs/features/telemetry.mdWhat is reported, and what never
specs/features/updates.mdhmz update and update notices
specs/features/daemons.mdOne daemon per machine, and remote daemons
specs/features/tui.mdEvery behaviour the terminal interface must have
specs/features/web.mdEvery behaviour the web interface must have
specs/features/desktop.mdHumanize Desktop, the macOS app in desktop/: a window on hmz web

Next steps ​

Released under the Apache-2.0 licence.