Skip to main content

agmx

PyPI Python License

Run AgentMatrix work on your own machine, under your own coding-agent subscription.

AgentMatrix is a coordination service for coding agents: it plans work, holds the review gates, and decides what lands. agmx is the other half — a small client that picks up the work the server hands out and executes it locally, inside a throwaway git worktree of a repository you already have checked out.

The server dispatches a project_id; your machine decides which directory that is. When the runner enrolls it reports, per project, the checkout's path and its remote URL — with any username, password or token stripped — so the server can confirm the checkout belongs to that project. A project the server rejects is printed and dropped for the session; if every project is rejected, agmx runner start exits instead of polling for work it can never take.

Why it is separate

The control plane needs Postgres, Redis and a pile of orchestration code. A runner does not. agmx depends on httpx and nothing else, so installing it never asks you for a database URL or a shared secret.

$ pip list
agmx  anyio  certifi  h11  httpcore  httpx  idna  typing_extensions

Install

pipx install agmx

Requires Python 3.10+ and git on PATH.

Quickstart

# 1. Authenticate.  Opens your browser at your team's sign-in page.
agmx login

# 2. Tell the runner where your checkouts live, then let it work.
agmx runner start --project agenticmatix=~/code/AgentMatrix

agmx login asks the server where your team signs in (its OAuth metadata) and logs you in there with your browser: a loopback redirect to 127.0.0.1, PKCE and a state check. On a machine you reach over SSH, agmx login --device shows a code to enter on any device with a browser. agmx login --token takes a personal access token instead (create one with manage_user create_token from a logged-in coordinator CLI); it is read from the terminal, never from the command line. The session refreshes itself; refresh tokens rotate, so each refresh is saved at once and taken under a file lock, which lets two agmx processes share one login. A server without single sign-on still gets --email and a password.

If the server refuses to enroll you (not_a_member, email_not_verified, user_disabled, subject_mismatch, invite_expired), the runner prints the server's answer and exits: those are for your team's admin, and retrying does not change them.

--project may be repeated. A project_id you have not registered is reported back as failed rather than guessed at, so a run can never be executed against the wrong tree.

Point at a different control plane with --server, or AGMX_SERVER.

The first runner start enrolls the machine with your login and receives the machine's own runner token, stored next to your login token. From then on every call the runner makes — including enrolling again when it restarts — uses that token, not your login session. If the server stops accepting it (the runner was revoked, or you logged in elsewhere and re-enrolled), the runner exits with one line telling you to agmx login and start again.

By default the server gives this machine only your own runs. --serve-team (remembered; --no-serve-team to stop) lets it take your teammates' runs too — they then run on the CLI subscriptions logged in on this machine.

On start, the runner removes worktrees that a runner which died mid-run left behind in your registered checkouts — only its own (agmx-*/worktree in its data directory, or in the temp directory where older versions put them, whose owning process is gone), never yours or those of a runner that is still running.

Sandbox by default

The server builds every agent command to run inside that CLI's own sandbox. Full access — the agent can do anything your OS user can — is your choice, made on this machine:

agmx runner start --full-access ...     # remembered in ~/.agmx/settings.json
agmx runner start --no-full-access ...  # back to the default

With it off, the runner does not take the server's word for it: a command carrying a flag that switches the sandbox or permission checks off (--dangerously-skip-permissions, --allow-dangerously-skip-permissions, --permission-mode bypassPermissions, --dangerously-bypass-approvals-and-sandbox, codex --sandbox danger-full-access, qwen --yolo without --sandbox) is not run; the run is reported failed before the agent starts.

At start it also checks, for each agent CLI on your PATH, that its sandbox really works here, with a real probe rather than a guess, and prints exactly what to install or configure when it does not: bwrap and socat for claude on Linux, plus the kernel setting that Ubuntu 24.04+ uses to block the nested user namespace claude's shell sandbox needs; a running docker or podman for qwen. agy is not treated as sandboxed: its sandbox has not been measured anywhere it starts and does not keep it from reading ~/.agmx, where the other runs' tokens are, so agy runs are refused unless you choose --full-access. Runs for a CLI whose sandbox cannot work are refused with that fix, instead of quietly running unsandboxed; the other CLIs keep working. The runner never edits your CLI settings files.

How a run executes

  1. The runner leases one run: {run_id, task_id, project_id, command, timeout_seconds, kind, base_ref, needs}. No path, no repository URL.

  2. It resolves project_id against the checkouts you registered.

  3. The commits the run needs must be here. Every SHA in needs (and base_ref) is checked; any that is missing is fetched from the agmx-run/* branches on your remote — that is where another machine's run left it. Still missing means the run is reported as failed, naming the SHA. It is never run against some other commit instead.

  4. It creates a git worktree at base_ref when the server gave one (a fix round starts from the previous attempt; a review sits at the head it reviews), otherwise at the freshly fetched tip of the shared branch on the remote — never your local checkout, even when it is on a detached HEAD, so every machine starts from the same base. The shared branch is your current branch's upstream, or the remote's default branch when your checkout is detached — or the branch the server names for the project, when it names one. A repository with no remote uses local HEAD. .agmx/ is created inside it for the files an agent hands back to the server.

  5. The command runs there through sh -c, with AGMX_WORKTREE set to the worktree's absolute path — some agent CLIs start their shell tool somewhere else and would otherwise go looking for the repository across your disk. The run's MCP server goes into ~/.agmx/run-secrets/<run_id>/ (0700, files 0600) — inside ~/.agmx, which the sandboxes the server builds for claude and codex refuse to read, so a sandboxed agent cannot read the token of another run on the same machine — in the form each CLI reads: AGMX_MCP_CONFIG (the lease's config verbatim, for --mcp-config of claude and qwen) and AGMX_AGY_DIR (agy, --add-dir: it holds an agmx plugin). qwen's sandbox is a container that mounts only the worktree, ~/.qwen and TMPDIR, so the runner mounts that one run's directory into it, read-only, through SANDBOX_MOUNTS. The run's token is exported as AGMX_MCP_TOKEN, and the command gets its own private TMPDIR. All of it is deleted when the run ends — on timeout, cancellation or interruption too — and the token is masked in any output streamed back. An older server that sends no MCP still gets these, with no servers in them.

    The command does not inherit the runner's environment. It gets an allowlist: PATH, HOME, USER, LOGNAME, SHELL, LANG, LC_*, TERM, TMPDIR, TZ, XDG_*, the proxy variables (http(s)_proxy, no_proxy, in both cases), CA bundles (NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, SSL_CERT_DIR), and the agent CLIs' own configuration: ANTHROPIC_*, CLAUDE_* and CLOUD_ML_REGION (claude); CODEX_* and OPENAI_API_KEY (codex); AGY_*, ANTIGRAVITY_*, GEMINI_API_KEY, GOOGLE_API_KEY, GOOGLE_GEMINI_BASE_URL, GOOGLE_GENAI_USE_ENTERPRISE (agy); QWEN_*, OPENAI_BASE_URL, GOOGLE_CLOUD_PROJECT, GOOGLE_GENAI_USE_VERTEXAI (qwen). Anything else — cloud credentials for Bedrock or Vertex, say — is passed only when you ask: --pass-env NAME (repeatable), or "pass_env": [...] in ~/.agmx/settings.json. The runner's own git commands keep the full environment, since pushing needs your SSH agent. If the repository has no git identity configured, the command also gets GIT_AUTHOR_*/GIT_COMMITTER_* as agmx runner <agmx@localhost> in its environment, so an agent never writes git config into your clone. Output is streamed back in batches while it runs, and every batch is delivered before the result is reported. The runner keeps sending heartbeats throughout, so a long run keeps its lease.

  6. What happens on exit depends on kind:

    • execute is the only kind that commits. The agent may commit by itself; whatever it leaves uncommitted is committed after it on agmx-run/<run_id> — never anything under .agmx/, which your repository does not ignore. The run's head is the worktree's HEAD; one that does not descend from the base (the agent checked out something unrelated) fails the run instead of being reported as the work. The branch is pushed to your remote, because the reviewer is usually a different machine. A push that fails fails the run, saying the commit exists only on this machine; the local branch is kept. A pre-commit hook that rejects the change fails the run too, but the work is still committed to the branch so someone can see what was rejected. If the commit itself fails (a signing key that cannot be used, say), the worktree is kept on disk and its path is in the error, so the agent's uncommitted work can be recovered.
    • Every other kind (review, and any kind added later) is read-only: no branch, no commit, no push. A review run's .agmx/review-<task_id>.json is read before the worktree goes and sent with the result — as an object when it is a JSON object, verbatim otherwise.
  7. The worktree is removed and the runner reports facts: exit code, base and head SHA, whether it timed out. It does not decide whether the run succeeded; that judgement stays on the server, with the review history. The exit code is the agent's own: 0 when the agent finished cleanly and a step of the runner's failed afterwards (commit, push), and absent when the agent never started (a missing commit, a worktree that could not be created) — so the server does not blame the agent for this machine's git. A report the server did not record (a 5xx, a dropped connection) is resent a bounded number of times; a refusal (4xx) is final.

A run that exceeds timeout_seconds has its whole process group terminated, not just the process the runner spawned — coding agents spawn children. The same happens at once when the server cancels a run (the heartbeat lists it, or the server answers run_cancelled/not_your_run): nothing is committed or pushed, the worktree is cleaned up, and the runner reports failed once.

How a result lands

The control plane keeps no checkout of your code, so it cannot merge and it does not pretend to. Once a result passes review, the server offers the merge to a runner that has registered that project, and the merge happens here:

  1. Fetch the shared branch (the one the server names for the project, else your checkout's upstream), and check whether the commit is already on it — if it is, the work is done and the runner says so. A commit this machine never had is fetched from the agmx-run/* branch the executing machine pushed.
  2. Create a temporary worktree at the fetched remote tip and merge there with --no-ff. Your own checkout is never checked out, merged into, or required to be clean: a local branch that is behind, a detached HEAD, or uncommitted edits make no difference.
  3. Push HEAD to the shared branch, then remove the temporary worktree. A merge that is not pushed leaves the work on exactly one laptop, and the merge commit reported back to the server would name something nobody else can see.
  4. Delete the agmx-run/* branches, locally and on the remote, that the landed commit now contains — only those, and a failure to delete never fails the landing.
  5. Report the merge commit. Only then does the task become done.

A repository with no remote is a single-machine project: the merge goes into the checked-out branch, which then has to be on a branch and clean.

Landing refuses rather than improvising, and says which kind of failure it was, because that decides who waits: local — this machine's own state (not a repository, the commit not found even after fetching, and without a remote a detached HEAD or uncommitted tracked changes); another machine of the team may still land it. transient — the push timed out, or the remote moved ahead (non-fast-forward, fetch first); retried shortly. conflict — a merge conflict, or a push refused for any other reason (a protected branch, no permission); only a person can fix those. Each failure is reported with the reason, and your repository is left as it was — the temporary worktree goes with the failed merge or the unpushed one, and the next attempt starts again from the remote's new tip. Your uncommitted work is never touched.

The practical consequence: if nobody is running agmx runner start, no task reaches done. That is the design, not an outage.

Configuration

Path Mode Contents
~/.agmx/.credentials.json 0600 login session (access and refresh token) or personal access token, runner token
~/.agmx/settings.json 0644 server, email, issuer, runner_id, pass_env, full_access, serve_team
~/.agmx/run-secrets/ 0700 one directory per running run: its MCP config and agy plugin, removed when it ends
~/.local/share/agmx/runs/ 0700 one directory per run: its worktree, secrets, TMPDIR

Secrets are kept in a separate file from settings, and the credentials file is created with its mode already set rather than being widened and then narrowed. Set AGMX_CONFIG_DIR to move the directory. Run directories live apart from it ($XDG_DATA_HOME/agmx, or AGMX_DATA_DIR): the sandboxes the server builds deny reads of ~/.agmx, which holds your tokens, and a worktree in /tmp would be writable by every sandboxed run.

Security notes

  • The password is read from the terminal only. There is deliberately no --password flag: it would survive in shell history and in ps output.
  • GIT_DIR and GIT_WORK_TREE are stripped from the environment handed to the command, and from the runner's own git calls, so neither an agent nor a merge can reach outside the repository it was pointed at. Those variables beat git -C, always.
  • The default server is HTTPS. agmx login sends a password in the request body; plain HTTP would hand it to anyone on the path.
  • The runner token can drive only the runner routes: it cannot call a single coordinator tool, and revoking one machine does not touch your login.
  • An agent never sees the runner's full environment (see How a run executes), and never writes into your checkout's git config: missing git identity is given to it through its environment.

Development

git clone https://github.com/nothan-agentic/AgentMatrix
cd AgentMatrix/runner
pip install -e .
python -m pytest tests -q

License

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agmx-0.2.2.tar.gz (97.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agmx-0.2.2-py3-none-any.whl (56.5 kB view details)

Uploaded Python 3

File details

Details for the file agmx-0.2.2.tar.gz.

File metadata

  • Download URL: agmx-0.2.2.tar.gz
  • Upload date:
  • Size: 97.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.12.14

File hashes

Hashes for agmx-0.2.2.tar.gz
Algorithm Hash digest
SHA256 ea06821a38b13abbe1935b5521ec8d33f105e2e7f693a4613eb20515b6ec92e9
MD5 dd86458cdf9e2f761c3c6f75a92ba004
BLAKE2b-256 69214a17acd5f40ba60c1199e64e83e5cd91e8cd2e8214266aa01f48d2d8f197

See more details on using hashes here.

File details

Details for the file agmx-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: agmx-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 56.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.12.14

File hashes

Hashes for agmx-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 e09a9f0202ec3b7252e73f86be691d5e095d441f0631a784c1ad69fd85eafb73
MD5 a89646f1a91a061af62c3167f6ea2474
BLAKE2b-256 1b3f8d44898bb1a1d9ee7ad7dcbe59630f77d71e6669b57488a95057ae54d4da

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 files

0.2.1

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page