Harness-Specific Settings
This document describes how to configure individual LLM tools and harnesses inside a Scion agent.
Purpose
Section titled “Purpose”While Scion manages the orchestration and execution of containers, the tools running inside those containers (like the Gemini CLI or Claude Code) often have their own configuration systems.
Locations
Section titled “Locations”Each agent has a dedicated “Home” directory that is mounted into the container. Harness-specific settings are typically found in a hidden subdirectory:
- Gemini:
/home/gemini/.gemini/settings.json - Claude:
/home/claude/.claude.json(or similar) - Opencode:
/home/scion/.config/opencode/opencode.json(opencode 1.x does not read the old.opencode.jsonname)
Seeding from Harness-Configs & Templates
Section titled “Seeding from Harness-Configs & Templates”When an agent is created, Scion composes its home directory by layering files from multiple sources:
- Harness-Config: Base settings for the specific LLM tool (from
~/.scion/harness-configs/<name>/home/, or another harness-config location). - Template: Role-specific prompts and configuration (from
.scion/templates/<name>/home/). - Common Files: Shared dotfiles like
.tmux.confand.zshrc.
This multi-layered approach allows you to define a “base” Gemini configuration once, and then overlay different “roles” (like Code Reviewer or Security Auditor) on top of it.
When harness-config changes reach an agent
Section titled “When harness-config changes reach an agent”A harness-config has two parts that reach an agent at different times:
home/files (for examplehome/.claude/settings.json, which registers the Claude Code hooks) are copied into the agent home only when the agent is provisioned: when it is created, and when it is reincarnated. Starting an existing agent reuses its agent home and does not copy them again.- The provisioning bundle (
config.yamland the provisioner scripts) is cleared and staged again from the harness-config on every start.
So after you change a harness-config’s home/ files, an existing agent keeps its old copies through stops and starts. Reincarnate or recreate the agent to pick up the change.
Where the start reads the harness-config from depends on the mode:
- Workstation: a local agent uses the harness-config on disk (template-bundled, then project, then global). When the workstation server starts, it syncs the global harness-configs (
~/.scion/harness-configs/) to the Hub: it imports each one the Hub does not have yet (except a built-in that was deleted on the Hub) and re-uploads each one whose content hash changed. - Hosted: the Hub stores each harness-config as a record plus its files in storage, written by
scion harness-config sync(aliaspush) or re-imported byscion harness-config update. When the Runtime Broker creates, starts or restarts an agent, it fetches the Hub’s current version of the agent’s harness-config (cached on the broker by content hash), not the version the agent was created with. A start still keeps the agent’s existinghome/files.
Managing Harness-Configs
Section titled “Managing Harness-Configs”A harness-config is a named, versioned bundle that defines a harness: its config.yaml
(harness type, container image, capabilities, auth methods, MCP mapping) plus supporting files
(the home/ directory, provision.py, dialect.yaml, capture_auth.py, and the shared
scion_harness.py library). Bundles live in ~/.scion/harness-configs/<name>/ (global) or
.scion/harness-configs/<name>/ (project-level); project-level configs override global ones with
the same name. A template can also bundle harness-configs in its own harness-configs/<name>/
directories, and these take precedence over both project and global configs of the same name (any
directory that fails to load is skipped and the next one is tried). When a Hub dispatch names a
Hub harness-config, the Hub’s copy is used instead of any of these directories; if the Runtime
Broker cannot fetch it, the start fails rather than falling back to them.
Since all harnesses are now provisioned via container-script (see
Supported Agent Harnesses), harness-configs are the unit you
install, refresh, publish, and delete. Manage them with the scion harness-config command group
(alias hc) and the Hub’s web UI.
The name field
Section titled “The name field”A harness-config’s identifier defaults to its directory name (e.g. claude,
gemini-experimental). A config.yaml may set an explicit name: field to override this; path
separators (., .., /, \) are rejected. On the Hub, each config also has a URL-safe
slug, and lookups match either the name or the slug.
Installing from a source
Section titled “Installing from a source”Add a new harness-config from a URL, local path, or archive:
# From a GitHub repo (full URL or shorthand), a local directory, an rclone URI, or an archivescion harness-config install github.com/myorg/scion-harnesses/tree/main/hermesscion harness-config install file:///path/to/my-harness--name <name>: override the derived config name. Without it, the name is thenamefield in the source’sconfig.yaml, then itsharnessfield, then the source directory name.--force: overwrite an existing config with the same name in the target scope (a local directory, or a Hub config in the same scope). Without it,installrefuses to replace an existing config.
When a Hub is available, install registers the config on the Hub (project scope by default,
or global with --global); otherwise it installs it locally. The output shows the Hub scope the
config was created or updated in. A project-scoped config and a global config with the same name
are separate configs, so check the printed scope to see which one you changed. When the Hub bootstraps or imports a harness-config directory, it opens the collected directory as a root, reads only the regular files recorded during collection without following symlinks or replaced directories, verifies each file’s size and SHA-256 digest on every read (config.yaml and uploaded files), and requires any config whose name was taken from config.yaml during discovery to carry that same name in the collected config.yaml.
Source-URL tracking and “Refresh from Source”
Section titled “Source-URL tracking and “Refresh from Source””When a config is imported from a URL through the Hub (the import box in the web UI, or
POST /api/v1/resources/import), or installed from a remote source (a URL or rclone URI) with
scion harness-config install in whichever scope it installs to, the Hub records the
sourceUrl it came from as metadata. A config installed from a local path or file:// URL
records no source URL; to attach one, run scion harness-config update <name> --url <url>. You
can re-import (refresh) a config from its stored source:
# Re-import a single config from its stored source URLscion harness-config update <name>
# Override / update the stored source URLscion harness-config update <name> --url github.com/myorg/scion-harnesses/tree/main/hermes
# Re-import every config that has a stored source URLscion harness-config update --all--url and --all are mutually exclusive. A config with no stored sourceUrl is skipped by
--all; for a single config, pass --url to supply one. This CLI command is the equivalent of
the “Refresh from Source” button on the harness-config detail page, as well as the “Refresh All from Source” button on the harness-configs list page in the Web UI (which triggers parallel reimport of all tracked configs with per-row progress indicators). Both require a Hub connection.
Image status
Section titled “Image status”The Hub tracks the availability of each harness-config’s container image in an imageStatus
field with one of four values:
imageStatus |
Meaning |
|---|---|
unknown |
Not yet checked (or a bare local-only image name that was not found locally). |
valid |
The image was found — either in the local daemon or in the remote registry. |
invalid |
The remote registry returned “not found” (HTTP 404) for the image. |
error |
The image check failed (registry unreachable, unauthorized, etc.). |
The check probes the local container daemon first, then the remote registry. The Hub re-checks lazily when the status is stale (older than ~5 minutes) and after image pulls; you can also force a re-check with the Re-check button on the detail page.
Publishing and pulling (Hub)
Section titled “Publishing and pulling (Hub)”Share a locally-authored config with your Hub, or pull one down:
scion harness-config sync <name> # upload local dir to the Hub (alias: push)scion harness-config sync <name> --name <hub-name> # publish under a different Hub namescion harness-config pull <name> # download from the Hub to the global dirscion harness-config pull <name> --to <path>sync/push upload only changed files (compared by content hash) and mirror local deletions: a
file removed from the local directory is removed from the Hub, and the removed paths are printed.
Backup and temp files (*.bak.<timestamp>, .*.tmp-*) are never uploaded, so any left on the Hub
are removed the same way. A directory with no files left to sync is refused. pull verifies each
file’s hash before writing. Like install, sync/push target the current project’s Hub scope by
default, or the global scope with --global (which needs hub admin rights). --global also reads
the config from the global directory (~/.scion/harness-configs), so to publish a config globally
it must live there. They create the config in that scope or update an existing one of the same name,
and print the scope they used.
Listing, inspecting, and resetting
Section titled “Listing, inspecting, and resetting”scion harness-config list # local configs (add --hub to merge in Hub configs)scion harness-config show <name> # details (local path/image, or Hub ID/status/source URL)scion harness-config reset <name> # restore the global dir to embedded defaultsscion harness-config upgrade [name] # add missing support files / metadata, refresh provisioner scriptsreset overwrites a config with the binary’s embedded defaults. upgrade does not clobber your
config values: it adds newly-required support files and merges missing metadata into
config.yaml (use --dry-run to preview, --activate-script to switch a config to
container-script provisioning, --force to override). It does refresh provisioner scripts, as
described below. With no name, upgrade processes every config in the global directory.
The provisioner scripts provision.py, scion_harness.py, and capture_auth.py belong to the
harness bundle, not to you:
- Non-force seeding (
scion init --machine, hosted-modescion serverstart, and Hub system-init) writes only to the bundled config directories, such asharness-configs/claude. It replaces these scripts on every run, the same way it treatsconfig.yaml. upgradereplaces them with the bundled copy when they differ, but only in a bundled config: one whose directory name matches its harness. It backs up each script as<file>.bak.<timestamp>first and reports it as arefresh_fileaction.
Either way, provisioner fixes reach nodes that already have the config, and your other files
are kept. Non-force seeding and non-force upgrade never touch two kinds of script:
- Scripts in a custom-named config, such as one installed with
harness-config install --name my-claude. - Scripts that are symlinks. These are treated as user-managed and skipped (
upgradereports askip_fileaction).
The force paths still replace the whole config, and they write through symlinks. These are
harness-config reset, upgrade --force, and workstation-mode scion server start.
To customize a provisioner, publish it as your own harness-config under a different name. Do not edit the bundled scripts in place.
Changing a harness config affects new agents only
Section titled “Changing a harness config affects new agents only”By design, an agent keeps the harness setup it was created with. Its home directory is composed
once, when the agent is created (see Seeding from Harness-Configs &
Templates). That directory holds the harness-config’s
home/ dotfiles, such as Claude’s .claude/settings.json with its hook registrations, and the
agent’s skills. The skills come from the template chain (including skill references it declares)
and the platform skills, not from the harness-config. Stopping and starting the agent does not
copy them again. For a harness that uses container-script provisioning, each start re-stages the
provisioner bundle (for example config.yaml, provision.py, scion_harness.py,
dialect.yaml and the capture-auth assets) and the hook wrapper script, and then runs
provision.py. Changes to home/ dotfiles, including hook registrations kept there, therefore
reach new agents only. Harnesses whose provisioner writes its hook configuration at start (for
example Copilot, Antigravity and Grok Build) pick up changes to the provisioner on the agent’s
next start.
To give an existing agent the new setup:
- Update the harness-config.
- Agents use the Hub’s copy.
scion harness-config upgrade <name>changes only the local copy in~/.scion/harness-configs/<name>/(add--forceto take every new default file, discarding your edits to them), not the Hub’s copy. The Hub refreshes its copy of a built-in harness-config from the binary’s defaults when it restarts. In Workstation mode, that restart also resets the bundled configs in~/.scion/harness-configs/to the defaults. Publish a harness-config you changed yourself withscion harness-config sync <name>.
- Agents use the Hub’s copy.
- Rebuild the agent’s home.
- Run
scion reincarnate <agent>. It keeps the agent’s identity and workspace and copies the harness-config’s home files, the skills from the template chain (including skill references it declares) and the platform skills in again. It overlays files: files that the new version no longer has stay in place. - In a worktree-per-agent project (where reincarnate is not available):
save the agent’s work, then delete the agent with
scion deleteand start it again.
- Run
Deleting
Section titled “Deleting”scion harness-config delete <name>The CLI delete removes the config from the Hub (matched by name or slug); it does not delete
your local on-disk files. In the web UI, the delete dialog offers an “Also delete stored files”
checkbox to remove the Hub-stored files as well. To remove a local directory, delete it from the
filesystem or reinstall with --force.
Recovering a deleted built-in
Section titled “Recovering a deleted built-in”A built-in harness config (such as claude) that you delete from the Hub stays deleted. A Hub
restart or upgrade does not re-create it, and the same applies to built-ins left unselected in
the setup wizard. To bring one back, re-import it as a global harness config from its
canonical URL:
https://github.com/GoogleCloudPlatform/scion/harnesses/<name>In the web UI, open the Hub’s Settings page, go to the Harness Configs tab, and import
that URL. From the CLI, run
scion harness-config install --global https://github.com/GoogleCloudPlatform/scion/harnesses/<name>.
With the API, send a global import (Hub admins only):
curl -X POST "$HUB_ENDPOINT/api/v1/resources/import" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"kind": "harness-config", "scope": "global", "sourceUrl": "https://github.com/GoogleCloudPlatform/scion/harnesses/claude"}'The URL fetches the repository’s main branch. A config re-imported this way counts as a built-in again: at its next start the Hub brings it to the version bundled in the Hub binary, and later upgrades keep updating it.
Use exactly that URL form. On a hosted Hub, a copy from any other URL for the same config,
such as a /tree/<ref>/ URL
(https://github.com/GoogleCloudPlatform/scion/tree/main/harnesses/claude), or from a local path,
is usable but user-managed, so it never receives the content bundled with later releases.
This applies whether you import or install it.
To turn such a copy back into an updating built-in, re-import the canonical URL over it, either
through the web UI or API import above, with
scion harness-config install --global --force <canonical URL>, or with
scion harness-config update <name> --url https://github.com/GoogleCloudPlatform/scion/harnesses/<name>.
This updates the existing config in place and records the canonical source URL, so later
upgrades update it again. You do not need to delete it first.
Workstation Hubs re-sync built-ins from ~/.scion/harness-configs at every start, so a
re-imported config is updated whichever way you imported it.
The same applies to the built-in default template: you can re-import it as a global template
("kind": "template", or the Templates tab) from
https://github.com/GoogleCloudPlatform/scion/resources/templates/default. On a hosted Hub the
result is always user-managed and does not receive later bundled updates. Workstation Hubs keep
it updated.
The Hub must be able to reach GitHub to fetch the URL. An air-gapped Hub needs another source it
can reach, for example a mirror given as an rclone URI in sourceUrl (:gcs:bucket/path), or a
project workspace import (POST /api/v1/projects/{id}/import-harness-configs with
workspacePath), which creates a project-scoped config. On a hosted Hub, both give a
user-managed copy.
Configuration (config.yaml)
Section titled “Configuration (config.yaml)”The config.yaml file at the root of a harness-config bundle defines its runtime parameters. Beyond basic fields like image, harness type, and model_aliases, it includes capabilities and launch configurations.
image and image_pull_policy (the latter is Kubernetes-only; values Always, IfNotPresent,
Never) can also be set — and, since ptone/scion#2156, overridden by an operator — via Hub
settings harness_configs.<name>.image / .image_pull_policy, without editing the bundle. An
explicit image or kubernetes.imagePullPolicy in an agent’s inline config still outranks the
Hub setting, which in turn outranks this file’s own default; a template’s image and
kubernetes are ignored. An explicitly set
profiles.<p>.harness_overrides.<name>.image (and that override’s image_pull_policy) is the exception:
since ptone/scion#1799 it outranks an inline value too, and only the user’s explicit
--image (or explicit pull policy) ranks above it. See Settings
Precedence
for the full chain.
Thinking Level Map (thinking)
Section titled “Thinking Level Map (thinking)”Scion carries the thinking level as a harness-agnostic integer from 0 to 100 (--thinking-level on
scion start, Hub agent defaults, templates). --thinking-level also accepts the shorthands
low (25), medium (50), high (75) and max (100), case-insensitive; they are stored as those
integers. Inside the container it arrives as
SCION_THINKING_LEVEL. A harness that honours it declares a thinking: block in its
config.yaml, next to model_aliases. The block maps level ranges to the harness’s native tier
strings:
thinking: # Ordered by ascending `max`. A level L (0-100, after clamping) maps to the # `value` of the first entry whose `max` >= L. `max` is an inclusive upper # bound. The last entry must have max: 100. levels: - {max: 25, value: low} - {max: 50, value: medium} - {max: 75, value: high} - {max: 100, value: xhigh} # Optional. Used when SCION_THINKING_LEVEL is unset, blank or not an integer. # Omit it to emit nothing, so the harness CLI's own default applies. default: mediumRules (checked when the harness-config is loaded; a violation fails the load):
levelsis required and must have at least one entry.- Each entry has exactly
max(an integer from 0 to 100) andvalue(a non-empty string). maxmust be strictly ascending, and the lastmaxmust be100.default, if present, is a non-empty string. It does not have to be one of thevalues.- No other keys are allowed.
The block travels to the container in the provision manifest. The bundle’s provision.py
resolves it with scion_harness.resolve_thinking(ctx), which owns the only parse rule: the level
is stripped, signs are accepted (-5, +7), and the result is clamped to 0-100. A value that is
not an integer (abc, 1.5) logs a warning and is treated as unset. Writing the resolved value
to the harness’s native setting is up to each provision.py: for example, codex writes
model_reasoning_effort in ~/.codex/config.toml, antigravity passes agy --effort, and claude
sets the CLAUDE_CODE_EFFORT_LEVEL environment variable.
A harness with no thinking: block ignores the thinking level. Currently only codex,
antigravity and claude declare one; see Supported Harnesses for
their tables. If you maintain a customized config.yaml for one of these harnesses, copy the
thinking: block from the bundled file, or run scion harness-config upgrade <name>, which
merges missing top-level keys such as thinking: without overwriting your values. Without the
block, the provisioner writes no thinking setting, so the CLI’s own default applies. codex logs
a warning on every start; antigravity and claude log one when a thinking level was requested.
Readiness (ready)
Section titled “Readiness (ready)”A newly started harness needs a moment before it reads its terminal input. A message sent to the
agent in that window is held and pasted once the harness is ready. The ready block says how
“ready” is decided:
ready: strategy: hook # hook | delay (default delay) settle: 1.5s # 0s to 60s; default 8s for delay, 1.5s for hook timeout: 90s # hook only, 10s to 10m; default 90sThere is one rule: the harness is ready at signal time + settle.
hook: the signal is a ready marker that the harness’s first session-start hook (or any later hook event) writes for the current run. A session-start from/clearor/compactdoes not count. Usehookonly when the harness fires a hook at startup, before any user input. If no marker appears, the agent is reported as workingtimeoutafter the launch, and a held message is pasted anyway at mosttimeoutafter its delivery began.delay: the signal is the creation of the agent’s tmux session. Use it when the harness has no early hook.timeoutis not allowed withdelay.
Rules (checked when the harness-config is loaded; a violation fails the load): the units are
those of Go durations (ns, us, ms, s, m, h), a zero must be written 0s, and no other
keys are allowed.
How the value travels. The broker resolves the harness-config, including project, template
and Hub overrides (an override replaces the whole ready block), and passes the resolved spec to
the container in SCION_HARNESS_READY. sciontool init records it in the container’s readiness
file, and both readers use those values: sciontool init, which reports the agent as working
when it is ready, and the broker, which waits before pasting a held message. A
SCION_HARNESS_READY value in a caller’s env cannot override it (a template cannot set SCION_* keys).
The built-in bundles use hook (settle 1.5s, timeout 90s) for claude, gemini-cli and
grok-build, and delay 8s for every other harness. Copilot uses delay: its sessionStart
hook fires only when the first prompt is submitted, not at startup. A harness-config without
the block (for example one installed before the block existed) uses the block of the built-in
bundle for its harness type, so an older claude config keeps hook; a harness with no
built-in bundle uses delay 8s.
A harness-config without a provisioner that declares a ready or delivery block, in
config.yaml or in an override, is treated like a harness-config with command or
config_dir: its ready block is used, its instructions_file, system_prompt_file and
system_prompt_mode (or the declarative defaults for instructions and system prompts) apply when
the agent is provisioned, and the harness reports its harness type as its name instead of
generic. A ready block taken from the built-in bundle does not have this effect. Message
delivery and interrupts still use the built-in bundle for the harness type (the defaults for a
type with no bundle), so a custom delivery block, interrupt_key and interrupt_sequence do
not yet change how messages are submitted or interrupted.
Version skew.
- Upgrade brokers with or before the Hub. A broker older than the
readyblock rejects a harness-config that has one, so agent launch fails on that broker. - A broker older than this reader, starting a newer image, does not pass the spec. The
container then uses
delay8s for every harness (logged at Info), so hook harnesses lose marker gating until the broker is upgraded. - A newer broker with an older image finds no spec in the readiness file and uses the built-in timings, as before.
Command Execution (command)
Section titled “Command Execution (command)”The command block defines how the agent’s primary LLM tool is invoked.
base: A list of strings forming the base command to run.The tool specified in
command.basemust be an interactive application that keeps running and accepts input via the terminal. The Scion runtime manages terminal sessions via a PTY and requires the process to stay alive. Tools that execute a single prompt and exit immediately will cause the agent to terminate prematurely.task_flag: CLI flag used to pass the task (e.g.,--prompt).resume_flag: CLI flag to resume a previous session (e.g.,--session).system_prompt_flag: CLI flag for the system prompt.
Environment Templates (env_template)
Section titled “Environment Templates (env_template)”The env_template block defines a map of environment variables to inject into the container, with support for Go template variable substitution.
Host-Side Evaluation
Templates are evaluated on the host (broker) before the container starts. They have access to host paths, not container paths.
| Variable | Description |
|---|---|
{{ .AgentName }} |
The name (slug) of the agent being started. |
{{ .AgentHome }} |
The absolute path to the agent’s home directory on the host machine. |
Use env_template sparingly to pass host-side paths to the container (e.g., passing a socket path that is bind-mounted). Do not use it to reference paths inside the container, as they will evaluate to the host path instead. Use the standard env map for static or container-side environment variables.
Key Concepts
Section titled “Key Concepts”- Tools: Allowlists of local or remote functions the LLM is permitted to call.
- Profiles: Harness-level profiles (distinct from Scion profiles) that control model parameters.
- Credentials: How API keys are injected and stored within the harness-specific configuration. Auth type selection uses universal Scion types (
api-key,oauth-token,auth-file,vertex-ai) set viaauth_selectedTypein the settings profile. Scion translates these to harness-native values during provisioning (e.g.,api-keybecomesgemini-api-keyin Gemini’ssettings.json). See Agent Credentials for the full credential pipeline.