Skip to content

Harness Field Reference ​

This is a living document. It is the authoritative reference for harness field classifications, merge rules, and the ForgeConfig struct. Update this document whenever you add a new field to Harness or ForgeConfig, move a field between classification tiers, or change merge semantics.

The architectural decisions behind these rules are recorded in ADR-0045 (forge-portable schema) and ADR-0088 (CEL-guarded overlays). Those ADRs are point-in-time records; this document reflects the current state.

Decided, not yet implemented (2026-10-01):ADR 0112 will let overlays set any field except base, trigger, overlays, slug, role and forge, with guarded fields limited to trusted inputs. The tables below change when it is implemented.

Field classification ​

Harness fields are classified into two tiers based on whether they can be overridden inside forge.<platform> blocks or overlays: entries.

Fields that can appear at both levels ​

These fields can appear at the harness top level (as defaults) and inside ForgeConfig (forge blocks or overlay entries):

FieldRationale
pre_scriptScripts often call forge-specific CLIs (gh, glab)
post_scriptPush, PR/MR creation is forge-specific
skillsSome skills wrap forge-specific APIs
runner_envToken names and event URLs differ per forge
validation_loopValidation scripts may call forge-specific tools
policySandbox policies may need forge-specific filesystem or process rules; network access is managed via providers (ADR-0065) but non-network policy sections can still differ per forge
providersProviders may need forge-specific entries (e.g., different API endpoints per platform); concatenated (top-level + forge)
openshellOpenShell profiles may need forge-specific configuration; profiles concatenated (top-level + forge)
host_filesHost files may need forge-specific entries (e.g., different credential files per platform); deduplicated by dest path (child wins)
envEnv config (runner and sandbox sub-maps) may need forge-specific entries (e.g., different token names per forge); sub-maps merged independently, forge/child keys win (ADR-0055)

Fields that stay at top level only ​

These fields are platform-neutral and cannot be overridden per-forge or per-overlay:

FieldRationale
agentAgent definitions are forge-agnostic
modelModel selection is independent of forge
imageContainer images are platform-neutral
api_serversREST proxies abstract forge details
pluginsPlugin directories are forge-agnostic; each entry is a local path or a pinned URL and keeps its own env/pi options (ADR-0038, ADR-0094). Top level only — not a ForgeConfig field, so it is not settable under forge: or overlays: (a plugins: key there is ignored, not an error)
agent_inputAgent prompt input is forge-agnostic
timeout_minutesTimeouts are operational, not forge-specific
sandbox_timeout_secondsSandbox-level timeout, not forge-specific
securitySecurity scanning is forge-agnostic
allowed_remote_resourcesURL allowlist for resource fetching (ADR 0038)
descriptionDocumentation, no runtime effect
roleAgent identity is forge-agnostic
slugKept top-level; per-forge slug differences handled via base composition
baseComposition is a structural concern, not forge-specific
docDocumentation path, no runtime effect
effortEffort level is operational, not forge-specific
readonly_repoRepo access mode is forge-agnostic
allow_runtime_fetchRuntime fetch opt-in is forge-agnostic
max_runtime_fetchesFetch cap is operational, not forge-specific
triggerCEL trigger expression is evaluated against normalized events, not forge-specific (ADR-0061)
privilege_levelsMint privilege per run-stage is forge-agnostic (ADR-0073). Top level only — not a ForgeConfig field
schema_versionSchema contract version, forge-agnostic (ADR-0127); absent = version 1. Planned, not yet implemented
preflight_checkSingle host-dependency gate run once before sandbox creation for pre_script/post_script/validation_loop; a literal sh -c command, not a script path (ADR-0128, ADR-0129). Planned, not yet implemented

Semantic types (ADR 0127) ​

Each harness field has a semantic type that governs how fullsend interprets its value (ADR 0127). The following groups cover the top-level fields and their nested values; forge.<platform> and overlays[] inherit the types of their shared ForgeConfig fields. A map or list container is structural; its keys and elements use the types listed below. Ordinary strings, numbers, booleans, and map values not explicitly identified as paths, commands, or resource references are scalar values, not shell commands.

Semantic typeMeaningFields
inline commandExecuted via sh -c on the host, not resource-resolvedvalidation_loop.preflight_check; top-level preflight_check (planned)
runtime local pathPath to a file or directory in the local configuration, not a commandpre_script, post_script, validation_loop.script, validation_loop.schema, agent_input (directory), host_files[].src (host path, optional ${VAR} expansion), api_servers[].script (path resolved, server startup planned)
resource referenceLocal path or pinned URL resolved/fetched as applicableagent, base, policy, skills[].source, plugins[].path, openshell.profiles[]; providers[] has additional identifier semantics below
skill overrideKey is a path within the skill; value is a local file, pinned URL, or null to remove the fileskills[].overrides[<path>]
source metadata pathDescribes a path in the source repository; not runtime-resolved or delivereddoc
destination pathNames a location inside the sandbox, not a host file to resolvehost_files[].dest
structuralContains nested fields, lists, maps, or conditionsforge, overlays[], validation_loop, host_files[], api_servers[], skills[], plugins[], plugins[].pi, openshell, security and its nested scanner/hook/escalation/trace blocks, runner_env, env, env.runner, env.sandbox, privilege_levels, api_servers[].env, plugins[].env, allowed_remote_resources, providers
scalarInterpreted as a configuration value, never as a path solely because it resembles onerole, slug, description, image, model, effort, timeout_minutes, readonly_repo, sandbox_timeout_seconds, allow_runtime_fetch, max_runtime_fetches, trigger (CEL expression), schema_version (planned); validation_loop.max_iterations, validation_loop.feedback_mode, host_files[].expand, host_files[].optional, api_servers[].name, api_servers[].port, api_servers[].env[<key>], plugins[].env[<key>], plugins[].pi.args[], runner_env[<key>], env.runner[<key>], env.sandbox[<key>], privilege_levels[<stage>], allowed_remote_resources[], overlays[].when (CEL expression); all leaf values under security

For local harnesses, relative runtime paths resolve from the .fullsend configuration root (the parent of harness/), not from the YAML file's directory. The same rule applies to local resource references except a local base:, which resolves relative to the child harness YAML's directory. Direct resource URLs use the allowlisted, hash-verified fetch pipeline; a URL base: hash verifies the harness YAML, not the relative files fetched alongside it. Use an immutable commit ref for a URL base whose relative resources will run on the host. For URL base: layers, relative pre_script, post_script, validation_loop.script/schema, host_files[].src (except ${VAR} sources), agent, policy, skills (including overrides), plugins[].path, openshell.profiles[], and path-form providers[] are fetched from the base repository and rewritten to cache paths. An inherited URL-base agent_input is cleared, because it is a directory and is not fetched. doc is source metadata, not a runtime dependency; api_servers[].script resolves as a local path, not a URL-base fetch, and server startup is planned. See ADR 0038 for remote delivery and the current-field reference for implementation status.

providers[] is a union: each entry is either a bare identifier (matching ^[a-zA-Z0-9_-]+$, looked up under providers/) or a fetched resource (a local path or a #sha256= URL). A skills[] entry can be a source string or a single-key map of that source to file overrides; a plugins[] entry can be a path string or a {path, env, pi} map. Scalar security leaf values retain their own validation and defaults; this type table does not override them.

The planned schema_version field (absent = 1 once implemented) will declare this contract; an incompatible field-type change will require a version bump and an update to this table in the same change. Backward-compatible field additions do not require a bump; ADR 0127 leaves other breaking schema changes for a separate versioning policy. Once version-aware loaders are implemented, they will reject malformed or unsupported versions in each raw composition layer before merging; older pinned consumers must be upgraded before harness content using a new version is published to them.

Merge and inheritance rules ​

When a forge block or overlay is merged into the harness top level, each field type follows specific merge semantics. The same rules apply during base: composition (base → child merging).

Two independent precedence axes govern field resolution (see #6798):

  • Specificity (within a layer): Conditional forge/overlay values override same-layer top-level values.
  • Derivation (across layers): Child-layer values override inherited base-layer values. Each base layer's forge and overlay blocks are resolved into top-level fields before merging into the child, so inherited conditional values cannot override the child's explicit settings.
Field typeMerge behaviorNil vs empty
Scalar fieldsForge/child value overrides top-level/base valueAbsent = inherit from top level / base
skillsMerged with deduplication by basename (forge/child overrides top-level/base)Absent (nil) = inherit; skills: [] = empty list merged with base (base entries are returned)
runner_envTop-level/base map merged with forge/child map; forge/child keys winAbsent (nil) = inherit; runner_env: {} = no forge-specific keys (top-level env still inherited)
privilege_levelsTop-level/base map merged with child map; child keys win (not in ForgeConfig, so no forge/overlay override)Absent (nil) = inherit from base; omitted entirely at every layer defaults every run-stage to write
validation_loopField-level merge; forge/child non-zero values win, base/top-level fills gapsAbsent (nil) = inherit from top level / base; validation_loop: {} inherits all fields (zero-value-as-unset). Post-merge Validate() still requires script. There is no way to disable an inherited validation loop.
providersConcatenated (top-level/base + forge/child)Absent (nil) = inherit; providers: [] = no forge-specific additions (top-level providers still apply)
openshellprofiles concatenated (top-level/base + forge/child)Absent (nil) = inherit; empty profiles: [] = no forge-specific additions
host_filesConcatenated (base + child); deduplicated by dest path (child wins)Absent (nil) = inherit
pluginsConcatenated (base + child)Absent (nil) = inherit
api_serversConcatenated (base + child)Absent (nil) = inherit
envSub-maps (runner, sandbox) merged independently; forge/child keys win (ADR-0055)Absent (nil) = inherit
securityChild replaces base entirely (if non-nil)Absent (nil) = inherit
overlaysConcatenated (base + child); all matching entries merged at resolution with later precedence (ADR-0088)Absent (nil) = inherit

ForgeConfig struct ​

ForgeConfig is the shared field payload used by both legacy forge: platform blocks and current overlays: entries (via OverlayEntry's yaml:",inline" embedding). The type name is a legacy artifact from the original forge feature (ADR-0045); it was retained when ADR-0088 introduced overlays to avoid a rename-heavy migration. Both mechanisms use mergeForgeConfig to apply their fields onto harness top-level values.

go
// ForgeConfig holds platform-specific harness configuration.
// This is purely declarative YAML config — it selects which
// scripts, skills, host files, and env vars to use per platform. It is
// distinct from the forge.Client interface (internal/forge/),
// which is the runtime abstraction for forge API operations.
type ForgeConfig struct {
    PreScript      string            `yaml:"pre_script,omitempty"`
    PostScript     string            `yaml:"post_script,omitempty"`
    Policy         string            `yaml:"policy,omitempty"`
    Skills         []SkillEntry      `yaml:"skills,omitempty"`
    Providers      []string          `yaml:"providers,omitempty"`
    OpenShell      *OpenShellConfig  `yaml:"openshell,omitempty"`
    HostFiles      []HostFile        `yaml:"host_files,omitempty"`
    ValidationLoop *ValidationLoop   `yaml:"validation_loop,omitempty"`
    RunnerEnv      map[string]string `yaml:"runner_env,omitempty"`
    Env            *EnvConfig        `yaml:"env,omitempty"`
}

Current resolution pipeline ​

The current forge resolution pipeline is:

Unmarshal → validateForge → ResolveForge(platform) → Validate

Overlay resolution (ADR-0088) ​

overlays: is the successor to deprecated forge: blocks. Each overlay entry has a when: CEL expression and the same override fields as ForgeConfig. All entries whose when evaluates to true are merged in order, with later matches taking precedence over earlier matches.

Resolution pipeline ​

Unmarshal → validateForge → validateOverlays →
ResolveForge(platform) → ResolveOverlays(event, forgePlatform, config) → Validate

When event is nil (CLI flows without event context, such as fullsend lock or fullsend run when no event can be recovered), ResolveOverlays substitutes an empty map so overlays conditioned on runtime.forge or config can still evaluate and match. Overlays that reference event fields should use has() to guard field access (e.g., has(event.source) && event.source.system == "jira").

CEL environment ​

Overlay when expressions are evaluated with:

VariableTypeSource
eventnormevent.EventThe triggering event — fields like source.system, entity.kind, transition.kind
runtime.forgestringEffective forge platform (precedence: CLI flag > config.forge > CI env vars)
configmap[string]anyFull per-repo config from config.yaml

Mutual exclusion ​

forge: and overlays: must not coexist in the same harness (post-merge). forge: is deprecated; new harnesses should use overlays: instead.

  • ADR-0045: Forge-portable harness schema — original architectural decision (Superseded by ADR-0088)
  • ADR-0088: CEL-guarded overlays — current overlay mechanism
  • ADR-0127: Harness schema versioning and field semantic types
  • Harness Composition: Merge function checklist (step 6 references this document)
  • Issue #5579: Harness field integration pipeline (complementary checklist)