Design Principles
jackin’ makes a small number of strict promises that shape every feature in the project. Knowing them up front explains why some things work the way they do (and why some shortcuts you might expect — “jackin’ could just fix that on my host for me” — don’t exist on purpose).
These principles apply project-wide. If you ever observe jackin’ behaving in a way that contradicts one of them, that’s a bug — please open an issue.
Never mutate the host machine silently
Section titled “Never mutate the host machine silently”The operator’s host is their property. jackin’ never writes to host-side state to make a container work.
Container containment cuts both ways. Agents can’t escape the
container — and jackin’ itself doesn’t reach back out. Every piece
of “make this work” plumbing happens inside the container, in
jackin’s own state directory, or in the container’s process env.
Your host repos, dotfiles, git remotes, shell rc files, and CLI
configs (gh, claude, codex, git) are read but never
written.
Concrete examples of what this rule blocks:
- No rewriting your git remotes. When a host repo’s
originisgit@github.com:owner/repo(SSH) and the container has no SSH key, jackin’ rewrites the URL to HTTPS inside the container’s git config only. Your host repo’s.git/configis untouched. Rungit pushon the host afterwards and SSH still works the way you expect. - No
gh auth setup-giton your host. The container can run it inside its own writable layer. Your host’s git credential helper stays exactly as you configured it. - No edits to
~/.gitconfig,~/.ssh/config,~/.config/gh/,~/.claude/, or~/.codex/. jackin’ reads from these to forward auth into the container, but never writes back. - No background
git fetch/git pull/git pruneon your host repos as a side effect. Pull-on-launch only happens when you explicitly opt in per workspace, and only against the repos that workspace mounts. - No in-container token rotation flowing back to your host.
If an agent inside the container runs
gh auth refreshorclaude /login, the new credentials stay in the container. Bidirectional sync (token refresh in the container reaching back to your host) is planned, but it will be explicit opt-in surfaced in the launch summary, never silent.
When jackin’ does need to act on the host
Section titled “When jackin’ does need to act on the host”A few jackin’ commands legitimately operate against the host —
for example, jackin load clones role repos into jackin’s own
data directory, and git_pull_on_entry (when you enable it)
runs git pull on the workspace’s mounted repos before the
agent starts. Whenever an action like this happens, jackin’
follows two rules:
- Stay scoped. Touch only the things the operator explicitly opted into (a workspace they configured, a subcommand they invoked, a flag they passed). No “while we’re here” side effects.
- Surface it. The action is visible — printed in the launch
summary, returned in the command output, or documented as
part of the flag. Operators should never have to grep
git reflogto discover what jackin’ did.
If you’re reading a design proposal that wants jackin’ to write to a new host-side location, look for an explicit “Host-side effects” section that names the action, the opt-in path, and the launch-summary line. Proposals without that section get rejected at review.
Operator-only configuration boundaries
Section titled “Operator-only configuration boundaries”Auth forwarding, env vars, mount selection, and the choice of
which agent runtime to use belong to the operator. They are
configured through jackin console, jackin config, or
jackin workspace. They are deliberately not addressable from
inside a role’s manifest.
This means an untrusted role you pulled from a public registry cannot:
- Force a credential-forwarding mode the operator didn’t approve.
- Mount a host directory the operator didn’t pick.
- Switch the agent runtime out from under the operator.
The role manifest controls what runs inside the container
(toolchain, plugins, default working directory, role’s own
Dockerfile). Everything that crosses the
host ↔ container boundary is the operator’s call.
Configuration is plain files, version-control friendly
Section titled “Configuration is plain files, version-control friendly”Operator configuration lives as plain TOML files you can put under version control or manage with a dotfile manager. When the format changes between releases jackin’ upgrades your files for you, so you do not hand-edit TOML to recover. The Configuration File reference covers the layout; Schema Versions covers the upgrade story.
In-container “full speed” is by design
Section titled “In-container “full speed” is by design”Claude Code’s --dangerously-skip-permissions, Codex’s
workspace-write + --dangerously-bypass-approvals-and-sandbox
(aliased --yolo), Amp’s --dangerously-allow-all, and the
equivalents shipping in every other agent runtime — jackin’
enables all of them by default.
The reason this is safe: the container, not the in-runtime prompt, is the trust boundary. Once you’ve decided to drop an agent into a workspace, in-runtime “are you sure?” prompts add friction without changing what the agent can reach. Container mounts, the network policy, and the Docker-in-Docker sidecar already define the blast radius. The agent gets to move fast inside that boundary; the boundary itself stays in place.
This is the same reason git_pull_on_entry, secret forwarding,
and persisted state all live at the workspace / role level
rather than being asked about per-action mid-session.
Container is the trust boundary, not the prompt
Section titled “Container is the trust boundary, not the prompt”A corollary to the previous principle, but worth calling out separately: the security model is the container plus the mount and network policy, not how cooperative the agent is.
This shapes the rest of the design:
- Mounts are minimal by default. The agent only sees what you mount.
- Each agent gets its own Docker-in-Docker sidecar, so any containers it builds or runs cannot escape into your host Docker daemon.
- Failures inside the container do not propagate to your host.
jackin purgediscards the persisted state cleanly without touching the host.
If a feature can be implemented inside the container, it goes inside the container. If it has to be host-side, it follows the “Stay scoped” + “Surface it” rules above.
Predictable over clever
Section titled “Predictable over clever”When two implementations are functionally equivalent, jackin’ picks the one with fewer surprises:
- Atomic file writes — credential files are written tmp-file + rename, never half-written.
- Read-then-skip — when a generated file’s content matches what’s already on disk, jackin’ doesn’t rewrite it, so file watchers and mtime-sensitive tools see no churn.
- Symlink rejection at trust boundaries — provisioned credential paths refuse to follow symlinks so a compromised role can’t redirect a write through the link.
- Typed errors at launch — when something fails (missing credential, broken host login, malformed config), jackin’ prints the actual reason instead of a generic catch-all, so you can tell “my host login expired” apart from “jackin’ is silently broken”.
How these principles inform feature design
Section titled “How these principles inform feature design”When you’re reading a feature page in this guide and wondering “why does jackin’ do it this way and not the obvious shortcut?” — the answer is almost always one of the principles above.
- The container fixing up its own git remote instead of jackin’
fixing your host repo’s
origin→ “Never mutate the host.” - An untrusted role not being able to flip auth-forward mode → “Operator-only configuration.”
- Auth tokens being copied into the container instead of re-prompted from inside → “Container is the trust boundary.”
If a feature appears to violate one of these principles, that’s a regression — please open an issue.