Role Manifest
Overview
Section titled “Overview”Every role repo must contain a jackin.role.toml file at the repository root. This manifest tells jackin’ how to build, configure, and identify the agent.
jackin’ enforces strict parsing — unknown fields are rejected with an error. This catches typos and prevents silent misconfiguration. Top-level version records the minimum manifest schema the file requires; older stamps remain valid until the manifest uses a newer field or enum value. On a desktop, use jackin role migrate <role-repo-path> to update a local manifest before adopting newer schema features. In CI and automated migration workflows, use the standalone jackin-validate --migrate <role-repo-path> binary instead of the full Jackin operator CLI. See Schema Versions for the full version history and migration matrix.
Full schema
Section titled “Full schema”version = "v1alpha3"dockerfile = "Dockerfile"agents = ["claude", "codex", "amp", "opencode"]
[identity]name = "The Architect"
[claude]model = "sonnet"plugins = [ "code-review@claude-plugins-official", "feature-dev@claude-plugins-official", "superpowers@superpowers-marketplace", "jackin-dev@jackin-marketplace",]
[[claude.marketplaces]]source = "obra/superpowers-marketplace"sparse = ["plugins", ".claude-plugin"]
[[claude.marketplaces]]source = "jackin-project/jackin-marketplace"
[codex]model = "gpt-5"
[amp]
[opencode]
[hooks]setup_once = "hooks/setup-once.sh"source = "hooks/source.sh"preflight = "hooks/preflight.sh"
[env.PROJECT]interactive = trueoptions = ["project1", "project2"]prompt = "Select a project:"
[env.BRANCH]interactive = truedepends_on = ["env.PROJECT"]prompt = "Branch for ${env.PROJECT}:"default = "feature/${env.PROJECT}"Top-level fields
Section titled “Top-level fields”| Field | Required | Description |
|---|---|---|
version | Yes | Manifest schema version. Current value is v1alpha3. |
dockerfile | Yes | Relative path to the Dockerfile within the repo |
agents | No | Non-empty list of agent slugs this role supports: "claude", "codex", "amp", or "opencode". When omitted, jackin treats the manifest as Claude-only. |
The Dockerfile path must:
- Be relative (no absolute paths)
- Stay inside the repository (no
../escapes) - Point to a valid Dockerfile
- Have a final stage that starts with
FROM projectjackin/construct:trixie
Every agent listed in agents must have a matching [<agent>] table, even when that table is empty. A manifest with agents = ["claude", "codex", "amp", "opencode"] needs [claude], [codex], [amp], and [opencode].
version = "v1alpha3"dockerfile = "Dockerfile"agents = ["claude", "codex", "amp", "opencode"]
[claude]plugins = []
[codex]model = "gpt-5"
[amp]
[opencode][claude]
Section titled “[claude]”| Field | Required | Description |
|---|---|---|
model | No | Optional Claude Code model passed to claude --model at launch |
plugins | No | List of Claude plugin identifiers to install in the derived image |
marketplaces | No | List of marketplace registrations to add before plugin installation |
Example values:
version = "v1alpha3"dockerfile = "Dockerfile"
[claude]model = "sonnet"plugins = [ "code-review@claude-plugins-official", "feature-dev@claude-plugins-official", "superpowers@superpowers-marketplace", "jackin-dev@jackin-marketplace",]
[[claude.marketplaces]]source = "obra/superpowers-marketplace"sparse = ["plugins", ".claude-plugin"]
[[claude.marketplaces]]source = "jackin-project/jackin-marketplace"Each [[claude.marketplaces]] block maps to claude plugin marketplace add <source>. If sparse is set, jackin passes those paths as claude plugin marketplace add <source> --sparse path1 path2.
[codex]
Section titled “[codex]”The [codex] table is required when "codex" appears in agents.
| Field | Required | Description |
|---|---|---|
model | No | Optional Codex model passed to codex -m at launch |
version = "v1alpha3"dockerfile = "Dockerfile"agents = ["codex"]
[codex]model = "gpt-5"The [amp] table is required when "amp" appears in agents. It is empty today and reserved for future Amp-specific settings.
version = "v1alpha3"dockerfile = "Dockerfile"agents = ["amp"]
[amp][opencode]
Section titled “[opencode]”The [opencode] table is required when "opencode" appears in agents.
| Field | Required | Description |
|---|---|---|
model | No | Optional OpenCode model passed to opencode -m at launch, in provider/model format (e.g. zai-coding-plan/glm-5.1) |
OpenCode is a terminal-based AI coding agent installed from GitHub Releases. It authenticates via ~/.local/share/opencode/auth.json, which stores provider-level credentials — for example, a Z.AI Coding Plan subscription (see z.ai/subscribe). jackin forwards these credentials into the container using the same sync / api_key / ignore auth-forwarding modes as the other agents. The OPENCODE_API_KEY env var is used for api_key mode.
version = "v1alpha3"dockerfile = "Dockerfile"agents = ["opencode"]
[opencode]model = "zai-coding-plan/glm-5.1"[[claude.marketplaces]]
Section titled “[[claude.marketplaces]]”Each marketplace block declares one Claude marketplace source to register before plugin installation.
| Field | Required | Description |
|---|---|---|
source | Yes | Raw marketplace source passed to claude plugin marketplace add |
sparse | No | Optional sparse-checkout paths for monorepo-backed marketplaces |
Example:
[claude]plugins = [ "code-review@claude-plugins-official", "feature-dev@claude-plugins-official", "superpowers@superpowers-marketplace", "jackin-dev@jackin-marketplace",]
[[claude.marketplaces]]source = "obra/superpowers-marketplace"sparse = ["plugins", ".claude-plugin"]
[[claude.marketplaces]]source = "jackin-project/jackin-marketplace"[identity]
Section titled “[identity]”| Field | Required | Description |
|---|---|---|
name | No | Human-readable display name for the role |
When omitted, jackin’ uses the role selector name.
[hooks]
Section titled “[hooks]”| Field | Required | Description |
|---|---|---|
setup_once | No | Relative path to a bash script executed once per container before the agent starts |
source | No | Relative path to a bash script sourced into the entrypoint shell before the agent starts |
preflight | No | Relative path to a bash script executed before every agent start |
Runtime hooks run inside the container after jackin has injected runtime metadata and resolved environment variables. Claude plugins declared in the manifest are already baked into the derived image by this point.
setup_onceis executed as a child process and gated by a marker file in jackin’s per-instance runtime state. It runs again only if the previous run failed or the instance state was purged. Treat it as “run once per jackin instance, not once per process restart.” Use it for installs, downloads, and one-time config writes that are still safe to repeat after state is intentionally discarded. Environment exports from this script do not reach the agent.sourceis sourced into the entrypoint shell on every container start. Use it forexportstatements,PATHchanges, and other shell state that must be inherited by the launched agent. Keep it small; because it mutates the entrypoint shell, avoid installs, downloads, broad traps, and permanent directory changes. Usereturn, notexit— a sourcedexitkills the entrypoint before the agent launches. Avoid embedding plaintext secrets: underJACKIN_DEBUG=1the entrypoint suspendsxtracearound the dot-source so expanded values do not leak to the operator’s terminal, but anything the script itself prints is still visible. Mount secret files via the role repo and read them into env vars instead of inlining them.preflightis executed as a child process on every container start. Use it for validation, diagnostics, and idempotent per-start setup. Environment exports from this script do not reach the agent.
Hook paths must:
- Be relative (no absolute paths)
- Stay inside the repository (no
../escapes) - Point to an existing, non-empty file
- Not be a symlink
Example:
[hooks]setup_once = "hooks/setup-once.sh"source = "hooks/source.sh"preflight = "hooks/preflight.sh"#!/bin/bashset -euo pipefail
if [ -n "${JACKIN_DIND_HOSTNAME:-}" ]; then export POSTGRESQL_DB_HOST="$JACKIN_DIND_HOSTNAME"fi#!/bin/bashset -euo pipefail
# Configure Context7 MCP if API key is availableif [ -n "${CONTEXT7_API_KEY:-}" ]; then ctx7 setup --claude --mcp --api-key "$CONTEXT7_API_KEY" -yfi[env.<NAME>]
Section titled “[env.<NAME>]”Declare environment variables that the agent needs at runtime. Each variable is a TOML table under [env].
Fields
Section titled “Fields”| Field | Type | Default | Description |
|---|---|---|---|
default | String | — | Default value (used if no prompt or user accepts default) |
interactive | bool | false | Whether to prompt the user at launch time |
skippable | bool | false | Whether the user can skip this prompt |
prompt | String | Variable name | Text shown when prompting |
options | String[] | [] | Options for a select-style prompt |
depends_on | String[] | [] | Variables that must be resolved first (use env. prefix) |
Validation rules
Section titled “Validation rules”- Variable names must contain only ASCII letters, digits, and underscores, and cannot start with a digit
- A non-interactive variable must have a
defaultvalue optionsrequiresinteractive = trueoptionscannot contain${env.*}interpolation — options are always staticdepends_onentries must use theenv.prefix (e.g.,"env.PROJECT")depends_onmust reference variables declared in the same manifest${env.*}references inpromptanddefaultmust point to declared variables that are listed independs_on- Reserved runtime names (see Reserved names for the canonical list) cannot be declared in
[env] - Circular dependencies are rejected
Runtime-managed variables
Section titled “Runtime-managed variables”jackin’ sets a small set of variables automatically inside the container — most notably JACKIN=1 (which marks that the process is running inside a jackin-managed runtime) and JACKIN_DIND_HOSTNAME (which agents use to reach published services started via the Docker-in-Docker daemon). The full set of reserved runtime names is documented under Reserved names and must not be declared in [env].
Interactive text input
Section titled “Interactive text input”Prompt the user for a free-text value:
[env.GIT_BRANCH]interactive = trueprompt = "Branch name:"
[env.BRANCH_WITH_DEFAULT]interactive = trueprompt = "Branch name:"default = "main"Interactive select
Section titled “Interactive select”Present a list of options:
[env.PROJECT]interactive = trueoptions = ["frontend", "backend", "infra"]prompt = "Select a project:"Skippable prompts
Section titled “Skippable prompts”Allow the user to skip a prompt. The variable won’t be set:
[env.API_KEY]interactive = trueskippable = trueprompt = "API key (optional):"Dependencies
Section titled “Dependencies”Control prompt ordering and skip cascading:
[env.PROJECT]interactive = trueskippable = trueoptions = ["frontend", "backend"]prompt = "Select a project:"
[env.BRANCH]interactive = truedepends_on = ["env.PROJECT"]prompt = "Branch to work on:"default = "main"If a skippable variable is skipped, all variables that depend on it are also skipped — regardless of their own skippable setting.
Interpolation
Section titled “Interpolation”Use ${env.VAR_NAME} in prompt and default fields to reference the resolved value of a dependency:
[env.PROJECT]interactive = trueoptions = ["frontend", "backend"]prompt = "Select a project:"
[env.BRANCH]interactive = truedepends_on = ["env.PROJECT"]prompt = "Branch for ${env.PROJECT}:"default = "feature/${env.PROJECT}"When the user selects frontend, the branch prompt becomes "Branch for frontend:" with default "feature/frontend".
Interpolation rules:
- Only
${env.*}references are resolved — other${...}forms are preserved as-is - Every
${env.*}reference must point to a variable declared in the same manifest and listed independs_on optionsarrays cannot contain${env.*}references — options are always static- Resolved values are never re-interpreted, so a value containing
${env.*}is treated as literal text
Minimal example
Section titled “Minimal example”The smallest valid manifest:
version = "v1alpha3"dockerfile = "Dockerfile"
[claude]plugins = []Complete example
Section titled “Complete example”version = "v1alpha3"dockerfile = "docker/Dockerfile.agent"
[identity]name = "The Architect"
[claude]plugins = [ "code-review@claude-plugins-official", "feature-dev@claude-plugins-official", "superpowers@superpowers-marketplace", "jackin-dev@jackin-marketplace",]
[[claude.marketplaces]]source = "obra/superpowers-marketplace"sparse = ["plugins", ".claude-plugin"]
[[claude.marketplaces]]source = "jackin-project/jackin-marketplace"
[hooks]source = "hooks/source.sh"preflight = "hooks/preflight.sh"
[env.CONTEXT7_API_KEY]interactive = trueskippable = trueprompt = "Context7 API key:"
[env.PROJECT]interactive = trueoptions = ["frontend", "backend", "infra"]prompt = "Select a project to clone:"
[env.BRANCH]interactive = truedepends_on = ["env.PROJECT"]prompt = "Branch for ${env.PROJECT}:"default = "feature/${env.PROJECT}"