Skip to content

Creating a Role

Creating a custom agent lets you define a specialized environment for a specific type of work. A Rust agent includes the Rust toolchain. A Python data science agent includes Jupyter, pandas, and scipy. You define the tools — jackin’ handles the rest.

  1. Create the role scaffold

    Terminal window
    jackin role create ChainArgos/Rustacean "$HOME/Projects"
    cd "$HOME/Projects/chainargos/jackin-rustacean"

    The generated repository includes a minimal manifest, Dockerfile, README, ignore files, and a validation workflow. Jackin lowercases the selector, so ChainArgos/Rustacean becomes chainargos/rustacean.

  2. Edit the Dockerfile

    Start from the generated file and add the tools your role needs:

    FROM projectjackin/construct:trixie
    # Install Rust via mise
    RUN mise install rust@stable && mise use --global rust@stable
    # Install additional Rust tools
    RUN cargo install cargo-nextest cargo-watch
  3. Validate locally

    Terminal window
    jackin role validate .
  4. Create a GitHub repository and push

    Terminal window
    git init
    git branch -M main
    git add -A
    git commit -m "Initial role setup"
    gh repo create chainargos/jackin-rustacean --source=. --remote=origin --push
  5. Load your agent

    Terminal window
    jackin load chainargos/rustacean . --debug
  6. Migrate when the manifest schema changes

    Terminal window
    jackin role migrate .

Use jackin role migrate on your own desktop when you are updating a checked-out role repo. CI workflows should keep using the generated validation workflow and the standalone jackin-validate binary instead of the full Jackin operator CLI.

Your final stage must start from the construct image:

FROM projectjackin/construct:trixie

Use earlier stages for compilation or asset preparation:

# Build stage — use any base image
FROM rust:1.95.0-slim AS builder
WORKDIR /build
COPY tools/ .
RUN cargo build --release
# Final stage — must use the construct
FROM projectjackin/construct:trixie
COPY --from=builder /build/target/release/my-tool /usr/local/bin/

The construct ships with mise — a polyglot version manager. Use it to install languages:

Node.js
RUN mise install node@22 && mise use --global node@22
# Python
RUN mise install python@3.12 && mise use --global python@3.12
# Go
RUN mise install go@1.23 && mise use --global go@1.23
# Multiple languages
RUN mise install node@22 python@3.12 go@1.23 && \
mise use --global node@22 python@3.12 go@1.23

The construct switches to the agent user. Your Dockerfile commands run as that user. If you need root access:

USER root
RUN apt-get update && apt-get install -y some-package
USER agent

Role repos can declare Claude Code plugins to install. Add them to the [claude] section in your manifest:

jackin.role.toml
version = "v1alpha3"
dockerfile = "Dockerfile"
[claude]
model = "sonnet"
plugins = ["code-review@claude-plugins-official"]
[identity]
name = "My Agent"

These plugin IDs are written into the agent’s runtime state and installed automatically when the container starts.

If you need plugins from a custom marketplace, declare the marketplace separately and keep plugin IDs fully qualified:

jackin.role.toml
version = "v1alpha3"
dockerfile = "Dockerfile"
[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]
name = "My Agent"

jackin adds each declared marketplace at container startup, then installs each plugin ID exactly as written.

An role can support more than one agent while keeping one shared Dockerfile:

jackin.role.toml
version = "v1alpha3"
dockerfile = "Dockerfile"
agents = ["claude", "codex", "amp", "opencode"]
[claude]
plugins = ["code-review@claude-plugins-official"]
[codex]
model = "gpt-5"
[amp]
[identity]
name = "My Agent"

The workspace chooses the default agent:

Terminal window
jackin workspace create my-app --workdir ~/Projects/my-app --default-agent amp

You can override it for one launch:

Terminal window
jackin load my-agent my-app --agent claude

The fastest way to test is to load it:

Terminal window
jackin load your-agent-name --rebuild

The --rebuild flag forces a fresh image build, picking up Dockerfile changes and newer agent CLI downloads.

Use --debug to see raw Docker output if something goes wrong:

Terminal window
jackin load your-agent-name --rebuild --debug

Testing a branch (your own work or someone else’s PR)

Section titled “Testing a branch (your own work or someone else’s PR)”

When you have changes that are not yet on the default branch — your own feature branch, a fork’s branch you want to try, or a contributor’s pull request you want to verify before approving — pass --role-branch to load that branch instead of the role repo’s default:

Terminal window
# Test your own branch on a role you own
jackin load your-agent-name --role-branch feat/some-change --rebuild
# Test someone else's PR branch (their fork must be the role's
# configured remote, or you opened the PR against your fork)
jackin load your-agent-name --role-branch their-feature --rebuild

--role-branch always triggers an interactive branch trust gate, even when the role itself is already trusted (trusted = true in your config). The reason is in Security Model → Branch trust gate: a trusted = true flag records that you reviewed the role’s default branch, and an unmerged branch may contain a Dockerfile or scripts that would run during the image build but were never reviewed. The prompt asks you to confirm you have read the diff before continuing, and it cannot be skipped — there is no --force-branch and non-interactive shells fail rather than defaulting to yes.

The recommended flow when reviewing someone else’s role-repo PR:

  1. Fetch the PR branch into a checkout of the role repo and read the diff yourself, especially Dockerfile, any runtime hook scripts, and the jackin.role.toml manifest.
  2. Run the load command above with --role-branch <PR-branch-name> and --rebuild.
  3. When the trust prompt appears, confirm only after the diff review is complete.
  4. Inside the container, exercise the change — install the toolchain the PR claims to add, run a representative project, etc.
  5. jackin eject your-agent-name when finished. The branch’s cached checkout is kept around so the next --role-branch load of the same branch is fast; jackin purge for that container clears it.

If you do not have write access to the upstream role repo, the standard fork-and-PR flow works without any extra jackin’ configuration. On your own fork:

  1. Push the branch to your fork.
  2. Point jackin’ at your fork as the role’s source. The cleanest way is to give the role a namespaced selector that matches your fork (for example your-org/your-role resolving to https://github.com/your-org/jackin-your-role.git); jackin’ resolves the source from the selector. If you want the role’s short name (your-role) to load from your fork instead of the upstream, use jackin config trust grant <selector> and any selector-source helpers your jackin’ release exposes — see the Configuration File internals reference if you need the on-disk schema.
  3. Run jackin load <role> --role-branch <branch> --rebuild.
  4. When the change is ready, open a PR against the upstream role repo from the same fork branch. Reviewers can then verify it against their own checkout using the same --role-branch flow.
AgentPurposeRepository
Agent SmithDefault general-purpose agentjackin-project/jackin-agent-smith
The ArchitectRust development (used for jackin’ itself)jackin-project/jackin-the-architect

These repos are good starting points when creating your own agent. Agent Smith is intentionally minimal, while The Architect shows a more involved setup with Rust tooling and build dependencies.