Skip to content

Configuration File

PathPurpose
~/.config/jackin/config.tomlGlobal operator configuration (auth defaults, role trust/source, global mounts, global env)
~/.config/jackin/workspaces/<name>.tomlOne saved workspace per file. The workspace name is the filename stem.
~/.jackin/roles/Cached role repository checkouts
~/.jackin/data/<container-name>/Persisted agent state (Claude history, settings, GitHub CLI config, plugins)

Three environment variables redirect these default storage roots without touching any TOML config, useful for testing a PR in isolation or running multiple independent configurations side by side:

VariableDefaultOverrides
JACKIN_CONFIG_DIR~/.config/jackinConfig root — config.toml, workspaces/
JACKIN_HOME_DIR~/.jackinjackin’ home root — data/, roles/, cache/
JACKIN_CONSTRUCT_IMAGEprojectjackin/construct:trixieOverride the construct base image used at derived-build time. Role Dockerfiles always reference the canonical name; this substitutes it during docker build. Set to jackin-local/construct:trixie after just construct-build-local to test against a local construct build. See The Construct Image for the full workflow.

Setting JACKIN_CONFIG_DIR and JACKIN_HOME_DIR to fresh directories before running jackin means every command reads and writes only those directories, leaving the live operator state completely untouched. Unset behavior is identical to not setting the variables — jackin falls back to the defaults above.

Saved workspaces live one-per-file under ~/.config/jackin/workspaces/ rather than as [workspaces.<name>] tables inside a single config.toml. The layout works directly with a dotfile manager like chezmoi tracking ~/.config/jackin/. Operators commonly want to:

  • Version-control selectively — track config.toml globally, include only team-relevant workspace files, leave the rest out via .gitignore, or keep workspaces private per-machine.
  • Restore the full setup on a new machine by checking out the dotfile repo and running chezmoi apply (or equivalent), with no manual TOML reconstruction.
  • Diff a single workspace in PR review without seeing churn in unrelated workspaces, which is impossible when every change touches a shared [workspaces.*] block.

Each file carries its own version = "v1alpha4" stamp, so a dotfile-managed setup that is partially old (some workspaces stamped, some not) migrates per-file on first load. See Schema Versions for the full version history and migration matrix.

The configuration is TOML. Global settings live at ~/.config/jackin/config.toml; saved workspaces live one-per-file under ~/.config/jackin/workspaces/. The CLI and console manage both locations for you, so you generally don’t need to edit them by hand.

Top-level version is the config schema version. jackin’ writes version = "v1alpha5" today. Older config files with no version are treated as legacy and migrated automatically on startup before parsing. See Schema Versions for the full version history and migration matrix.

auth_forward is configured per agent (claude, codex, amp, kimi, opencode) at three layers, with most-specific wins:

version = "v1alpha5"
# Layer 1 — global default (per agent)
[claude]
auth_forward = "sync" # "sync" (default), "api_key", "oauth_token", or "ignore"
[codex]
auth_forward = "sync" # "sync" (default), "api_key", or "ignore" — Codex rejects "oauth_token"
[amp]
auth_forward = "sync" # "sync" (default), "api_key", or "ignore" — Amp rejects "oauth_token"
[kimi]
auth_forward = "sync" # "sync" (default), "api_key", or "ignore" — Kimi rejects "oauth_token"
[opencode]
auth_forward = "sync" # "sync" (default), "api_key", or "ignore" — OpenCode rejects "oauth_token"
# Layer 2 — workspace-wide override
[claude]
auth_forward = "oauth_token"
[codex]
auth_forward = "api_key"
[amp]
auth_forward = "api_key"
[kimi]
auth_forward = "sync"
[opencode]
auth_forward = "sync"
# Layer 3 — most-specific (workspace × role × agent) override
[roles.agent-smith.claude]
auth_forward = "ignore"
[roles.agent-smith.codex]
auth_forward = "sync"
[roles.agent-smith.amp]
auth_forward = "ignore"
[roles.agent-smith.kimi]
auth_forward = "sync"
[roles.agent-smith.opencode]
auth_forward = "sync"

The layer 2 and 3 examples above belong in ~/.config/jackin/workspaces/my-app.toml; the global defaults belong in config.toml.

FieldDescriptionDefault
auth_forwardHow the host’s agent credentials (or env-supplied keys/tokens) are made available to agent containers. See Authentication Forwarding.sync

Accepted values: sync | api_key | oauth_token | ignore. oauth_token is Claude only — setting it under any [codex], [amp], [kimi], or [opencode] block is rejected at parse time.

Locations the field may appear at, in resolution order (most-specific first):

  • [roles.<role>.claude].auth_forward in workspaces/<ws>.toml
  • [roles.<role>.codex].auth_forward in workspaces/<ws>.toml
  • [roles.<role>.amp].auth_forward in workspaces/<ws>.toml
  • [roles.<role>.kimi].auth_forward in workspaces/<ws>.toml
  • [roles.<role>.opencode].auth_forward in workspaces/<ws>.toml
  • [claude].auth_forward in workspaces/<ws>.toml
  • [codex].auth_forward in workspaces/<ws>.toml
  • [amp].auth_forward in workspaces/<ws>.toml
  • [kimi].auth_forward in workspaces/<ws>.toml
  • [opencode].auth_forward in workspaces/<ws>.toml
  • [claude].auth_forward (global default)
  • [codex].auth_forward (global default)
  • [amp].auth_forward (global default)
  • [kimi].auth_forward (global default)
  • [opencode].auth_forward (global default)

Credentials referenced by api_key and oauth_token live in ordinary [*.env] blocks under the well-known names ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, OPENAI_API_KEY, AMP_API_KEY, and KIMI_API_KEY, and OPENCODE_API_KEY.

GitHub CLI (gh) auth uses a parallel [github] block with three modes — sync (default), token, ignore. The [github] axis has no agent dimension because ~/.config/gh/ is shared by every agent in the container.

# Layer 1 — global default
[github]
auth_forward = "sync" # "sync" (default), "token", or "ignore"
# Layer 2 — workspace-wide override (e.g. a customer-facing PAT)
[github]
auth_forward = "token"
[github.env]
GH_TOKEN = { op = "op://abc.../def.../fld...", path = "Work/ACME/github-fine-grained-pat" }
# Layer 3 — workspace × role override (e.g. a tighter release-bot token)
[roles.release-bot.github]
auth_forward = "token"
[roles.release-bot.github.env]
GH_TOKEN = { op = "op://abc.../def.../fld...", path = "Work/ACME/github-release-pat" }
# Per-role opt-out — throwaway role with no GitHub access
[roles.scratch.github]
auth_forward = "ignore"

Workspace-scoped GitHub blocks live in ~/.config/jackin/workspaces/customer-acme.toml.

Mode behaviour:

  • sync — materialize ~/.config/gh/hosts.yml from the host’s gh login on each launch (gh auth token --hostname github.com, with ~/.config/gh/hosts.yml parse as a fallback). When the host is logged out, any existing in-container hosts.yml is preserved.
  • token — wipe any prior hosts.yml and inject GH_TOKEN (and GITHUB_TOKEN from the same source) into the container’s process env from the resolved [github.env] map. Pre-flight aborts launch when GH_TOKEN is missing.
  • ignore — wipe any forwarded hosts.yml and export nothing.

Resolution order, most-specific first:

  • [roles.<role>.github].auth_forward in workspaces/<ws>.toml
  • [github].auth_forward in workspaces/<ws>.toml
  • [github].auth_forward (global default)

Operator-only — role manifests cannot set or override [github], same rule as [claude] / [codex] / [kimi] / [opencode]. Optional GHE-targeted env vars GH_HOST and GH_ENTERPRISE_TOKEN may also be set in the […github.env] block; they are forwarded to the container as-is.

Four layers contribute env vars to the agent container, merged with later-wins semantics:

# Layer 1 — global
[env]
OPERATOR_ORG = "acme-corp"
# Layer 2 — per role
[roles.agent-smith.env]
API_TOKEN = "op://Personal/api/token"
# Layer 3 — per workspace
[env]
REGISTRY = "${COMPANY_REGISTRY_URL}"
# Layer 4 — per (workspace, role)
[roles.agent-smith.env]
API_TOKEN = "op://Work/shared-smith/token"

Layer 3 and 4 env blocks are stored in ~/.config/jackin/workspaces/big-monorepo.toml.

Values can be:

  • op://VAULT/ITEM/FIELD — resolved via the 1Password CLI (op read)
  • $NAME or ${NAME} — read from the host env
  • Anything else — literal

See Environment Variables for the full guide.

workdir = "/home/user/Projects/my-app"
allowed_roles = ["agent-smith", "the-architect"]
default_role = "agent-smith"
default_agent = "amp"
last_role = "the-architect"
[[mounts]]
src = "/home/user/Projects/my-app"
dst = "/home/user/Projects/my-app"
readonly = false
isolation = "worktree"
[[mounts]]
src = "/home/user/cache"
dst = "/cache"
readonly = true
[keep_awake]
enabled = true
git_pull_on_entry = true

This example is the contents of ~/.config/jackin/workspaces/my-app.toml; there is no [workspaces.my-app] header inside the file.

FieldDescription
workdirContainer working directory
mountsArray of mount specifications
mounts[].srcHost path
mounts[].dstContainer path
mounts[].readonlyRead-only flag
mounts[].isolationPer-mount isolation mode: shared (default), worktree, or clone. Omitting the field deserializes to shared, but every save writes the field explicitly so old configs migrate to the new shape on first save. Global mounts (under [docker.mounts]) reject this field at parse time. See Per-mount isolation.
allowed_rolesList of allowed role selectors
default_roleDefault role selector
default_agentOptional workspace default agent ("claude", "codex", "amp", "kimi", or "opencode"). Omit to let launch resolution use the role manifest: a single-agent role uses its only runtime, while a multi-agent role prompts interactively.
last_roleMost recently used role selector for this workspace
keep_awake.enabledmacOS-only: keep the host awake while any agent in this workspace is running (see Keeping the host awake). On Linux/Windows the flag is silently accepted but has no effect — useful when sharing a config.toml across machines, but the host will still sleep.
git_pull_on_entryRun git pull on every mounted git repository from the host before the agent container starts. Failures are non-fatal — the launch continues even when offline or the working tree is dirty. Omitted from TOML when false (the default). See Git pull on entry.

When you use jackin workspace create, workdir is only the container start path. Add an explicit --mount for every directory the container should see.

[docker.mounts]
gradle-cache = { src = "/home/user/.gradle/caches", dst = "/home/agent/.gradle/caches", readonly = true }
[docker.mounts."chainargos/*"]
secrets = { src = "/home/user/.chainargos/secrets", dst = "/secrets", readonly = true }

Global mounts are stored by name. Unscoped mounts live directly under [docker.mounts]. Scoped mounts live under a scope key such as [docker.mounts."chainargos/*"].

When coauthor_trailer is true, jackin installs a shared prepare-commit-msg hook inside the role container the first time a session needs it. The hook is a symlink to jackin-capsule, so trailer normalization runs through the same Rust binary that owns deterministic runtime setup. It reads which agent is running and appends that agent’s Co-authored-by trailer whenever Git asks the hook to prepare a commit message, including amended, reused, squash, and merge messages. Existing agent trailers are preserved, so a commit touched by multiple agents keeps each involved agent in the final trailer block.

When dco is true, the same hook also appends a Signed-off-by DCO trailer read from git config user.name and git config user.email inside the container. Matching existing trailer lines are normalized into Git’s final trailer block before commit creation. The two fields are independent: enable either or both.

[git]
coauthor_trailer = true # enable Co-authored-by agent trailer
dco = true # enable Signed-off-by DCO trailer

The [git] table is omitted from config.toml when all fields are at their defaults (the table is not written unless the operator sets it). See Schema Versions for the field-level reference.

Each running role stores state at ~/.jackin/data/<container-name>/:

File/DirectoryPurpose
claude/account.jsonClaude Code account metadata (mounted at /jackin/claude/account.json in the container; only forwarded under auth_forward = "sync")
claude/credentials.jsonClaude Code OAuth credentials (mounted at /jackin/claude/credentials.json; only forwarded under auth_forward = "sync")
codex/auth.jsonSynced copy of host ~/.codex/auth.json under auth_forward = "sync", 0600 perms (mounted at /jackin/codex/auth.json; absent when host has no auth.json)
amp/secrets.jsonSynced copy of host ~/.local/share/amp/secrets.json under auth_forward = "sync", 0600 perms (mounted at /jackin/amp/secrets.json; absent when host has no secrets.json). secrets.json is the XDG_DATA file the Amp CLI writes the apiKey@https://ampcode.com/ token into; the XDG_CONFIG path ~/.config/amp/settings.json is preferences only and is intentionally not forwarded
kimi/config.tomlSynced copy of host ~/.kimi/config.toml under auth_forward = "sync", 0600 perms (mounted under /jackin/kimi/ and copied into /home/agent/.kimi/ before Kimi starts). Kimi stores OAuth tokens separately, but its login flow writes the OAuth-backed provider/model/service references into this config file, so a fresh default config is not enough to use the forwarded token.
kimi/credentials/Synced copy of host ~/.kimi/credentials/ OAuth token files under auth_forward = "sync", 0600 perms
kimi/device_idSynced copy of host ~/.kimi/device_id under auth_forward = "sync", 0600 perms; Kimi includes this device id in OAuth/device headers
opencode/auth.jsonSynced copy of host ~/.local/share/opencode/auth.json under auth_forward = "sync", 0600 perms (mounted at /jackin/opencode/auth.json; absent when host has no auth.json). OpenCode stores provider credentials in this file — for example, a Z.AI Coding Plan API key
home/.claude/ and home/.claude.jsonDurable Claude Code home state, including conversation history, runtime-local plugins, account metadata, and settings written during the session
home/.codex/Durable Codex home state, including conversation history, runtime-local config, and login state created inside the session
home/.local/share/amp/Durable Amp data state created inside the session
home/.kimi/Durable Kimi home state, including runtime-local config and login state created inside the session
home/.local/share/opencode/Durable OpenCode data state (auth credentials, model preferences) created inside the session
state/jackin runtime state mounted at /jackin/state, including the setup_once marker
.config/gh/GitHub CLI authentication and settings (agent-neutral)

Host→container auth handoff lives under a single tree (/jackin/) the operator can audit at a glance. The agent home directories are bind-mounted from jackin-managed per-instance state, not from the operator’s host home. jackin-capsule runtime-setup seeds image-baked defaults into that durable home on first launch, then copies the current auth handoff files from /jackin/ into the durable home before starting the agent. This keeps conversation history and runtime-local plugins recoverable when Docker containers disappear while still letting the current auth mode override stale forwarded credentials.

For roles that list multiple supported agents in their manifest, jackin provisions claude/, codex/, amp/, kimi/, and opencode/ auth handoff directories and their corresponding home/.claude/, home/.codex/, home/.local/share/amp/, home/.kimi/, and home/.local/share/opencode/ durable home directories for every agent the role supports — not just the agent selected for the initial launch. This means starting a second agent via jackin hardline --new does not require re-authentication: the credentials and durable home were already placed in the container when it first launched.

claude/account.json is rebuilt as {} (empty object) by every non-sync auth mode so it is always present on disk; the bind mount is the gate for whether the file flows into the container, not the file’s existence. claude/credentials.json is wiped on every non-sync mode and never bind-mounted under ignore/api_key/oauth_token.

Delete with jackin purge <role> to start fresh.