Every task runs against a repo: the git repository it clones, works in, and (for the GitHub workflows) opens a PR against. A repo is more than a git URL, though — it also carries the per-repo configuration the runner needs to build and launch a task container: which secrets to inject, what to add to the container image, which container privileges to grant, and which workflows the repo offers.
Crucially, a repo record holds mostly references, never the sensitive values themselves. The secrets, image layer, and hook script live as files on each runner host; the repo only names them. That keeps secrets out of the database, artifacts, and image layers, and lets a remote runner resolve each reference against its own host — so the value stays host-agnostic and never crosses the wire.
A repo's identity is its id; name is the human label. Every task carries a repo_id
pointing at one.
The Repo model (src/panopticon/core/models.py) has these fields:
| Field | Meaning |
|---|---|
id |
Stable identifier; a task references it as repo_id. |
name |
Human-readable label. |
git_url |
Git remote the per-task clone is cloned from and origin points at. |
default_base |
Default base branch for new tasks (defaults to main). |
env_file |
Name (relative to the secrets dir) of an env-file of secrets, injected at spawn via --env-file. See Secrets. |
image_layer_file |
Name (relative to the layers dir) of a Dockerfile fragment — the repo tier of the composed image. See Container image. |
capabilities |
Opt-in map for elevated container privileges (e.g. docker_in_docker). See Capabilities. |
hook_file |
Name (relative to the hooks dir) of a script the runner runs before docker run. See Host hook. |
enabled_workflows / disabled_workflows |
Filter which workflows the repo offers. See Workflow visibility. |
The three reference fields (env_file, image_layer_file, hook_file) are all optional — a
minimal repo is just an id, name, and git_url.
env_file is a name relative to the secrets dir — $PANOPTICON_CONFIG/secrets, default
~/.config/panopticon/secrets/ — naming a file of KEY=value lines. At spawn the runner
resolves the name against its own host's secrets dir and injects the file with
docker run --env-file, so the task container gets exactly its repo's secrets and nothing
else.
Because only the name is stored, secrets stay out of the database, artifacts, and image
layers, and a remote runner resolves the same name against its own host — the file's content
never crosses the wire. The resolver refuses any name that escapes the secrets dir (a ..
segment or an absolute path).
The env-file's most important entry is the container's claude auth token,
CLAUDE_CODE_OAUTH_TOKEN (plus any ANTHROPIC_API_KEY or GH_TOKEN the tasks need). You don't
have to write it by hand: panopticon quickstart sets a repo's token up at any time, and the
setup-repo workflow is available from the dashboard whenever you need
to (re-)mint it — press g to open the repos modal, highlight the repo, and press s. See
auth.md for the details and the by-hand path.
env_file is validated at create time: POST /repos rejects a reference whose file doesn't
exist under the secrets dir.
image_layer_file is a name relative to the layers dir, naming a Dockerfile fragment (not
inline content) — the repo's own tier of the task image, which the runner composes as
base → workflow → repo and builds at spawn. This is where a repo layers on its toolchain,
for example installing uv and make. The task service serves the fragment over
GET /repos/{id}/image-layer; the layer is optional (declare none and the repo tier is empty),
and secrets are never baked in — they're injected at run time via env_file. See
layers.md for how the layers compose.
capabilities is a JSON opt-in map for elevated container privileges the runner grants at
spawn. The first (and currently only) capability is docker_in_docker: set it and the runner
spawns the container --privileged, gives it a volume for /var/lib/docker, and the entrypoint
starts a nested Docker daemon. It's off by default because it's a trust escalation — a
privileged container is effectively host root — so a repo opts in only when its tasks genuinely
need to run Docker.
hook_file names a script the runner runs on the host after the per-task workspace is prepared
but before docker run — a chance to adjust the checkout before the agent sees it (for example
stripping host-only config files). Like env_file, it is a name relative to the hooks dir
($PANOPTICON_CONFIG/hooks), resolved against each runner's own host. See
hooks.md for what the hook receives and how failures are handled.
enabled_workflows and disabled_workflows filter which workflows the repo offers in the
task-creation picker, on top of each workflow's own opt-in flag. GET /repos/{id}/workflows
returns the filtered list.
When the session service spawns a task, it uses the repo's fields in order:
- Fetch the repo by the task's
repo_id. - Prepare the per-task clone —
git clone --localfrom the host's per-repo cache into a fresh directory mounted read-write at/workspace. - Run
hook_fileon the host, if the repo declares one. - Compose the image — fetch the workflow and repo (
image_layer_file) layers, build base → workflow → repo. docker runthe container with the repo's config:--env-filefromenv_file, the/workspacemount, and--privilegedwhencapabilities.docker_in_dockeris set.
panopticon quickstart registers the repo you run it in and enables the matching workflow. To
add or reconfigure other repos, use the dashboard's repos modal.
Press g to open it. It lists your repos; from there:
n— add a repo.e— edit the highlighted repo.s— run the highlighted repo'ssetup-repotask, which mints itsclaudetoken (seeauth.md).esc— close.
n and e open the repo form, which has two tabs. Space toggles a checkbox; Enter (or
Ctrl+S) saves from any field; Esc cancels.
general — the repo's core fields:
git_url— the git remote. It leads: in create mode, blankidandnameauto-fill from it.id— stable identifier. Editable only when creating; shown read-only when editing.name— human label.default_base— base branch for new tasks (defaults tomain).env_file,image_layer_file,hook_file— the reference fields described above.- privileged docker (docker-in-docker) — a checkbox for the one capability the form edits; it
maps to
capabilities.docker_in_docker. Other capability keys are left untouched on save.
workflows — a checklist of the workflows this repo can offer. This is where you turn on
github-self-reviewed, github-peer-reviewed, and the rest. An opt-in workflow is off until you
check it; an opt-out one is on until you uncheck it.
Repos are also managed over the task service's REST API:
POST /repos— create a repo (validates thatenv_fileexists).GET /repos/GET /repos/{id}— list or fetch.PATCH /repos/{id}— partial update; fields you don't send are preserved.GET /repos/{id}/workflows— the workflows this repo offers.GET /repos/{id}/image-layer— the repo's composed Dockerfile layer.
auth.md— theclaudetoken that lives inenv_file.layers.md— how the base → workflow → repo image layers compose.hooks.md— repo hooks and howhook_fileresolves.workflows/setup-repo.md— host-side utility that mints and places the auth token.