Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e43dcec
ci: write a release's notes since the release before it, not its last…
futrime Oct 13, 2026
7fe0cd4
fix(web): line up controls that share a row (#395)
futrime Oct 13, 2026
75650a4
ci: find the release pull request by its branch to run CI on, never o…
futrime Oct 13, 2026
8391ad1
fix: line up the sidebar's top row with the traffic lights, and every…
futrime Oct 13, 2026
0fd5a7a
ci: approve the release pull request's own CI run instead of dispatch…
futrime Oct 13, 2026
6c267ab
fix(desktop): keep a strip for the traffic lights over the sidebar's …
futrime Oct 13, 2026
520f8b9
fix(desktop): keep the traffic lights' strip across the whole window …
futrime Oct 13, 2026
3432deb
feat: chat like Claude, no all-sessions log, Exomyth fixes, split pro…
futrime Oct 13, 2026
beedb87
feat: many chats, each its own epic beside the workspace's flow, fold…
futrime Oct 13, 2026
881571a
feat(runtime): let docker and swarm runtimes share a host: name prefi…
antoinegg1 Oct 14, 2026
ccb7bf0
fix(coganchor): let turns working in one mirror at once see each othe…
antoinegg1 Oct 14, 2026
fa0a18f
fix(flows): keep and carry on supervised sessions, whose CLI runs her…
antoinegg1 Oct 14, 2026
9a5c8e8
fix(telemetry): scrub reports as bundles are, timings and profiles to…
futrime Oct 14, 2026
85ce368
fix(coganchor): keep the turns of the shared-mirror test in the mirro…
futrime Oct 14, 2026
b3e9c1c
fix(agents): retry Codex's dropped streams and start every harness in…
antoinegg1 Oct 14, 2026
83c92c3
feat: rewrite specs as api/ and features/, pick the btw agent per epi…
futrime Oct 14, 2026
19a082f
fix(coganchor): build a harness's virtual home on its own machine und…
futrime Oct 14, 2026
16455b5
feat(runtime)!: say where a harness runs with one setting, not four p…
futrime Oct 14, 2026
7231299
fix(tui,web): call an execution of a flow an epic in every label and …
futrime Oct 14, 2026
6cda4bc
docs(releasing): say what main says of what publish.yml attaches to a…
futrime Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ For tests:
- `uv run pytest` runs unit and integration, which is what CI runs.
- `tests/system/` is OPTIONAL: real agents on real tasks, 5–30 minutes and real tokens per test, and in `tests/system/web/` the web page driven in real browsers (Playwright), never in CI. Unit and integration tests MUST NOT drive a browser. Do NOT run it on every change; run only the file that covers an end-to-end path you changed, e.g. `uv run pytest tests/system/test_flows_ralph.py`.

For `specs/*.md`:
For `specs/**/*.md`:

- MUST strictly adhere to specs at `specs/`.
- MUST NOT modify any SPEC UNLESS explicitly instructed to do so.
Expand Down
2 changes: 1 addition & 1 deletion docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export default defineConfig({
collapsed: false,
items: [
{ text: 'Remote execution', link: '/user/remote-execution' },
{ text: 'Where the agent runs (affinity)', link: '/user/remote-execution#where-the-agent-runs' },
{ text: 'Where the agent runs (harness setting)', link: '/user/remote-execution#where-the-agent-runs' },
{ text: 'Containers', link: '/user/containers' },
],
},
Expand Down
4 changes: 2 additions & 2 deletions docs/.vitepress/theme/components/HmzAnchor.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
// An agent on your machine whose work lands on another one. The routes are the default
// arrangement in `specs/coganchor/SPEC.md` and `reference/remote-execution`: the project's
// An agent on your machine whose work lands on another one. The routes are those of harness
// location local in `specs/features/placements.md` and `reference/remote-execution`: the project's
// files, the commands and whatever those commands reach are the target's; the agent's own link
// to its model provider, its settings and its login stay here. The agent reads and writes a
// local copy of the target's workspace, which humanize keeps in step with it.
Expand Down
2 changes: 1 addition & 1 deletion docs/.vitepress/theme/components/HmzLoops.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// code between the turns, and another flow can call it, handing down only the roles it
// declared. Pick a flow to watch its loop. Each loop is the one the flow really runs, and all
// four ship with humanize: `src/hmz/flows/builtin/<name>/__init__.py`. What a called flow is
// handed is `specs/flows.md`. The CLIs in the sockets are only examples. Under the lanes, the
// handed is `specs/api/flows.md`. The CLIs in the sockets are only examples. Under the lanes, the
// line of the flow running at each moment, written as the loop is: a session is opened with
// `spawn()` and is only the conversation; where a turn works is the run's (`run(..., env=None)`,
// the workspace, when it is not named). Each flow's page is on humanfia.ai.
Expand Down
47 changes: 26 additions & 21 deletions docs/.vitepress/theme/components/HmzStack.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ const ALLOWED: Record<string, string[]> = {
'hmz.runtime.epic': ['hmz.coganchor', 'hmz.runtime.tracing'],
'hmz.runtime.tracing': ['hmz.coganchor'],
'hmz.runtime.watching': ['hmz.coganchor'],
'hmz.runtime.exporting': ['hmz.coganchor', 'hmz.runtime.epic', 'hmz.runtime.tracing'],
'hmz.runtime.exporting': [
'hmz.coganchor',
'hmz.runtime.epic',
'hmz.runtime.telemetry',
'hmz.runtime.tracing',
],
'hmz.runtime.backing': ['hmz.runtime.epic', 'hmz.runtime.exporting', 'hmz.runtime.settings'],
'hmz.runtime.runner': [
'hmz.coganchor',
Expand Down Expand Up @@ -109,139 +114,139 @@ const LAYERS: Layer[] = [
dotted: 'hmz.cli',
here: 'The command line: a new command, and how a command writes its output.',
note: 'Not in the table. It joins the layers, so it may import any of them, inside the command that needs it.',
spec: 'cli.md',
spec: 'api/cli.md',
ref: '/reference/cli',
},
{
id: 'tui',
dotted: 'hmz.tui',
here: 'The terminal interface: slash commands, keys, menus, and what they draw.',
note: 'Reaches the runtime only through `daemon`, as one frontend of the runs a host holds.',
spec: 'tui.md',
spec: 'features/tui.md',
ref: '/reference/tui',
},
{
id: 'web',
dotted: 'hmz.web',
here: 'The web interface: the runs of this directory in a browser on this machine, and what a page reads and asks of them.',
note: 'Reaches the runtime only through `daemon`, as one frontend of the runs a host holds, and draws a run with `watching` as the terminal interface does.',
spec: 'web.md',
spec: 'features/web.md',
ref: '/reference/web',
},
{
id: 'sdk',
dotted: 'hmz.sdk',
here: 'The Python API a tool outside humanize calls: `from hmz.sdk import Hmz`.',
note: 'Nothing imports it: it is the way in from outside.',
spec: 'sdk.md',
spec: 'api/sdk.md',
ref: '/reference/sdk',
},
{
id: 'daemon',
dotted: 'hmz.daemon',
here: "A workspace's runs held apart from the terminal, and the socket every frontend reaches them over.",
note: 'Names the runtime by its front door, `hmz.runtime`.',
spec: 'daemon.md',
spec: 'api/daemon.md',
ref: '/reference/daemon',
},
{
id: 'runtime',
dotted: 'hmz.runtime',
here: 'The front door: hands `Hmz`, `Run` and `Refused` through from `doing` and `runner`.',
spec: 'runtime/SPEC.md',
spec: 'features/epics.md',
},
{
id: 'doing',
dotted: 'hmz.runtime.doing',
here: 'humanize as one object, `Hmz`. Anything `cli`, `tui` and `daemon` would each write goes here once.',
spec: 'runtime/doing.md',
spec: 'api/sdk.md',
ref: '/reference/sdk',
},
{
id: 'runner',
dotted: 'hmz.runtime.runner',
here: 'The `hmz exec` line: reading it, finding the flow, checking it, refusing it, running it.',
spec: 'runtime/SPEC.md',
spec: 'features/epics.md',
ref: '/reference/cli',
},
{
id: 'flowing',
dotted: 'hmz.runtime.flowing',
here: 'Everything done to a flow: the engine, budgets, resuming, the agent and environment drivers, the `-a` `-e` `-p` parsers, the fakes, finding flows and flowverses.',
spec: 'runtime/flowing.md',
spec: 'features/epics.md',
ref: '/reference/flows',
},
{
id: 'flows',
dotted: 'hmz.flows',
here: 'The flow API: the types a flow imports. Each is a promise to somebody else\'s repository.',
note: 'Names `flowing` only inside `flow`, `load` and `Outworlder.new`, never at import. Importing `hmz.flows` loads nothing else of humanize.',
spec: 'flows.md',
spec: 'api/flows.md',
ref: '/reference/flows',
},
{
id: 'exporting',
dotted: 'hmz.runtime.exporting',
here: 'One whole run packaged up to send somewhere, and read back in.',
spec: 'runtime/SPEC.md',
spec: 'features/backups.md',
},
{
id: 'backing',
dotted: 'hmz.runtime.backing',
here: 'Epics backed up to git repositories, behind everything else.',
spec: 'runtime/SPEC.md',
spec: 'features/backups.md',
},
{
id: 'epic',
dotted: 'hmz.runtime.epic',
here: 'One run of one flow, written down as it happens.',
spec: 'runtime/SPEC.md',
spec: 'api/files/epics.md',
},
{
id: 'tracing',
dotted: 'hmz.runtime.tracing',
here: 'The backends\' own logs read back as one trace: a reader per backend.',
spec: 'runtime/tracing.md',
spec: 'features/monitoring.md',
ref: '/reference/tracing',
},
{
id: 'watching',
dotted: 'hmz.runtime.watching',
here: 'A run read as it happens: who is working, what it has cost, and the view a side question is asked with. Every frontend draws the same figures from it.',
spec: 'runtime/watching.md',
spec: 'features/monitoring.md',
},
{
id: 'coganchor',
dotted: 'hmz.coganchor',
here: 'Driving a coding agent CLI: backends, drivers, accounts, models, fallbacks, prices, machines, and the anchor.',
spec: 'coganchor/SPEC.md',
spec: 'features/agents.md',
ref: '/reference/agents',
},
{
id: 'serve',
dotted: 'hmz.coganchor.serve',
here: 'The half of the anchor that ships to the target machine.',
note: 'The target may be any architecture, so it may import `hmz.coganchor.proto`, and the fence and its Linux bindings where a fence is asked about, and nothing else.',
spec: 'coganchor/serve.md',
spec: 'features/placements.md',
ref: '/reference/remote-execution',
},
{
id: 'telemetry',
dotted: 'hmz.runtime.telemetry',
here: 'What humanize reports about itself, and whether it does.',
spec: 'runtime/SPEC.md',
spec: 'features/telemetry.md',
},
{
id: 'settings',
dotted: 'hmz.runtime.settings',
here: 'What each workspace was set up to run.',
spec: 'runtime/SPEC.md',
spec: 'api/settings.md',
},
{
id: 'kept',
dotted: 'hmz.runtime.kept',
here: 'An agent written down, in the `CLI/MODEL:EFFORT` shape `-a` takes.',
spec: 'runtime/SPEC.md',
spec: 'features/agents.md',
},
]

Expand Down
6 changes: 3 additions & 3 deletions docs/.vitepress/theme/components/HmzTimeline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ const scene = useScene({
cam.shot(l.shots.push, T3, 1.4)
tl.to(one('.divider'), { autoAlpha: 1, duration: 0.01 }, T3 + 0.4)
tl.to(one('.divider-line'), { drawSVG: '100%', duration: 0.8, ease: 'cine' }, T3 + 0.4)
// Only a run started with `--profile` records its programs.
// Only a run started with `--profile-environment` records its programs.
tl.fromTo(one('.profile'), { autoAlpha: 0, x: 14 }, { autoAlpha: 1, x: 0, duration: 0.5, ease: 'back.out(1.8)' }, T3 + 0.5)
tl.fromTo(q('.profile rect'), { strokeWidth: 1.2 }, { strokeWidth: 3, duration: 0.2, yoyo: true, repeat: 1, ease: 'power2.out' }, T3 + 0.9)
STARTED.forEach((link, k) => {
Expand Down Expand Up @@ -425,8 +425,8 @@ const scene = useScene({

<!-- Only a run started with it records its programs: over the links, so it reads. -->
<g class="profile">
<rect :x="L.x1 - 150" :y="L.divider - 10" width="150" height="20" rx="10" />
<text :x="L.x1 - 75" :y="L.divider + 4" text-anchor="middle">hmz exec … --profile</text>
<rect :x="L.x1 - 230" :y="L.divider - 10" width="230" height="20" rx="10" />
<text :x="L.x1 - 115" :y="L.divider + 4" text-anchor="middle">hmz exec … --profile-environment</text>
</g>

<!-- The rows. -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ const EXAMPLES = [

const USAGE = [
'usage: hmz exec [-h] -f FLOW [-a ROLE=SPEC[,...]] [-e ROLE=SPEC[,...]]',
' [-p KEY=VALUE[,...]] [--profile] [--resume] [--json]',
' [-p KEY=VALUE[,...]] [--profile-flow] [--profile-environment]',
' [--resume] [--json]',
' task',
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ const scene = useScene({
:beats="BEATS"
sim
mobile-ratio="45 / 83"
label="A container per environment. The line hmz exec -f boxed -e box=docker@gpubox/home/me/myproject names the role (box), the backend (docker), the saved daemon (gpubox) and the directory (/home/me/myproject). The daemon gpubox, saved to hand out 4 CPUs and 8G, starts a container for the role from its image, python:3.12-slim, with exactly the CPUs and memory the role declares, here 2 CPUs and 4G. The directory on the daemon's host is mounted into the container at the same path. The agent's CLI runs on this machine by default, its harness here, since the image has no CLI and the daemon's affinity is blank; it works in a mirror of the directory kept under ~/.hmz/envs/mirrors/. Each command it runs, such as hostname, lands in the container through docker exec, and prints the container's id. Its edit lands in /home/me/myproject. When the run ends the container goes, its share goes back to the daemon, and the work stays in the directory."
label="A container per environment. The line hmz exec -f boxed -e box=docker@gpubox/home/me/myproject names the role (box), the backend (docker), the saved daemon (gpubox) and the directory (/home/me/myproject). The daemon gpubox, saved to hand out 4 CPUs and 8G, starts a container for the role from its image, python:3.12-slim, with exactly the CPUs and memory the role declares, here 2 CPUs and 4G. The directory on the daemon's host is mounted into the container at the same path. The agent's CLI runs on this machine, its harness here, since the image has no CLI and the daemon is saved with its harness set to local; it works in a mirror of the directory kept under ~/.hmz/envs/mirrors/. Each command it runs, such as hostname, lands in the container through docker exec, and prints the container's id. Its edit lands in /home/me/myproject. When the run ends the container goes, its share goes back to the daemon, and the work stays in the directory."
>
<svg :viewBox="`0 0 ${L.w} ${L.h}`" aria-hidden="true">
<defs>
Expand Down Expand Up @@ -381,7 +381,7 @@ const scene = useScene({
<rect class="chip-ring" :x="-(L.mach.w - 24) / 2" y="-14" :width="L.mach.w - 24" height="28" rx="7" />
</g>
</g>
<g class="aff"><text class="dim" :x="L.mach.x + 12" :y="L.mach.y + 78">affinity blank: the image has no CLI,</text></g>
<g class="aff"><text class="dim" :x="L.mach.x + 12" :y="L.mach.y + 78">harness local: the image has no CLI,</text></g>
<g class="aff"><text class="dim" :x="L.mach.x + 12" :y="L.mach.y + 93">so the CLI runs here, with your sign-in</text></g>
<g class="mirror">
<rect class="mirror-box" :x="L.mach.x + 12" :y="L.mach.y + 102" :width="L.mach.w - 24" height="42" rx="7" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ const BACKENDS: Backend[] = [
ENV,
],
note:
'A gateway way is written into pi’s own models.json as a provider named humanize-<hash>, ' +
'A gateway way is written into a per-process copy of pi’s models.json as a provider named humanize-<hash>, ' +
'its key as $PI_GATEWAY_KEY rather than as itself, and every turn is run with --provider ' +
'naming it.',
},
Expand Down
20 changes: 10 additions & 10 deletions docs/.vitepress/theme/components/user-remote/RemoteSplit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
// host's own paths, on the host's network. A session is only history, kept here: the builder's
// turns name `env=envs["box"]` when they run and work on build-box, the reviewer's name the
// workspace and work here, and the builder's one session takes its next turn on build-box
// again. Last, where the harness itself runs: a saved host's affinity, tried in order, the
// next place only when the one before has no room.
// again. Last, where the harness itself runs: a saved host's harness setting, one place and
// nowhere else -- `local` for a host with no CLI of its own.
import { computed, ref } from 'vue'

import HmzStage from '../../motion/HmzStage.vue'
Expand All @@ -21,7 +21,7 @@ const BEATS = [
"ssh carries every command to build-box: the host's files, at its paths, on its network",
'The builder spawns a session here, and runs its turn with env=envs["box"]: on build-box',
"The reviewer's turn runs in the workspace, here; the builder's session takes its next turn on build-box",
"Where the harness runs: a saved host's affinity, tried in order",
"Where the harness runs: the saved host's harness setting, here local",
]

/** A mono character's advance at 11px. */
Expand Down Expand Up @@ -49,9 +49,9 @@ const NARROW_CODE: Line[] = [
{ text: ' env=envs["workspace"])', step: 3, env: { col: 4, len: 21 } },
]

/** The affinity of build-box, once saved, in the order its places are tried. */
/** What build-box's harness setting may say; it is saved as `local`. */
const PLACES = [
{ name: 'self', w: 40 },
{ name: 'env', w: 34 },
{ name: 'local', w: 46 },
{ name: 'ssh:gpu2', w: 66 },
]
Expand Down Expand Up @@ -230,7 +230,7 @@ const scene = useScene({
over(dotC(0, 1), term, hue.builder, T3 + 4.2)
pulse(tl, one('.folder'), T3 + 5.2, 1.03)

// 4 · where the harness runs: build-box saved with an affinity, its places tried in order.
// 4 · where the harness runs: build-box saved with its harness `local`, not `env`.
const T4 = T3 + 6.0
tl.addLabel('beat-4', T4)
cam.shot(l.shots.box, T4, 1.4)
Expand Down Expand Up @@ -264,7 +264,7 @@ const scene = useScene({
:scene="scene"
:beats="BEATS"
mobile-ratio="9 / 16"
label="Remote execution's split, with the onbox flow. This machine keeps the agent's CLI, signed in, and its sessions, which are only history. ssh to build-box carries every command the agent runs, at the host's own paths, /home/me/build/myproject, on the host's network. The builder spawns its session here with working = await builder.spawn(), and runs its turn with builder.run(task, session=working, env=envs['box']): the turn works on build-box, where python test_calc.py runs. The flow runs git diff on build-box; the reviewer spawns its session with reading = await reviewer.spawn() and runs its turn with env=envs['workspace'], in the workspace here. The builder's same session then takes its next turn on build-box. Where the agent's CLI itself runs is the affinity of a saved host, its places tried in order: self has no room on a host without claude, so local is tried, which always has room, and the builder's harness runs here; ssh:gpu2 after it is never tried."
label="Remote execution's split, with the onbox flow. This machine keeps the agent's CLI, signed in, and its sessions, which are only history. ssh to build-box carries every command the agent runs, at the host's own paths, /home/me/build/myproject, on the host's network. The builder spawns its session here with working = await builder.spawn(), and runs its turn with builder.run(task, session=working, env=envs['box']): the turn works on build-box, where python test_calc.py runs. The flow runs git diff on build-box; the reviewer spawns its session with reading = await reviewer.spawn() and runs its turn with env=envs['workspace'], in the workspace here. The builder's same session then takes its next turn on build-box. Where the agent's CLI itself runs is the harness setting of a saved host, one place and nowhere else: env would need claude on build-box, which has none, so build-box is saved with local, and the builder's harness runs here; ssh:gpu2, another saved runtime, is not chosen."
>
<svg :viewBox="`0 0 ${L.w} ${L.h}`" aria-hidden="true">
<defs>
Expand Down Expand Up @@ -340,9 +340,9 @@ const scene = useScene({
</g>
</g>
<path class="cross" :d="`M${placeX(0) + 4} ${L.box.y + 178} L${placeX(0) + PLACES[0].w - 4} ${L.box.y + 178}`" />
<g class="gpu-skip"><text class="note" :x="placeX(2) + PLACES[2].w + 6" :y="L.box.y + 182">never tried</text></g>
<g class="msg msg-0"><text class="note-word bad" :x="L.box.x + 12" :y="L.box.y + 205">self: no claude on build-box, no room</text></g>
<g class="msg msg-1"><text class="note-word good" :x="L.box.x + 12" :y="L.box.y + 205">local: always has room, so here</text></g>
<g class="gpu-skip"><text class="note" :x="placeX(2) + PLACES[2].w + 6" :y="L.box.y + 182">not chosen</text></g>
<g class="msg msg-0"><text class="note-word bad" :x="L.box.x + 12" :y="L.box.y + 205">env: no claude on build-box</text></g>
<g class="msg msg-1"><text class="note-word good" :x="L.box.x + 12" :y="L.box.y + 205">local: build-box's setting, so here</text></g>

<!-- The flow. -->
<g class="code">
Expand Down
Loading
Loading