Run agent CLIs in project-scoped containers on macOS
This repository provides a practical way to run coding agents on Apple Silicon
Macs with Apple’s container tool. Use provider-backed online models or keep
inference local with Ollama.
Run agentctl --version to inspect the installed release. Published versions
are listed on the GitHub Releases
page; the maintainer
release process is documented in docs/releases.md.
The main entry point is agentctl, which manages:
- curated images such as
agent-plain,agent-python, andagent-swift - runtime selection (
codex,claude, and more over time) - local vs online launch modes
- runtime credentials stored through macOS Keychain
- optional host MCP servers, feature packs, and container lifecycle
You need:
- an Apple Silicon Mac running macOS 26
- Apple’s
containerCLI 1.1 or newer - Git for cloning the repository and selecting releases
- the system Bash and Apple-provided
jq(jq 1.6 or newer; macOS 26 providesjq 1.7.1-apple)
This baseline is Homebrew-free and does not require host-side Python or another package manager. The managed MCP bridge requires host-side Node.js. Ollama is required only for local-model workflows; online runtime workflows do not need it.
Recommended memory:
- for local-model workflows, plan for at least 32 GB RAM
- online-only workflows may work with less memory, but that is not yet verified in the current docs/test matrix
Official releases:
agentctl: https://github.com/pd95/local-agent-container/releasescontainer: https://github.com/apple/container/releases- Ollama: https://ollama.com/download
After installing container (and Ollama if you plan to use local models), make
a Git clone so you can select and return to published agentctl releases later:
git clone https://github.com/pd95/local-agent-container.git
cd local-agent-containerThe default checkout follows the main branch. To pin the installation to a
specific published release, fetch its tag and check it out. This example uses
v0.7.1; replace it with the version you want:
release=v0.7.1
git fetch origin tag "$release"
git switch --detach "$release"A detached checkout is expected when using a release tag. To return to the
development branch later, run git switch main followed by git pull --ff-only.
Then make agentctl available on your PATH. The easiest option on macOS is
usually a symlink into /usr/local/bin:
sudo ln -sf "$PWD/agentctl" /usr/local/bin/agentctlIf you prefer a user-local install instead:
mkdir -p "$HOME/bin"
ln -sf "$PWD/agentctl" "$HOME/bin/agentctl"
export PATH="$HOME/bin:$PATH"Add the PATH export to ~/.zprofile if $HOME/bin should remain available in
new Terminal sessions. Both installation methods use a symlink, so selecting a
different version in the Git clone immediately changes the host-side
agentctl version.
Start Apple's container service:
container system startSee Use a specific agentctl release for the complete checkout-and-refresh workflow.
Build the Python image once, then change to the project you want the agent to work on:
agentctl build --image agent-python
cd /path/to/projectFor provider-backed Codex, authenticate once and start an online session:
agentctl auth --runtime codex
agentctl run --image agent-python --onlineLater runs from the same directory reuse that container:
agentctl run --onlineFor local inference, install Ollama, pull the default model, and let agentctl start a container-accessible listener:
ollama pull gpt-oss:20b
agentctl run --image agent-python --start-ollamaIf you prefer the smaller general-purpose image, replace agent-python in the
build and first-run commands with agent-plain:
agentctl build --image agent-plain
agentctl run --image agent-plain --onlineSee docs/local-vs-online.md for other local profiles, model overrides, and additional runtimes.
agentctl run starts an agent inside a container, but it mounts a host
directory into that container at /workdir.
The mounted host directory supplies the files the agent works on, while the
selected image supplies its development tools. By default, agentctl creates or
reuses a named container for the current directory, so container-local runtime
state and history remain available between runs. Use --temp when you want a
disposable container.
In the normal case:
- the directory you run
agentctl runfrom becomes the mounted work directory - everything under that directory is visible to the agent
- the agent can read and write files in that mounted directory tree
- the agent does not get unrestricted access to the rest of your host
filesystem through
agentctl
So the normal workflow is:
cdinto the project or document folder you want the agent to work on- run
agentctl run - let the agent work inside that mounted directory tree
If you want a different directory than the current one, give the container an explicit name so later lifecycle commands do not depend on your current directory:
agentctl run --name agent-my-project --workdir /path/to/projectBy default, the current directory selects a persistent container. Choose its image on the first run; later runs from the same directory reuse the container, installed tools, runtime state, and conversation history:
cd /path/to/project
agentctl run --image agent-python --online
# Later, from the same directory:
agentctl run --onlineagent-python is a practical default when the agent needs Python tooling. Use
agent-plain for a smaller general-purpose environment, and choose
agent-swift when the project actually needs the Swift toolchain. To change an
existing container's image, use agentctl upgrade --image ...; see
Choosing an image.
Authenticate once, then add --online whenever the runtime should use its
provider-backed cloud models instead of the local Ollama profile:
agentctl auth --runtime codex
agentctl run --onlineAuthentication is synchronized with the persistent container. See docs/auth.md for additional runtimes and credential handling.
Add --update when you want agentctl to update the Codex CLI inside the
project's container immediately before starting the session:
agentctl run --online --updateThe updated Codex installation remains in a persistent container for later runs. This updates Codex itself; it does not update the agentctl Git checkout or rebuild the container image. See Runtime management for the standalone runtime update command.
--temp creates an unnamed container for the current directory and removes it
after the session. Files written in the mounted project directory remain on the
Mac, while container-local packages and runtime state are discarded:
agentctl run --temp --online
agentctl run --temp --image agent-python --onlineFor a new persistent container, the built-in xcode preset enables the managed
MCP bridge and exposes the Mac's xcrun mcpbridge to Codex:
agentctl run --image agent-python --online --mcp xcodeAn existing container created without MCP wiring needs a one-time upgrade. Definitions can then be added without recreating it again:
agentctl upgrade --enable-mcp
agentctl mcp add xcodeAdd custom host MCP servers with an inline definition or a private definition file, then inspect the configured routes and their health:
agentctl mcp add \
'{"name":"macos-ui-helper","command":"/absolute/path/to/server","args":[]}'
agentctl mcp add @"$HOME/.config/agentctl/private-mcp.json"
agentctl mcp list
agentctl mcp statusThe host command remains outside the container and is reachable through a private managed bridge. See docs/managed-mcp.md for credentials, HTTP upstreams, lifecycle behavior, and additional definitions.
Enable Remote Control for a persistent project when you want an eligible ChatGPT mobile or desktop client to notify you and let you continue working with its Codex environment:
agentctl remote-control start
agentctl remote-control pairRun these commands from the project's directory. Pairing is needed when you authorize a new client, not every time the container starts. Agentctl remembers that Remote Control is enabled and restores it through normal container start/stop cycles. The integration is experimental and uses online Codex; see Remote Control details and limitations.
The image supplies the base toolchain, the runtime selects the agent CLI, and features add optional tools to a compatible image:
agentctl runtime list
agentctl runtime info codex
agentctl runtime install claude
agentctl runtime use claude
# Add office tooling to an agent-python container.
agentctl feature info office
agentctl feature install officeUse these curated images for most workflows:
agent-plain: general shell, Git, and runtime workagent-python: Python-heavy tasks and librariesagent-swift: Swift toolchain and SwiftPM workflows
agent-office remains only as a legacy compatibility image. For new work, use
agent-python plus the office feature pack.
Build only the images you need:
agentctl build --image agent-plain
agentctl build --image agent-python
agentctl build --image agent-swiftIf you started with one curated image and later need another one for the same
container, build the target image and recreate the container with agentctl upgrade --image .... For example:
agentctl build --image agent-python
agentctl upgrade --name <container> --image agent-pythonUpgrades create backup images by default. To inspect a backup image without
refreshing it or mounting the current workdir, use rescue:
agentctl rescue --image <container>-backup-<timestamp>For upgrade recovery workflows, package and Python restoration policies, and resumable recovery plans, see Upgrade recovery. For backup-image rescue and restore examples, see docs/rescue.md.
If you already have a compatible base container and want to bring the managed
control surface onto it, use agentctl bootstrap instead of starting from a
curated image. More on that in docs/bootstrap.md.
The installed symlink points into your Git clone, so the checked-out commit determines which host-side agentctl code is used. Fetch the tags and select the published release you want:
cd /path/to/local-agent-container
git fetch origin --tags
git switch --detach v0.7.1 # Replace with the desired release.
agentctl --versionThen change to each project whose existing container should receive that release's managed scripts and defaults:
cd /path/to/project
agentctl refreshThe directory change matters because the current project normally selects the
persistent container. From another directory, target it explicitly with
agentctl refresh --name <container>.
refresh preserves the container, installed packages, and active runtime
configuration. It does not rebuild images or change the container's image.
Read the selected release's notes in case that release also calls for an image
build or agentctl upgrade.
To follow current development again:
cd /path/to/local-agent-container
git switch main
git pull --ff-onlyRemote Control lets an eligible ChatGPT desktop or mobile client work with a
Codex environment running inside an existing agentctl container. Agentctl starts
the Codex App Server in the container using the same ~/.codex state as local
Codex sessions. The App Server establishes the provider connection itself, so
agentctl does not publish an inbound App Server port or socket on the host.
agentctl remote-control start
agentctl remote-control pair
agentctl remote-control stopstart prepares the existing container, synchronizes online authentication,
and starts the App Server. It also starts the container itself when necessary.
Remote Control is provider-backed and therefore implies online operation; it
does not use the local Ollama profile.
Pairing is the explicit authorization step for a new ChatGPT controller. The
pair command asks Codex for a short-lived code; enter that code only in the
intended ChatGPT client to allow it to discover and connect to this running
environment. Agentctl never pairs automatically, stores the code, or writes it
to logs. Existing enrollment state under ~/.codex is reused across normal
container stop/start cycles, so pairing is not part of every startup.
remote-control stop disables the service until it is explicitly started
again. In contrast, an ordinary agentctl stop preserves Remote Control intent,
and the next agentctl start restores the App Server automatically.
This integration wraps experimental Codex CLI behavior. Availability depends on the ChatGPT account, workspace policy, and client rollout. Agentctl keeps the App Server on its local Unix socket and does not expose it on the network. See docs/remote-control.md for lifecycle behavior, authentication synchronization, status semantics, diagnostics, and testing.
SSH-agent forwarding, Unix-socket forwarding, and stdio protocol bridges are available for specialized integrations. See docs/advanced-container-usage.md and docs/unix-sockets.md.
Host integration and shell unit tests are documented in TESTING.md.
Fast checks (the host runner defaults to its smoke tier):
bash tests/run-unit-tests.sh
bash tests/run-tests.shUse bash tests/run-tests.sh --tier full for release and runtime-upgrade
validation.
Start here, then move into the more specialized guides as needed: