Skip to content

Remote execution ​

Reference for anchoring: how a turn whose work lands on another machine is run, where each part of it runs, what crosses between machines, and what is refused. The layer is CogAnchor, hmz.coganchor.anchor and its command line is hmz internal anchor. Where an agent's turns land is set per agent by machine= and per flow session by its environment; where the harness runs is set per saved runtime by its harness setting (below).

Terms ​

TermDefinition
AnchorThe settings that send one agent's turns to another machine: an AnchorConfig. An agent with no anchor runs its turns on this machine as ordinary processes.
TargetThe machine the work lands on: the files the agent reads and writes and the commands it runs. Named by a target spelling.
Serving halfhmz internal anchor serve, running on the target. It answers file operations and runs commands inside the directories it exports.
HarnessThe agent's own process (the coding agent CLI) and, when supervised, the supervisor tracing it.
SupervisorA seccomp-filtered ptrace tracer that stops the agent's path, exec and connect syscalls and answers them from the target.
Mirror (shadow)A directory on the harness's machine that reproduces the target's workspace, filled lazily. The supervised agent works in it.
ArrangementHow one anchor runs its harness relative to the target: supervised here, supervised on another machine (afar), or the target's own CLI (native). A flow session's harness location becomes one of them: local is supervised, standalone is afar, env is native.
Broker (rendezvous)A TCP server that introduces a harness and a serving half on two machines that cannot dial each other, and relays their bytes when no direct connection can be made.
TicketA 128-bit secret naming one meeting at a broker.
ExportA directory the serving half exposes, as VIRTUAL[:REAL]: the path the agent names, and where it really is on the target.
FenceWhat an agent's processes may reach (Agents › The fence). Under an anchor it is held on both machines (below).

Arrangements ​

Two fields decide the arrangement: native and harness.

Supervised (harness local)Afar, beside the work (harness standalone)Afar, third machine (harness standalone)Native (harness env)
Settingharness="local" (default), native=Falseharness="same", or a harness target equal to targetharness= an ssh:// or docker:// target other than targetnative=True
Agent process and supervisorthis machinethe targetthe harness machinenone: the target's installed CLI runs there
Mirrorthis machine, at shadow (default: the workspace's own path)the target, in its mirror cache, kept between turnsthe harness machine, in its mirror cache, kept between turnsnone
Account, CLI state, model connectionthis machinethe targetthe harness machinethe target, for each turn
What crosses to this machineone request per trapped syscall, file contents, command I/Othe agent's stdin, stdout, stderrthe agent's stdin, stdout, stderr; file work goes between the other two through a rendezvousthe CLI's stdin, stdout, stderr, signals, exit status
Capabilitiesanchor:supervisedanchor:supervised, anchor:afaranchor:supervised, anchor:afaranchor:native-cli
  • native=True with a harness that parses to a non-local machine is refused (see Validation).
  • A harness whose spelling resolves to a local target (local, local:DIR, or same with a local target) is the supervised arrangement: AnchorConfig(target="local:/x", harness="same").capabilities is {"anchor:supervised"}.
  • AnchorConfig.capabilities is computed from the settings alone; nothing connects.
text
supervised                          native
this machine        target          this machine          target
+-------------+     +-----------+    +---------------+    +-------------+
| agent CLI   |     |           |    | hmz internal  |    | agent CLI   |
|  | syscalls |     | anchor    |    | anchor        |--->|  files,     |
| supervisor -+---->| serve     |    | --native      |    |  commands,  |
| mirror      |     | files,    |    |  3 streams    |    |  network    |
+-------------+     | commands  |    +---------------+    +-------------+
                    +-----------+

afar, third machine
this machine            harness machine            target
+----------------+      +-------------------+      +--------------+
| hmz internal   |----->| agent CLI         |      | anchor serve |
| anchor         | ssh/ | supervisor -------+----->| files,       |
|  3 streams     |docker| mirror (kept)     | peer | commands     |
| broker         |<-----+-------------------+------+              |
+----------------+      +-------------------+      +--------------+

Defined in src/hmz/coganchor/anchor.py (AnchorConfig.capabilities), src/hmz/coganchor/places.py, src/hmz/coganchor/elsewhere.py.

A project's own programs ​

A CLI starts some programs because the project it works in says so: hooks, MCP servers it spawns over stdio, helper commands, plugins. It starts them where the CLI runs. A flow session runs only where that is the work's own machine:

ArrangementCLI and work on one machineA project that would start programs
No anchor (work here)yesallowed
Nativeyesallowed
Afar, beside the work (harness="same")yesallowed
Supervisednorefused
Afar, third machinenorefused

In a supervised or third-machine arrangement, Humanize reads the files the CLI's Profile.launchers lists when a session opens, moves or carries on a conversation, and again before each turn. It reads them from the target's copy of the workspace, in the session's directory and each directory above it up to the workspace. A file counts if a listed key holds a value other than null, false or an empty string, table or list. A file listed with no keys, or a listed plugin directory that is not empty, counts by being there. If anything counts, the open or the turn fails with UnsupportedOperation before the CLI starts. The error names every file and key it found:

text
claude runs on another machine than its work on ssh://box, and the project's own configuration
there would have it start programs on claude's machine rather than the work's:
.claude/settings.json: hooks.*.*.hooks.*.command; .mcp.json: mcpServers.*.command. Run it
natively on ssh://box, or take those out of the project

A file that cannot be parsed, or that the target will not let be read, counts and is named (unreadable). If the target cannot be reached, the open or the turn fails with HarnessDropped. The per-CLI files and keys are listed under Agents.

Defined in src/hmz/coganchor/crossing.py and HarnessDriver.crossed in src/hmz/runtime/flowing/harnesses.py.

Targets ​

A target spelling names the machine the work lands on. The same grammar names a harness machine in AnchorConfig.harness.

SpellingMachineHow it is reachedSomething started on itRequirements there
localthis machine, standing in for anotherthe serving half is a child process hereyesnone
local:DIRthis machine, with DIR standing in for the far side's copy of the workspaceas localyesnone
ssh://[USER@]HOST[:PORT][?KEYWORD=VALUE&...]a host reached with the system sshthe serving half is bootstrapped over ssh and spoken to on its pipesyesPOSIX /bin/sh, Python >= 3.12
docker://CONTAINER[?OPTIONS][@ENDPOINT]a running containerthe serving half is bootstrapped with docker exec -i, running as the container's user or the one OPTIONS names, with its variables and under its wrapperyesdocker here; /bin/sh and Python >= 3.12 in the container
apple-container://CONTAINERa running Apple container on this Macthe serving half is bootstrapped with container exec -i, running as the container's useryesApple's container here; /bin/sh and Python >= 3.12 in the container
slurm://JOBID[?python=PATH]the node a running Slurm job of this user's was giventhe serving half is bootstrapped with srun --jobid=JOBID --overlap, a step of that job sharing its allocation, running as this useryessrun here; /bin/sh and Python >= 3.12 on the node (PATH, absolute or under ~/, is tried first)
tcp://HOST:PORTa serving half left listeninga TCP connection, TCP_NODELAY, 30 s connect timeoutnoa served target and its token
peer://TICKET@HOST:PORTa serving half waiting at a brokerthe broker at HOST:PORT, presenting TICKETnowritten by Humanize only

Parsing rules:

  • ssh://: the authority is split at its last : into host and port when the part after it is all digits; otherwise the whole authority is the host (user@host included). After ?, options are KEYWORD=VALUE pairs separated by &, URL-quoted, parsed strictly. KEYWORD matches [A-Za-z][A-Za-z0-9]*; VALUE must be non-empty and must not contain a newline, carriage return, NUL or ". Each option is passed to ssh as -o KEYWORD=VALUE (the value double-quoted where it contains whitespace), except F, passed as -F VALUE.

  • docker://: the container name ends at the first ? or @; what follows the first @ is a docker endpoint. An endpoint of local is the same target as none. Between a ? and the @, options are KEY=VALUE pairs separated by &, URL-quoted (an @ in a value as %40), parsed strictly, none holding a newline, carriage return or NUL, and passed to every docker exec on the target's road in the order written:

    OptionPassed asValue
    user=UID[:GID]-u UID[:GID]numeric; at most once
    env=NAME=VALUE-e NAME=VALUENAME matches [A-Za-z_][A-Za-z0-9_]*; any number
    exec=WORDa word after the container name, ahead of the commandnon-empty; any number

    The exec words are a wrapper: a program in the container (with its arguments) that runs the command it is given after them, with the same streams, and exits with its status, as exec "$@" does. The target is written on command lines and in logs, so an env value is no place for a secret. A target naming a user keeps its archive and mirrors in /tmp/humanize-<UID> and /tmp/humanize-mirrors-<UID> instead (see Bootstrapping). The options are part of the target: two spellings telling one container different things are two targets, each given the archive once.

  • apple-container://: the container name, holding no @, /, ? or #.

  • slurm://: the job's id, all digits; after ?, only python=PATH, URL-quoted, PATH absolute or under ~/ (the node's home) and holding no newline, carriage return, NUL or ".

  • tcp://: HOST:PORT split at the last :; the port must be digits.

  • peer://: TICKET@HOST:PORT; brackets around an IPv6 host are stripped.

InputValueError message
an unknown schemeunsupported target 'bogus://x'; expected ssh://HOST, docker://CONTAINER[@ENDPOINT], apple-container://CONTAINER, slurm://JOBID[?python=PATH], tcp://HOST:PORT, peer://TICKET@HOST:PORT or local[:PATH]
a slurm:// query other than one python=PATHmalformed target 'slurm://12?py=x'; expected slurm://JOBID[?python=PATH]
tcp:// without a numeric portmalformed target 'tcp://h'; expected tcp://HOST:PORT
an ssh option that is not onemalformed target 'ssh://h?Bad-Key=1'; Bad-Key='1' is not an ssh option
an ssh query that does not parsemalformed target '<spec>'; expected ssh://HOST?KEYWORD=VALUE&...
a docker option that is not onemalformed target 'docker://c?user=me'; user='me' is not a docker exec option
a docker query that does not parsemalformed target '<spec>'; expected docker://CONTAINER?user=UID[:GID]&env=NAME=VALUE&exec=WORD&...
a bad docker endpointunsupported docker endpoint 'weird'; expected local, unix:///PATH, tcp://HOST:PORT[?tls=DIR], ssh://[USER@]HOST[:PORT][?KEYWORD=VALUE&...] or context:NAME
a malformed meetingmalformed meeting '<spec>'; expected TICKET@HOST:PORT

Bootstrapping the serving half ​

For local, ssh://, docker://, apple-container:// and slurm://, Humanize starts hmz internal anchor serve --stdio --export WORKSPACE[:REAL] on the far side and speaks the protocol over its stdin and stdout.

StepBehaviour
ArchiveA zipapp of hmz.coganchor and the command line reaching it, with an hmz-<version>.dist-info/METADATA so that __version__ and hmz --version answer the same on the target, built once per source tree into $TMPDIR/humanize-<uid>/humanize-<digest>.pyz (directory 0700, refused if anyone else can write it), beside a <stamp>.digest naming the archive each source tree (by version, path, file sizes and modification times) built. Two checkouts or versions never share an archive. A run touches the archive it uses at least hourly; a build removes anything in the directory, and the $TMPDIR/humanize-<uid>.pyz an earlier Humanize shared, untouched for 14 days. A directory anyone else can write fails the install with ConnectionError: could not install Humanize on <target>: …. The driving half (agents, providers, machines, backends.py, models.py, fallbacks.py, prices.py) is left out. Pure standard library; runs on any architecture.
Name on the targethumanize-<digest>.pyz, <digest> the first 16 hex digits of the archive's SHA-256.
Cache on the target$HOME/.cache/humanize/ over ssh and on a Slurm node, /tmp/humanize/ in a container, and /tmp/humanize-<UID>/ in a container whose target names user=UID[:GID]. That one, and the target's mirror directory, are made by the far side's shell before anything is put in them or run from them, refused unless each is a directory (not a link) the user owns, and made 0700: Humanize: <dir> is not a directory this user owns, exit 1.
InstallRun with the interpreter below, HUMANIZE_BUNDLES set to the cache and mkdir -p'd by the far side's shell, the archive on stdin: refuse it (Humanize: <n> bytes arrived that are not humanize-<digest>.pyz) unless its SHA-256 starts with <digest>; keep a file already there only if its own SHA-256 does too, touching it; otherwise write <file>.<pid> and move it into place. Then remove other humanize-*.pyz* in the cache untouched for 14 days. Every line that runs the archive first touch -ces it. Concurrent installs are safe. Each (target, digest) pair is pushed at most once per process.
InterpreterThe first candidate that exists and reports a version >= 3.12, tried in order: a slurm:// target's python, then python3, python3.14, python3.13, python3.12, /opt/homebrew/bin/python3, /usr/local/bin/python3, /Library/Frameworks/Python.framework/Versions/Current/bin/python3, /usr/bin/python3.
No interpreterExit 127, stderr Humanize: no python 3.12 or newer on this machine; looked for: <candidates>.
Install failureConnectionError: could not install Humanize on <target>: <stderr>; for a container, ; the container said: <last 3 log lines> is appended.
ProtocolBoth halves send PROTOCOL_VERSION (currently 1). A mismatch fails the handshake with EPROTO, client speaks protocol <n>, this Humanize speaks 1, and closes the connection.

The handshake reply (Op.HELLO) carries version, hostname, platform (sys.platform), python, pid, exports ([{virtual, real}]) and fence ({fs: bool, net: bool}, see below). A token mismatch fails it with EACCES, invalid token.

ssh connections ​

Every ssh Humanize runs is:

text
ssh <target options> -T -o BatchMode=no -o ServerAliveInterval=30 <reuse options> [-p PORT] HOST '<quoted command>'
  • The target's own options come first; ssh keeps the first value it is given for a keyword.
  • Reuse options, unless HUMANIZE_SSH_REUSE is off, 0, no or false (trimmed, case-insensitive) or set and empty: -o ControlMaster=auto -o ControlPersist=120 -o ControlPath=<dir>/%C[-<8 hex>], where <dir> is $XDG_RUNTIME_DIR/humanize-ssh-<uid> (else the system temporary directory), mode 0700 — or /tmp/humanize-ssh-<uid> where that path would leave the socket longer than a Unix socket path may be (103 bytes; a Mac's $TMPDIR alone is about 49). Connections are not shared where <dir> is not a directory of the user's that nobody else may enter. The -<8 hex> suffix is a digest of the target's options, so two targets at one host with different options use different master connections.
  • The command is sent as one string, each word shell-quoted, prefixed with exec.

Slurm steps ​

Every command Humanize runs on a slurm://JOBID target is a step of that job:

text
srun --jobid=JOBID --overlap --nodes=1 --ntasks=1 --unbuffered --quiet --export=HOME,USER --chdir=/ <argv>
  • --overlap shares the job's allocation rather than waiting for a share of it; the step runs on the job's one node, within its CPUs, memory and GPUs.
  • The argv is passed as it is, not through a shell; nothing of this process's environment crosses but HOME and USER, nor its directory, which the node may not have.
  • Each step that is running holds listening ports on this machine, from the cluster's SrunPortRange, which may be only a few dozen ports shared by every user here: a cluster with SrunPortRange=30927-30951 holds about 8 connections at once. A Slurm runtime saved with via: tcp holds none: its job runs the serving half listening on its node, and srun is used only to put the archive there and read the port.

Defined in src/hmz/coganchor/transport.py (Target, Road, python_command, build_bundle), src/hmz/coganchor/proto.py, src/hmz/coganchor/serve/server.py.

AnchorConfig ​

hmz.coganchor.AnchorConfig is a frozen, keyword-only dataclass. Every field is an option of hmz internal anchor, and every option but --check and --log-level is a field; hmz.coganchor.argv reads one into the other and renders it back (python -Pm hmz internal anchor <options> AGENT ARGS...).

FieldTypeDefaultOptionOption defaultMeaning
targetstr"local"--target URL$HUMANIZE_TARGET, else localWhere the work lands. See Targets.
harnessstr"local"--harness WHERE$HUMANIZE_HARNESS, else localWhere the harness runs: local, same (the machine target names), or a target spelling.
brokerstr""--broker HOST$HUMANIZE_RENDEZVOUS, else ""The address both halves dial to be introduced. "": $HUMANIZE_RENDEZVOUS, else this machine's outward-facing IPv4 address.
workspacestr | NoneNone--workspace PATHNoneThe project directory as the target names it. None: this process's working directory.
chdirstr | NoneNone--chdir PATHNoneWhere inside the workspace the agent starts, as the target names it. Must be the workspace or under it.
remote_pathstr | NoneNone--remote-path PATHNoneWhere the workspace really is on the target, where that differs from workspace (the export's REAL).
shadowstr | NoneNone--shadow PATH$HUMANIZE_SHADOWThe mirror directory on the harness's machine. None: the workspace path itself; for a harness elsewhere, a kept mirror under that machine's mirror cache.
local_pathstuple[str, ...]()--local-path PATH (repeatable)Paths kept on this machine even inside the workspace.
local_execstuple[str, ...]()--local-exec PATH (repeatable)Programs under these paths run here, not on the target.
privatetuple[str, ...]()--private NAME (repeatable)Variables the agent has and the commands it runs on the target do not.
redirectstuple[tuple[str, str], ...]()--redirect FROM=TO (repeatable)Paths answered with other paths, kept on this machine. A directory covers everything under it.
netstr"local"--net {local,remote}localWhere the agent's own TCP connections go. Commands always use the target's network.
net_allowtuple[str, ...]()--net-allow HOST[:PORT] (repeatable)With net="remote", destinations kept local.
tokenstr | NoneNone--token TOKEN$HUMANIZE_TOKENThe secret a tcp:// target requires.
forceboolFalse--forceUse a mirror directory that holds unrelated files or was last used for another target.
nativeboolFalse--nativeRun the CLI installed on the target. See Native.
hushestuple[str, ...]()--hush NAME (repeatable)Native: variables removed from the CLI's environment on the target.
projectstuple[tuple[str, str], ...]()--project NAME=DIR (repeatable)Native: credential directories written to the target for the turn; NAME is set to where each landed.
homeVirtual | NoneNone--home JSONThe CLI's virtual home. Native: built on the target for the turn, with nothing merged. Supervised: built on the harness machine around the CLI the supervisor starts, its merges applied, and removed when the CLI exits; set for a harness elsewhere.
installsstr""--installs LINE""Native: the install line quoted when the CLI is missing on the target.
fenceFence | NoneNone--fence JSONNoneWhat the agent may reach, held on both machines. --fence takes Fence.dumps() output.
  • --log-level {debug,info,warning,error} defaults to $HUMANIZE_LOG, else warning, and logs to stderr.
  • The environment-variable defaults apply to the command line only. check(), connect() and drive() called in-process do not read $HUMANIZE_TOKEN or $HUMANIZE_SHADOW; a spawned turn is the rendered command line and does.
  • A turn spawned from an agent is AnchorConfig.command(argv, swaps=…, private=…, chdir=…): the provider's credential paths and kept session paths are added as redirects, the provider's variables as private, and the session's directory as chdir.

Variables the agent is given ​

VariableValueArrangements
HUMANIZEthe hmz.coganchor versionsupervised, native
HUMANIZE_TARGETthe target, as Target.describe() spells itsupervised, native
HUMANIZE_WORKSPACEthe workspace, as workspace names it (absolute)supervised, native
PWDthe mirror directory the agent starts insupervised

Validation ​

AnchorConfig.__post_init__ raises ValueError; hmz internal anchor reports the same message as a usage error and exits 2.

RefusedMessage
target or harness not a target spellingthe target parse message
native=True with a harness on another machinea native session has no harness to put anywhere: the CLI on the target is the one that runs, so --harness and --native ask for opposite things
a peer:// target with any harness but locala peer:// target is a meeting Humanize books for a harness it is placing; naming both is naming the same introduction twice
net not local or remoteunsupported net 'X'; expected local or remote (the option: argument --net: invalid choice: …)
a redirect that is not two absolute pathsunsupported redirect 'a=b'; expected two absolute paths
an empty hush, or one containing =unsupported hush 'A=B'; expected a variable name
a projection without a variable name and an absolute pathunsupported projection X='rel'; expected a variable name and an absolute path
a non-open fence with no scopes (drawn path by path)a fence drawn path by path cannot be held on another machine; draw it from its levels with Fence.of
a non-open fence with a harness on another machinea fence is drawn around this machine's paths, and cannot hold a harness on another machine
a fence with online=False and net="remote"a fence that cuts the network keeps the agent's own connections here; --net remote would send them past it

The command line also refuses no agent and no --check with no agent given; try `hmz internal anchor claude` (exit 2).

Refused only when a turn is started:

ConditionError
no agent namedValueError: no agent given (native), ValueError: no agent command given (supervised)
a harness on a tcp:// or peer:// machine, or same with such a targetValueError: nothing is started on the far side of tcp://h:1
chdir outside the workspaceValueError: <chdir> is not inside <workspace>
the agent not on this machine's PATH (supervised)FileNotFoundError: <name>: not found on PATH

Exit status of hmz internal anchor ​

StatusMeaning
the agent's ownthe agent ran; a signal-killed CLI under --native is 128 + signal
0--check succeeded
1ConnectionError, ProtocolError, OSError or ValueError while reaching the target or running the turn, printed as hmz: <message>
2refused settings (usage error)
127--native and the CLI is not installed on the target: hmz: <cli> is not installed on <target>[; install it there with `<installs>`]
130interrupted

--check prints, without running anything on the target:

text
target      ssh://build-box
hostname    build-box
python      3.12.3 (pid 41207)
export      /srv/project -> /srv/project
workspace   /srv/project (184 entries)

Defined in src/hmz/coganchor/anchor.py, src/hmz/coganchor/argv.py, src/hmz/cli/anchor.py.

Where the harness runs ​

It is decided at two levels: AnchorConfig.harness for one anchor, and the harness setting of the runtime a flow session works on, which the flow runtime turns into anchor settings per session.

AnchorConfig.harness ​

ValueHarness machineMirrorNotes
localthis machineshadow, else the workspace paththe supervised arrangement
samethe machine target nameskept, on that machineafar; nothing is introduced, the serving half is started beside the harness with a local: target
an ssh:// or docker:// spelling equal to target (scheme, host, port, path and options)that machinekept, on that machineas same
any other ssh:// or docker:// spellingthat machinekept, on that machineafar; a rendezvous introduces the harness to the serving half on the target
local:DIRthis machineas localnot afar

A turn with the harness elsewhere is spawned as hmz internal anchor on the harness machine (bootstrapped as a target is), with harness rewritten to local and target rewritten to local:<remote_path> (same machine) or peer://<ticket>@<broker> (third machine). This machine runs only the process carrying the three streams.

The CLI there runs in a virtual home as it would here, built on the harness machine from that machine's user's own configuration (AnchorConfig.home, --home JSON) by the hmz internal anchor supervising it, with each driver's merges applied (Qwen Code's effort, hook table and MCP server; a gateway's entry in Grok Build's config.toml and pi's models.json; agy's agents), named to the CLI by its home variable or, for agy, --app_data_dir right after the program's name, and removed when the CLI exits. The home is kept on the harness machine as the CLI's state paths are: no path in it and no program run from it goes to the target. The flow's skills it carries are put there first: one tar, unpacked under <cache>/skills/<16 hex digits of its sha256> (the target's cache), renamed into place whole, reused while a turn carries the same skills, and swept once no turn has carried them for 30 days. A failure to put them there fails the turn with ConnectionError: could not carry the flow's skills to <harness>: <stderr>.

Where shadow is unset, the mirror on the harness machine is <mirrors>/<name>, where <mirrors> is $HOME/.cache/humanize-mirrors over ssh, /tmp/humanize-mirrors in a container, or /tmp/humanize-mirrors-<UID> in a container whose target names user=UID[:GID], and <name> is the first 16 hex digits of sha256(workspace \0 remote_path \0 target). It is passed as HUMANIZE_SHADOW with force set, and kept between turns.

The driver still names the workspace by its own path: a CLI told where to work over its protocol (Codex's thread/start cwd, an ACP session/new cwd, and so on) is told workspace. On a harness reaching its work as a peer:// target (a third machine), the supervisor answers workspace and everything under it with the mirror, as another spelling of it, whatever the harness machine holds at that path; it is never created there. A --local-path or --local-exec under workspace is kept here by either name. Not for a mirror nested in the workspace or the other way round.

The harness setting of a runtime ​

A flow session has two settings that say where it runs:

SettingValues
Env locationlocal: the environment's machine is this machine (a local environment). standalone: any other machine (ssh, docker, swarm, apple-container, slurm).
Harness locationlocal: the CLI runs on this machine; with a standalone environment it works in a mirror here, supervised. env: the CLI installed on the environment's machine runs there natively, with no mirror. standalone: the CLI runs on a separate saved runtime, a harness runtime, in a mirror there.

Env location local always has harness location local, with no mirror. For a standalone environment, the harness location is what the harness field of the saved runtime the work is on says:

text
harness := env | local | ssh:<name> | docker:<name> | swarm:<name> | apple-container:<name> | slurm:<name>
SettingThe harnessRefused when
env (default)native on the environment's machinethe CLI is missing there (HarnessNotInstalled), the machine cannot hold the session's fence (HarnessSandboxed), the machine cannot be asked (HarnessUnrecoverable), or a PreToolUse or PermissionRequest hook is hung on the agent (UnsupportedOperation)
localsupervised here, anchored to the environment's machinenever, by the setting (the CLI must still be installed here)
ssh:<name>, docker:<name>, swarm:<name>, apple-container:<name>, slurm:<name>supervised on that saved runtime's machine, reaching the environment's machine through the anchorit cannot be opened or reached (EnvError), has no share left, its container limit among it (ResourceUnmet), or the role's Permission fences anything (HarnessSandboxed)
  • There is no fallback: the harness goes where the setting says or the session is refused, with an error of the refusal's own type, as <backend>[@<provider>]: <cli>'s harness cannot run where its harness setting (<setting>) says: <refusal>.
  • Work in a local environment always has its harness here. Work on a machine that is no saved runtime (ssh@[<host>], a host nobody saved; docker/…, swarm/…, apple-container/… or slurm/…, this machine's), and work on a runtime saved without a harness, has env.
  • The setting read is the one of the runtime the session's placement names (store.find(placement.backend, placement.provider)), the one actually opened. A runtime a harness is put on is opened as it is: its own setting and fallback list are never read.
  • A harness runtime is opened (hmz.runtime.flowing.harbors.Harbors) once per run, shared by every role, as an environment of role harness with no resources asked, in its saved workdir, else ~ over ssh, else an empty $TMPDIR/humanize-<uid>/harness (created by the run) for a docker daemon on this machine or this Mac's Apple containers; any other runtime saved without a workdir is refused. It is probed before the flow is called and closed with the run's environments. A container for it is started with --cap-add SYS_PTRACE: without it, docker's default seccomp profile refuses the pidfd_getfd the supervisor borrows each command's descriptors with, and a command whose output goes to a socket (opencode's, Claude Code's stdin) is run with that output lost.
  • The setting is checked when the runtime is made: one that is none of the above, or one naming the runtime itself, is a ValueError (<name>: '<setting>' is not where a harness runs: env, local or <ssh|docker|swarm|apple-container|slurm>:<runtime name>, <name>: its harness names itself; env is its own machine). A setting naming a runtime nobody saved is accepted, and refused when used.
  • A runtime saved by an earlier Humanize with an ordered affinity list is read as though its harness were the first entry (self read as env), or env where the list is empty; the next write of it drops affinity.

Settling it ​

The decision is made in HarnessDriver.open (hmz.runtime.flowing.harnesses) for every session a role opens, before the session's agent is built. With the session's machine M (from its environment), its fence, its runtime's setting, and the set H of hooks hung on the flow agent among PreToolUse and PermissionRequest (an AskUser hook is not among them: a question comes back down the CLI's own stream wherever the CLI runs):

  1. M is this machine: M unchanged.
  2. The setting is local: M unchanged, the harness supervised here.
  3. The setting is env and H is non-empty: refused. The CLI's own hook table names a program on this machine, so a CLI on another machine cannot be gated by it.
  4. The setting is env: the machine is asked, once per role and target, whether the CLI is there; then, for a session with a non-open fence, once per role, target and online value, whether the machine can hold the fence. Both answers yes: M with native=True and shadow=None. Otherwise refused.
  5. The setting names a runtime: that runtime is opened, and M gets harness set to its target and shadow=None. A runtime that is this machine is M unchanged, as local.
ProbeHowResult
CLI presentThe native road itself: AnchorConfig(native=True, shadow=None, fence=None).command(["/bin/sh", "-c", 'command -v -- "$1" >/dev/null 2>&1 || command -v -- "$2" >/dev/null 2>&1 || exit 69', "humanize", PROGRAM, basename(PROGRAM)]), in a new process session, stdin /dev/null, 300 s limit. PROGRAM is the CLI's name, or an added CLI's configured command.exit 0: present; exit 69: absent; other exit, spawn failure or timeout: cannot be asked
Fence holdablehmz.coganchor.check(anchor); the handshake's fence.fs (and fence.net where the fence cuts the network)both true: holdable

Answers are cached on the role's driver for the rest of the run; concurrent sessions wait for one answer. The probes are asked one at a time.

Runner.arun settles the harness of every agent on every machine (HarnessDriver.placeable), against every environment, the workspace included, with the role's declared permission, once the environments are probed and before the flow is called; a refusal there is Refused, which hmz exec prints as hmz exec: error: <message> with exit 2, and the answers are kept for the sessions. A session opened later is still refused as it opens, raised from its spawn. The refusals (<where> is <backend>[@<provider>] of the environment, e.g. ssh@gpu-box), each after the prefix above:

ErrorMessageWhen
HarnessNotInstalled<cli> is not installed on <where>: <install line> there, or set the harness of the runtime it is on to localenv, probe exit 69
HarnessUnrecoverable<where> could not be asked whether <cli> is there: <last stderr line | exit status N>env, probe failed
HarnessUnrecoverable<where> did not say within 300s whether <cli> is thereenv, probe timed out
HarnessUnrecoverable<where> could not be asked whether it can fence: <error>env, handshake failed
HarnessSandboxed<where> cannot fence the agent to its permission: it needs Landlock; grant the agent everything, or set the harness of the runtime it is on to localenv, the machine cannot hold the fence
UnsupportedOperationa <kind>[ and a <kind>] hook cannot gate the tools of a CLI on another machine; set the harness of the runtime it is on to local, <kind> being permission_request or pre_tool_useenv, a PreToolUse or PermissionRequest hook hung
HarnessNotInstalled<cli> is not installed here: <install line>the harness is here and the CLI is not on this machine
HarnessSandboxed<role>=<cli>[@<account>]/<model>:<effort>: <AgentClass>: a fence cannot hold a harness that runs on another machinea harness runtime, and the role's Permission is anything but every scope ALL
EnvUnavailable, EnvConnectionError, ResourceUnmetas the runtime's own environment would be refuseda harness runtime that cannot be opened, reached, or given a container

Recording ​

Where each session's harness went is written with it: the epic's session record carries harness (local, env, or the <backend>:<name> of the harness runtime its runtime's setting named; "" for work on this machine; self, written by an earlier Humanize, is read as env), and so does the daemon's opened record. See Tracing.

Limitations ​

  • A fence cannot follow a harness elsewhere. A fence names this machine's paths. On a harness runtime, and under an AnchorConfig.harness elsewhere, any non-open fence is refused; a flow role must grant local, user, system and online all ALL. The default Permission (local=ALL, user=READ, system=READ, online=ALL) is refused.
  • Accounts do not follow a harness elsewhere. See Where the account lives.
  • Gating hooks need the harness here. A role with a PreToolUse or PermissionRequest hook hung is refused under the setting env; set the runtime's harness to local. An AskUser hook does not need it here. An anchored turn gets no hook table, so there PreToolUse is read off the CLI's stream and cannot stop a tool (Agents › Refusing a tool).
  • Callbacks do not cross. A native turn on a non-local target offered the flow's tool callbacks or command lines is refused: <cli>: a turn driven on <target> cannot be offered the flow's own callbacks or command lines -- the bridge that carries them is a program on this machine, and the CLI that would start it is on that one (command lines alone for omp). omp's callbacks are the exception (AgentBase.pipes_tools): it asks for each call over the RPC pipe it is driven through, so they reach it wherever it runs.
  • Sessions whose harness is on the env or standalone stay where that CLI keeps them, on the other machine; a run copies what they log into its epic as they go and whole as they close (Providers › Sessions on another machine). So only a session whose harness is local, whose CLI keeps its conversation here, can be kept in a flow's state and carried on: writing one whose harness is env or standalone is refused with StateNotSerializable, and a kept one read back onto such a turn with UnsupportedOperation.
  • Forks stay on one machine. A fork into a session on another machine is refused with UnsupportedOperation: <cli> cannot fork a session onto another machine.

Defined in src/hmz/runtime/flowing/harnesses.py (HarnessDriver._harnessed, _put, _native_on, _has_cli, _fenceable), src/hmz/runtime/flowing/harbors.py (Harbors), src/hmz/runtime/runner.py, src/hmz/runtime/epic.py (harnessed, harbor), specs/features/placements.md §4.

Supervised arrangement ​

connect(command, config) with harness here and native=False. The agent process runs on this machine, unchanged, under a supervisor. Everything it does to files, programs and the network is answered from the target.

Interception ​

  • A seccomp filter traps exactly the syscalls that name a path, start a program or open a connection; every other syscall (read, write, mmap, futex, getdents64, …) runs natively.
  • Trapped on x86-64 (55): access, chdir, chmod, chown, connect, creat, execve, execveat, faccessat, faccessat2, fanotify_mark, fchmodat, fchmodat2, fchownat, futimesat, getxattr, inotify_add_watch, lchown, lgetxattr, link, linkat, listxattr, llistxattr, lremovexattr, lsetxattr, lstat, mkdir, mkdirat, mknod, mknodat, name_to_handle_at, newfstatat, open, open_tree, openat, openat2, readlink, readlinkat, removexattr, rename, renameat, renameat2, rmdir, setxattr, stat, statfs, statx, symlink, symlinkat, truncate, unlink, unlinkat, utime, utimensat, utimes. On aarch64 the 19 legacy forms do not exist and 36 are trapped.
  • chmod, fchmodat, fchmodat2 (except on a link it was told not to follow, which the kernel refuses), the utime* family (a descriptor's own file too, as futimens names it) and every mutation of the tree are replayed on the target first. A plain file mknod makes is sent like any other; a pipe or device it makes stays here. chown, lchown, fchownat and the *setxattr/*removexattr calls act on the mirror only, once it holds the file's contents; the lookups (stat*, access*, statfs, *getxattr, *listxattr, name_to_handle_at, open_tree, inotify_add_watch, fanotify_mark) run against the mirror once it holds the path.
  • Not trapped: fstatfs and every other call taking a descriptor rather than a path; calls that need a capability an agent is not given (mount, umount2, pivot_root, chroot, swapon, swapoff, acct, quotactl, move_mount, uselib); the *xattrat and file_getattr/file_setattr calls; and a local socket's address in bind or connect.
  • A path is resolved as text: . and .. are collapsed, doubled / removed, and /proc/self/fd/<n>, /proc/<pid>/fd/<n>, /proc/self/cwd, /proc/self/root and /proc/self/exe are followed to what they name. An ordinary symlink in the middle of a path is not walked. A link that cannot be read back fails the call.
  • A path the mirror is also reached by is rewritten onto the mirror's own path before the call runs, for every trapped call that names a path: /private/tmp/… for a mirror under /tmp on a Mac target, any case on a target that ignores case, and workspace/… for a peer:// target (a harness elsewhere). A mirror whose path runs through a symlink here (~/.cache linked elsewhere, say) is kept at the path the link resolves to, which is where the agent starts and what the kernel reports as its working directory, and the path it was given is one more such name, in calls and in a command's arguments alike. A program run here (the agent's own, or a --local-exec) named that way is run from the mirror; one run on the target is named to it by its own path.
  • Only x86-64 and aarch64 Linux are supported. Any other platform fails at start-up with RuntimeError, naming where the supervisor can run instead.

The mirror ​

  • The mirror is prepared before the target is reached. Its guard, recorded outside the directory in ~/.cache/humanize/shadows/<sha256(path)[:16]>.json (or under $HUMANIZE_SHADOWS), written whole beside it and moved into place, so that a turn starting while another records the mirror reads the record and not half of it:

    ConditionWithout force
    the directory was last used for another targetFileExistsError: <path> mirrors <old>, not <new>. Humanize replaces this directory with the new target's contents and would delete everything only the old one has. Use a different directory, or pass --force.
    the directory holds files and was never a mirrorFileExistsError: <path> already contains files and is not a Humanize mirror. Humanize replaces this directory with the target's contents and would delete them. Use an empty directory, or pass --force.
    the path exists and is not a directoryUnmirrored: … shadow root is not a directory (always)
    the path cannot be created hereUnmirrored: … <reason>, e.g. Permission denied: /home/me (always)

    Unmirrored (an OSError) is printed by hmz internal anchor as hmz: cannot keep the local copy of the work at <path>: <reason>, with exit 1. A turn that fails with it is classified unmirrored (Agents › Failures), not as a refused credential. The target's identity for this check is its spelling without ssh options.

  • Structure is materialised on first access: real directories and symlinks, and sparse placeholder files carrying the target's size, mode and mtime. stat, getdents64, read, write, mmap and lseek then run locally.

  • Content is pulled the first time a file is opened for reading.

  • A file the agent modifies is pushed in full before any command runs on the target, and again when the session ends.

  • Creating, removing, renaming, linking and mode changes are replayed on the target first; the target's error is the agent's error.

  • Completing a remote command invalidates every cached directory listing.

  • The mirror is authoritative: what the target does not have is deleted from it.

  • A file written since it was last pushed is pushed before what the target says of it is taken: it is never deleted, made a placeholder or fetched over first.

  • Turns working in one mirror at once, as the sessions of one run on one environment do, share what they know of it. Each turn appends every change to that knowledge (a file fetched, made a placeholder, written, pushed, renamed or removed) to <sha256(path)[:16]>.journal beside the mirror's guard, reads the others' changes from it, and holds its lock while it changes the mirror, network calls included. A file one turn wrote is pushed by whichever turn next flushes, not deleted as one the target lacks, and one it pushed or fetched is not made a placeholder again under it. A turn starting with no other in the mirror (none holding <sha256(path)[:16]>.live) begins the journal again. Where the two cannot be opened or locked, the turn works alone and logs <mirror> cannot be shared with other turns working in it, which may then lose what this one writes: <reason>. Each turn still reads a directory again only after its own commands.

What stays on this machine ​

A path is answered from the target unless it is one of these:

Kept hereSource
The agent program as resolved on PATH, and its real pathstatepaths.resolve
For a script, its #! interpreter, and for #!/usr/bin/env NAME every PATH candidate for NAMEstatepaths._interpreter
Codex's native binary and codex-code-mode-hoststatepaths._codex_runtime_programs
Every executable beside the program inside its own node_modules package (e.g. mimo's bin/.mimocode)statepaths._beside
The CLI's state paths (table below), as files and as programsstatepaths.PROFILES
~/.hmz, ~/.humanize, ~/.cache/humanize, ~/.config/humanizestatepaths.COMMON_STATE_PATHS
local_paths, local_execs, and what every redirects entry is answered withAnchorConfig
The program that bridges the flow's tool callbacks, when offered (not for omp, which needs no bridge)added to local_execs
The agent's shelf of the flow's command lines, once any is shelved, and the Python they runadded to local_execs; the shelf to local_paths too
The virtual home the anchor builds around the CLI (home), as files and as programsAnchorConfig.home
CLIState paths kept here
agy~/.gemini
claude~/.claude, ~/.claude.json, ~/.local/share/claude, ~/.cache/claude-cli-nodejs
codex~/.codex
cursor-agent~/.cursor, ~/.local/share/cursor-agent
dsh~/.dsh (also for dsh-jsonrpc-agent-*)
grok~/.grok
kimi~/.kimi-code, ~/.kimi
mcode~/.minimax, ~/.mavis
mimo~/.mimocode, ~/.config/mimocode, ~/.local/share/mimocode, ~/.cache/mimocode, ~/.local/state/mimocode
omp~/.omp
opencode~/.opencode, ~/.config/opencode, ~/.local/share/opencode, ~/.cache/opencode, ~/.local/state/opencode
pi~/.pi
qwen~/.qwen
any other programnone beyond the common paths; state inside the workspace must be named with --local-path

A path kept here carves a hole in the mirror only where it lies inside the mirror. A mirror kept inside one of them, as a container's is under ~/.hmz/envs/mirrors, is the target's workspace all the same, and what is beside it in that directory stays here.

Commands, network and signals ​

  • Every program the agent starts that is not kept here runs on the target, in the target's copy of the working directory, work helpers such as a bundled ripgrep included. It gets the agent's descriptors, output and exit status; its parent is released as soon as it starts, so commands run concurrently.
  • A command that inherited the pipe the agent's driver speaks on gets an empty stdin on the target. An agent that is a shell hands its stdin on as a shell does.
  • Variables in private are removed from the environment commands run with on the target.
  • net="local": the agent's own TCP connections leave from this machine. net="remote": they are tunnelled and opened from the target, except destinations in net_allow.
  • SIGINT, SIGTERM and the other common signals are forwarded both ways. A repeated signal and the rarer signals are not reproduced.
  • A command that cannot be started, or that the supervisor loses track of, fails. Nothing it started is left running when the session ends.

Defined in src/hmz/coganchor/supervisor.py, src/hmz/coganchor/linux/syscalls.py, src/hmz/coganchor/shadow.py, src/hmz/coganchor/statepaths.py, src/hmz/coganchor/policy.py.

Native arrangement ​

drive(command, config), reached through connect when native=True. Nothing is mirrored, traced or supervised. The sequence per turn:

  1. Handshake with the serving half.
  2. Find the CLI on the target: command -v -- NAME from the workspace, then by the last component of NAME. Not found: NotInstalled (exit 127 on the command line).
  3. If a fence is set and not open, require the handshake's fence capability; otherwise PermissionError: the target cannot fence the CLI: it needs Landlock[ ABI 4 and seccomp].
  4. If projects is non-empty, make a private directory on the target (umask 077, mktemp -d, chmod 700) and write each credential file into <dir>/<variable lowercased>/... through a command's stdin, mode 0600. The variable is set to that directory. A failure raises OSError: could not put <VAR> on the target: … and the turn does not run. A file holding a refresh token (a sign-in that refreshes itself) is first locked here, exclusively, for the whole turn (flock on its directory); one any other turn is using refuses this one with OSError (EBUSY) before anything reaches the target. A supervised turn holds the same lock shared, for the refresh-token files its redirects answer.
  5. If home is set, build it in the turn's private directory (made for it where projects made none), with the flow's skills in its skills directory (below).
  6. Start the CLI as env [-u HUSHED]... NAME=VALUE... PROGRAM ARGS... in chdir (default the workspace), with this process's environment layered on the target's.
  7. Relay stdin, stdout and stderr byte for byte; forward the first SIGINT/SIGTERM to the CLI and restore the previous handler, so a second one reaches this process.
  8. Read each refresh-token file back from the target; one the CLI changed, that is still a whole sign-in, is written atomically over the file it was copied from here, unless that file changed meanwhile (then it is left, with a warning that the account may need signing in again). Then remove the private directory, whatever happened.
What does not follow the CLIHow it crosses
The account's variablesSent as the turn's environment. Every variable the provider hushes is removed on the target with env -u, since the target's own shell profile would otherwise supply it.
The account's credential files--project NAME=DIR, written through stdin, never argv.
The flow's skillsWritten into the skills directory of the CLI's virtual home (--home), never into the workspace. A CLI with no virtual home on the target gets none.
The CLI's virtual home--home JSON. Built in the turn's private directory by a POSIX shell script on the target, around the target user's own home ($VARIABLE, else $HOME/<default>), never over a credential --project put there; the home variable is set to it, and Profile.secures to the projected directory under an account and to the target user's value otherwise. Under a projected account, the session paths stay in it rather than being linked, and are brought back by --bring. Removed with the private directory.

Credential files cross only where a variable of the backend's own moves the directory they are in:

Credential rootCrosses asBackends
the backend's homeits home variableclaude (CLAUDE_CONFIG_DIR), codex (CODEX_HOME), cursor-agent (CURSOR_CONFIG_DIR), grok (GROK_HOME), kimi (KIMI_CODE_HOME), mcode (MINIMAX_DATA_DIR), pi (PI_CODING_AGENT_DIR), qwen (QWEN_HOME)
$XDG_CONFIG_HOMEXDG_CONFIG_HOMEfiles written config/…, e.g. Claude's anthropic/
the backend's home, no own variabledoes not crossagy, opencode, mimo
the user's home (~/…)does not crosse.g. Claude's ~/.claude.json, Cursor's ~/.cursor/auth.json

An account some of whose credential files cannot cross, with nothing else to carry it, is refused rather than run as whatever account the target is signed into.

  • There is no net: the CLI's connections are the target's. A gateway at 127.0.0.1 is the target's loopback.
  • The CLI keeps its sessions on the target; a run copies what they log into its epic (Providers › Sessions on another machine).

Defined in src/hmz/coganchor/anchor.py (drive, _installed, _projected, _homed, _swept), src/hmz/coganchor/agents/base.py (_reaching, _projecting).

Afar arrangement and the rendezvous ​

With the harness on the target (same), nothing is introduced: the harness machine runs hmz internal anchor --target local:<remote_path> against itself. With the harness on a third machine, Humanize books a meeting:

  1. This process starts (once, for its lifetime) a broker listening on 0.0.0.0, port $HUMANIZE_RENDEZVOUS_PORT (default 0, any free port).
  2. It mints a ticket (secrets.token_hex(16), 128 bits) and starts, on the target, hmz internal anchor serve --peer TICKET@HOST:PORT --export …, where HOST is broker, else $HUMANIZE_RENDEZVOUS, else this machine's outward-facing IPv4 address (the source address of a route to 192.0.2.1; 127.0.0.1 if there is none).
  3. It spawns hmz internal anchor --target peer://TICKET@HOST:PORT on the harness machine.
  4. Both halves dial the broker with the ticket and their role (anchor or serve), and offer the address they dialled from plus the IPv4 addresses their hostname resolves to -- left out where resolving it takes longer than 1 s, as an unanswered macOS .local name does.
PhaseBehaviourTime limit
Pairinga half waits at the broker for the other120 s
Discoveryeach half is told the address its connection arrived from, and the other half's addresses30 s per broker answer
Punchingboth halves connect to each other's candidates from the port they dialled the broker from, and listen on it; each punched socket must present HMZ1 and a digest of the ticket; the anchor half says GO on the socket it keeps and the serving half answers OK4 s (--punching on a standalone broker)
Greetinga punched socket that does not prove itself is dropped3 s
Relayif punching fails, the two connections already held to the broker are spliced, 64 KiB at a timenone
  • The broker remembers pairs of external addresses it failed to introduce (up to 1024 pairs). After 3 failures a pair is relayed without punching, except when its failure count is a multiple of 16.
  • A broker holds at most 65,536 meetings at once; beyond that a half is refused with this rendezvous is already holding 65536 meetings.
  • Addresses are IPv4 only; a machine reachable only over IPv6 is relayed.
  • Neither half is told whether it was relayed.
  • hmz internal anchor rendezvous [--listen [HOST:]PORT] [--punching SECONDS] runs a broker on its own (default 0.0.0.0:0) and prints hmz internal anchor rendezvous listening HOST PORT on stderr.

Defined in src/hmz/coganchor/elsewhere.py (afar, _carried), src/hmz/coganchor/rendezvous.py.

A fence on both machines ​

AnchorConfig.fence is held by the anchor on each machine around that machine's own paths, never by a wrapper around the anchor. The fence must have scopes (built with Fence.of); what crosses is its levels, not its paths.

ArrangementThis machineTarget
SupervisedThe agent process is walled in by Landlock before it runs, with the mirror granted at the workdir's level. Where online=False, a proxy served by the anchor process is its only way out, passing the fence's hosts; binds are limited to a kernel-chosen port and the fence's listen ports, on loopback. The supervisor, its link and the mirror are outside the wall.Every command the agent runs is sent with {local, user, system, online, tmp}, tmp being the agent's scratch directory here. The serving half draws the fence again (abroad.drawn) around its exported directories, its own $HOME and its own minimum, and runs the command under its own hmz internal fence with its scratch at that same path: made 0700 there if missing and left afterwards, used where it is a directory of the serving user's own (not a link), and otherwise replaced by one made for the one command. A command that names the path, as Claude Code's shell commands name the file they write their working directory to, finds it. Where online=False, the command reaches no host.
NativenothingThe CLI is run under hmz internal fence on the target with the levels, the hosts its model is at, the state it writes under its home (sent as ~/…), the turn's credential directory, read access to its own install tree, and its listen ports.
Afarrefusedrefused
  • A CLI's own sandbox is not used under an anchor: it would wrap a command here that runs on the target. Codex is sent sandboxPolicy: {"type": "externalSandbox", "networkAccess": "enabled" | "restricted"} (by the fence's online) where the fence writes nothing the rung forbids: nothing at all for read-only, nothing of the home or system for workspace-write; Cursor's workspace-write runs without cursorsandbox where the fence writes nothing of the home or the system.
  • Whatever a driver grants its CLI past the wall (a port it serves on, a sign-in outside its home, generated settings) is granted on both machines.
  • The target reports what it can hold at the handshake: fence.fs is Landlock ABI >= 1; fence.net is Landlock ABI >= 4 (Linux 6.7), a loadable seccomp user-notification filter, and pidfd_getfd permitted with Yama ptrace_scope < 2. A macOS target reports both as whether sandbox-exec can apply a profile there. A target of a build before fences reports nothing and is treated as unable.
WhereErrorMessage
agent constructionUnfenced<Agent>: a fence drawn path by path cannot be held on another machine
agent constructionUnfenced<Agent>: a fence cannot hold a harness that runs on another machine
agent constructionUnfenced<Agent>: an agent whose own connections are sent to the target cannot have its network cut here
agent construction, supervisedUnfenced<Agent> cannot be held to its permission: it is supervised on this machine, which has no Landlock[ ABI 4 and seccomp to cut the network]
first turn, supervisedUnfenced<id>: <target> cannot fence the commands the agent runs there: it needs Seatbelt on a Mac, or Landlock (Linux 5.13 or later, not refused by a container's seccomp profile)[, at ABI 4 (Linux 6.7) with seccomp, to cut the network]; grant the agent everything, or run it where it can be fenced
anchor start, supervisedPermissionErrorthis machine cannot fence the agent: it needs Landlock[ ABI 4 and seccomp]
native turnPermissionErrorthe target cannot fence the CLI: it needs Landlock[ ABI 4 and seccomp]
a fenced command on the targetPermissionErrorthis machine cannot fence a command: it needs Landlock[ ABI 4 and seccomp]; on a macOS target, … it needs Seatbelt, which a process already inside a sandbox cannot apply

Under a flow, Unfenced becomes HarnessSandboxed. Docker's default seccomp profile permits Landlock but not the seccomp listener or pidfd_getfd, so a container under it holds a fence with online=True and refuses one with online=False; run such a container with a profile that allows them, or --security-opt seccomp=unconfined.

Defined in src/hmz/coganchor/anchor.py (_Walls, _fenced), src/hmz/coganchor/fence/abroad.py, src/hmz/coganchor/serve/fencing.py, src/hmz/coganchor/agents/base.py (_abroad, _reached, _walled).

Serving a target ​

text
hmz internal anchor serve --export VIRTUAL[:REAL] [--export ...]
                          (--stdio | --listen [HOST:]PORT | --peer TICKET@HOST:PORT)
                          [--token TOKEN] [--log-level LEVEL]
ModeServesStarted by
--stdioone session on stdin/stdout; fds 0 and 1 are pointed at /dev/null for the rest of the processHumanize, for local, ssh://, docker://, apple-container:// and slurm:// targets
--listen [HOST:]PORTevery TCP connection, one thread each, until interrupted; prints hmz internal anchor serve listening HOST PORT on stderra person, for tcp:// targets; or the batch step of a Slurm job whose runtime says via: tcp, on 0.0.0.0:0 with a random secret set in its script
--peer TICKET@HOST:PORTone session met at a brokerHumanize, for a harness on a third machine
  • --export VIRTUAL[:REAL] is required and repeatable. VIRTUAL must be absolute; REAL defaults to VIRTUAL, with ~ expanded. A path outside every export is refused with EACCES, path is outside every export. Virtual prefixes inside command arguments are rewritten to real paths. Malformed: hmz: malformed export '<spec>'; expected VIRTUAL[:REAL], exit 2.
  • A bare --listen PORT listens on 127.0.0.1. Any host other than 127.0.0.1, ::1 or localhost without --token is refused: hmz: cannot listen on a non-loopback address without --token, exit 2. A malformed address: hmz: malformed listen address '<spec>'; expected [HOST:]PORT, exit 2. A bind failure exits 1.
  • --token defaults to $HUMANIZE_TOKEN. With a token, a session whose handshake does not present it is refused with invalid token.
  • For ssh://, docker:// and slurm:// targets the token is unused; ssh, the docker socket and Slurm authenticate the session.

A listening serving half is a shell on its machine

An export bounds which paths a request may name; it does not confine what the commands it runs can do. Listen on a non-loopback address only with a secret --token.

Defined in src/hmz/cli/anchor.py, src/hmz/coganchor/serve/, specs/features/placements.md §10.

Where the account lives ​

ArrangementCredentials, CLI state, model-provider connectionProvider variablesProvider credential files
Supervisedthis machineset on the agent here; listed in private, so commands on the target do not get themanswered here as redirects; never cross
Afarthe harness machinenot sent: the harness process's environment is the harness machine'sredirected to this machine's paths, which the harness machine does not have
Nativethe target, for each turnsent as the turn's environment; hushed variables removed with env -uwritten to a private directory on the target and removed after the turn, where they can cross; a refresh-token file goes to one turn at a time and is written back refreshed

Python API ​

python
from hmz.coganchor import AnchorConfig, check, connect, drive
from hmz.coganchor.anchor import NotInstalled
CallReturnsRaises
check(config=None) -> dictthe handshake reply (version, hostname, platform, python, pid, exports, fence) plus target, workspace and entries (entries in the workspace). Runs nothing on the target.ValueError (target), OSError (unreachable, no workspace)
connect(command, config=None) -> intthe agent's exit status, after everything it wrote has been pushed. Calls drive when native; spawns the afar line when the harness is elsewhere.ValueError, FileNotFoundError, OSError
drive(command, config=None) -> intthe CLI's exit status, or 128 + signalValueError (no agent given, target), NotInstalled (a FileNotFoundError), OSError, PermissionError (fence)
AnchorConfig.command(argv, *, swaps=(), private=(), chdir="") -> list[str]the argv that runs argv under this anchor in its own processValueError, ConnectionError
AnchorConfig.capabilities -> frozenset[str]{"anchor:native-cli"}, {"anchor:supervised"} or {"anchor:supervised", "anchor:afar"}
AnchorConfig.mount() -> (Target, str, str)the parsed target, the workspace's absolute path here, and the export spellingValueError

config=None is AnchorConfig(): this directory on a local target.

Requirements ​

MachineRequirement
The harness machine, supervised or afarLinux on x86-64 or aarch64, Python >= 3.12, ptrace and seccomp permitted.
This machine, when the harness is elsewhere or the arrangement is nativenone of the above; only what runs the three streams
A targeta POSIX /bin/sh and Python >= 3.12; no root, compiler, kernel module or installed package
A target of a fenced sessionLinux with Landlock (ABI >= 1); ABI >= 4 (Linux 6.7), seccomp user notification and pidfd_getfd where the network is cut. Or macOS, not itself inside a sandbox (Seatbelt)
A Slurm targetthe job's node sees the workspace at the same path as this machine (a shared filesystem); srun here; with via: tcp, this machine able to reach the node's ports
A native targetthe CLI installed and on the login's PATH
A harness machine elsewherean ssh:// or docker:// machine meeting the first row, with the CLI installed
A third-machine harnessboth machines able to reach this machine's broker port over IPv4

Failure behaviour ​

  • Serving is not a sandbox. Exports bound the paths a request may name, not what commands do; a symlink out of an export is followed. Only a fence confines commands.
  • Mirrored directories carry this machine's permissions and the time the mirror made them.
  • Only file contents are pushed. A mode change through an open descriptor is not replayed; ownership, device nodes and extended attributes never leave the mirror.
  • An unanswered request is abandoned here, not there. It may still take effect on the target after the agent was told it failed.
  • Losing the connection does not stop the agent. Work that needs the target fails, mirrored files still read, and the agent exits with its own status.
  • The mirror is authoritative. What the target lacks is deleted from the mirror, including a file written here and never pushed; the deletion is logged.
  • A command's output may not reach the agent in a container. The supervisor borrows each command's descriptors with pidfd_getfd, which docker's default seccomp profile refuses without CAP_SYS_PTRACE. A pipe or tty is opened again through /proc instead; a socket cannot be, and is logged as could not borrow fd <n> from pid <pid>. Humanize starts the container a harness setting puts a harness in with --cap-add SYS_PTRACE; one started otherwise needs it.
  • A path is read when its syscall stops. A descriptor replaced by another thread in between resolves as it was.
  • Native setup commands are bounded. Each short command a native turn runs to set itself up has 120 s; past that it is killed and the turn fails with TimeoutError: the target did not answer '<cmd>' in 120s.

Limits ​

  • Files cross whole, in both directions.
  • One writer: nothing else may edit the target's workspace during a session.
  • sudo below a supervised agent does not work here; commands run on the target, where it is unaffected.
  • Renaming or linking between the workspace and a path kept here fails.
  • A 32-bit process below the agent is not intercepted, and runs against the mirror with nothing replayed.
  • Host names are resolved here and dialled from the target; split-horizon DNS can disagree.
  • A relayed session pays this machine's bandwidth and latency for every byte.

Security properties ​

PropertyHolds
A provider's credential files leave this machineonly under native, into a 0700 directory on the target, removed after each turn
A sign-in that refreshes itself is out in two places at oncenever, where its directory can be locked: a native turn holds it alone, and its refreshed copy is written back before it is removed
This machine's own sign-in leaves this machinenever: without an @account, a CLI elsewhere uses that machine's sign-in
A provider's variables reach commands on the targetnever under supervised (private); under native, the CLI's environment is the turn's
A credential appears in an argvnever; projected files cross on stdin
A listening serving half without a tokenloopback only
A Slurm job's listening serving half (via: tcp)token-authenticated, the token set in its batch script, never on a command line on the node; the traffic is not encrypted, so it stays within the cluster's network
A broker portopen on every interface for the life of the process that booked a third-machine harness; pairing requires a 128-bit ticket known to exactly two machines
A mirror overwrites a directory it did not createnever without force
A flow session's CLI starts a program its project names on a machine other than the work'snever: refused when the session opens (a project's own programs)

Environment variables ​

Every variable Humanize reads is listed in Environment variables.

VariableRead byEffect
HUMANIZE_TARGEThmz internal anchordefault --target
HUMANIZE_HARNESShmz internal anchordefault --harness
HUMANIZE_SHADOWhmz internal anchordefault --shadow; set by Humanize for a harness elsewhere
HUMANIZE_TOKENhmz internal anchor, … servedefault --token; passed to a spawned serving half
HUMANIZE_LOGhmz internal anchor, … serve, … rendezvousdefault --log-level (warning; info for rendezvous); a value that is not a level is ignored with a warning
HUMANIZE_RENDEZVOUShmz internal anchor, the in-process brokerdefault --broker; the address advertised to both halves
HUMANIZE_RENDEZVOUS_PORTthe in-process brokerthe port it listens on; 0 or unset for any
HUMANIZE_SSH_REUSEevery ssh Humanize runsoff, 0, no, false (any case, trimmed) or empty disables connection sharing
HUMANIZE_SHADOWSthe mirror guard and journalwhere mirror records and journals are kept, instead of ~/.cache/humanize/shadows
XDG_RUNTIME_DIRevery ssh Humanize runswhere humanize-ssh-<uid>/ control sockets are made
HUMANIZE, HUMANIZE_TARGET, HUMANIZE_WORKSPACEset for the agentsee Variables the agent is given

Released under the Apache-2.0 licence.