Config Reference
Complete reference for all fields available in .fullsend/config.yaml. For how these fields resolve through layered configuration, see Layered Config Reference. Fleet manifests can declare the same managed-configuration schema (every field below, including runtime and allowed_remote_resources) as defaults.config, github.config, gitlab.config, or per-repository config in repos.yaml (this is not config_base, which is the configuration-preset path written to .fullsend/config.base.yaml); see Repo Management — Managed configuration. For initial setup, see Configuring GitHub or Configuring GitLab.
# ── Schema ───────────────────────────────────────────────────
version: "1" # Schema version (required)
# ── Platform ─────────────────────────────────────────────────
forge: github # Hosting platform: github, gitlab (auto-detected if omitted)
# ── Operations ───────────────────────────────────────────────
kill_switch: false # Emergency stop — disables all agent dispatch when true
keep_history: true # Append previous sticky-comment content as collapsed "Previous run" blocks
# ── Runtime ──────────────────────────────────────────────────
runtime: claude # Default agent runtime: claude, pi, codex, opencode
# ── Roles ────────────────────────────────────────────────────
roles: # Agent roles to install (determines which Apps and credentials are provisioned)
- triage
- coder
- review
- fix
- retro
- prioritize
# ── Agents ───────────────────────────────────────────────────
agents: # Registered agent sources and per-agent tuning
- source: https://example.com/triage.yaml#sha256=abc... # URL (requires #sha256=) or local path
name: triage # Explicit name (derived from source filename if omitted)
ref: main # Branch/tag resolved at adoption; used by `agent update`
enabled: true # Toggle agent on/off without removing the entry
runtime: claude # Per-agent runtime override
model: opus # Per-agent model override (model id or provider/id)
effort: high # Per-agent reasoning effort: low, medium, high, xhigh, max
subagents: # Per-agent sub-agent model map (pi and codex)
default: sonnet # Fallback model for personas without an explicit entry
code-reviewer: opus # Model for a specific persona
# trigger: <CEL expression> # When the built-in review agent runs (review entry only)
# ── Remote resources ─────────────────────────────────────────
allowed_remote_resources: # URL prefixes allowed for remote resources (agents, policies, skills, plugins, profiles, providers, and base composition)
- https://raw.githubusercontent.com/fullsend-ai/fullsend/
- https://raw.githubusercontent.com/fullsend-ai/agents/
# ── Issue tracker ────────────────────────────────────────────
tracker: # Issue tracker. At most one provider block: github, gitlab, or jira
github:
allowed_targets:
orgs: # GitHub orgs agents may create issues in
- my-org
repos: # Specific repos (owner/name) agents may create issues in
- fullsend-ai/fullsend
- fullsend-ai/agents
# gitlab:
# allowed_targets:
# groups: [my-group] # GitLab groups (projects in the group and its subgroups)
# repos: [my-group/sub/project] # Full project paths
# jira:
# base_url: https://example.atlassian.net # Jira site URL (required with any Jira setting below)
# api_url: https://api.atlassian.com # Jira API gateway URL (optional; this is the default)
# project: PROJ # This repository's Jira project key (required with base_url)
# allowed_targets:
# projects: [OTHER] # Jira project keys for cross-project issue creation or moves
# poll:
# enabled: true # Managed poll workflow switch (see the tracker.jira section)
# query: 'labels = "fullsend" ORDER BY updated DESC' # JQL; always scoped to project
# ── Authorization ────────────────────────────────────────────
authorization: # Extra sources of permission for triggering agents
- provider: owners_file # GitHub: grant access from the repo-root Prow OWNERS file, checked before the collaborator API
- provider: jira # Jira (jira-poll): who may trigger agents from Jira events
write: # Fullsend role write
groups: [example-team-developers] # Jira group names (or group IDs)
accounts: ["5f7c012e0b2a4c001f3d9876"] # Jira account IDs
triage: # Fullsend role triage
groups: [example-team]
accounts: []
labels: jira_editors # Label-added events: jira_editors (default), allowlist, project_roles
# ── Status notifications ─────────────────────────────────────
status_notifications:
comment:
start: enabled # Post a comment when an agent starts: enabled (default), disabled
completion: enabled # Post a comment when an agent completes: enabled (default), on_failure, disabled
reaction:
start: disabled # Add an emoji reaction on start: enabled, disabled (default)
completion: disabled # Add an emoji reaction on completion: enabled, on_failure, disabled (default)
# ── Mint ─────────────────────────────────────────────────────
mint_url: https://mint.fullsend.sh # Token mint URL for credential issuance
# ── Inference ────────────────────────────────────────────────
inference:
provider: vertex # Inference backend: vertex
project: my-gcp-project # GCP project ID (must be provided by installer)
region: global # GCP region for inference requests
wif_provider: projects/123/locations/global/workloadIdentityPools/pool/providers/prov # WIF provider resource name
wif_audience: "" # GitLab: agent job ID token audience for an existing provider (default "fullsend")
service_account: "" # GitLab: service account email the agent job impersonates (default none)
openai: # OpenAI Workload Identity Federation (ADR 0092)
audience: "" # OpenAI WIF audience
identity_provider_id: "" # OpenAI WIF identity provider ID
service_account_id: "" # OpenAI WIF service account ID
gateway: # Inference gateway credential route (ADR 0137)
url: "" # Gateway URL, e.g. https://gateway.example.com or https://gateway.example.com/team-a (optional path prefix, no trailing /v1, port 443 only; plain http only for a loopback test host)
audience: "" # OIDC audience the runner requests for the gateway (oidc mode)
auth: "" # Credential mode: oidc (default) or api-key (ADR 0138)
messages_auth_header: "" # Messages-dialect credential header: authorization (default) or x-api-key (api-key mode only, ADR 0142)
models: {} # Inline model list: id -> {api, compat, contextWindow, maxTokens}
models_file: "" # Or: repository path to a pi-inference-gateway config file
# ── Model aliases ────────────────────────────────────────────
models:
aliases: # Override fullsend's pinned model alias table per key
opus: claude-opus-4-6 # Model id or provider/id spec
sonnet: claude-sonnet-5
haiku: claude-haiku-3-5
fable: claude-fable-5-1Field details
Most fields are self-explanatory from the inline comments above. This section expands on fields where additional context helps.
version
Schema version string. Must be "1". Present in every config file.
forge
Hosting platform for the repository (github or gitlab). When omitted, fullsend auto-detects the forge from CI environment variables. Most installations do not need to set this explicitly.
kill_switch
Emergency stop. When set to true, all agent dispatch is disabled for the repository. Uses pointer semantics in the layered config system — nil (omitted) falls through to parent; an explicit false is a local decision that does not fall through.
keep_history
Controls whether sticky comment updates (from post-review, post-comment, and issues post-comment) append the previous comment body as a collapsed "Previous run" <details> block. Default is true (history appended). Set to false when accumulated "Previous run" blocks add unwanted noise — for example, when comments are synced to Jira where <details> does not render as collapsible.
runtime
Default agent runtime for all agents in this repository. Valid values: claude, pi, codex, opencode. opencode is opt-in and limited to read-only agents (triage, prioritize) until the security-hook adapter lands (#7824). Per-agent overrides in the agents: list take precedence. Per-run overrides via --runtime flag or FULLSEND_RUNTIME env var take precedence over both. See Choose a Runtime for runtime comparison.
roles
Agent roles determine which GitHub Apps (and their associated credentials and permissions) are provisioned for the repository. Valid roles: fullsend, triage, coder, review, fix, retro, prioritize, e2e. The fix role reuses the coder GitHub App and PEM, so no separate App or secret is created for it. For each role's GitHub App and purpose, see Custom Agent Identity — Built-in roles.
Mint-only dogfood roles such as scribe can be registered with fullsend mint add-role (and used via remote harness registration) but are not valid values for roles until scaffold and workflow wiring lands. Adding them under roles fails config validation rather than silently no-oping.
In the layered config system, roles uses replace-if-set semantics — an overlay that sets roles replaces the parent list entirely (no union). Omitting the key inherits the parent's roles. See Layered Config Reference.
agents
Registered agent sources and per-agent tuning. Each entry identifies an agent by source URL or local path, with optional overrides for runtime, model, effort, sub-agent models and the trigger.
source— URL (must include#sha256=<64-hex-char>integrity hash) or local path. URL sources must be covered byallowed_remote_resources.name— Explicit name. When omitted, derived from the source filename (e.g.,triage.yaml→triage). Must start with an alphanumeric character and contain only[a-zA-Z0-9_-].ref— Branch or tag resolved when the agent was adopted viafullsend agent add. When present,fullsend agent updatere-resolves against this ref. Empty for SHA-pinned or legacy entries.enabled— Toggle the agent without removing its entry. A suppression-only entry (enabled: false, no source) disables a built-in or parent-layer agent by name.runtime,model,effort— Per-agent overrides. An override-only entry (no source, at least one setting) tunes a built-in agent by name.subagents— Map of persona names to model references. Thedefaultkey sets the fallback for personas without an explicit entry. A~(null) value tombstones an inherited entry. Keys must be lowercase alphanumeric segments joined by hyphens (max 64 chars). On codex, for an agent that delegates, the values must be OpenAI ids and a key must name a registered persona ordefault; see Codex › Child models.trigger— A CEL trigger (a boolean over the normalizedevent). It decides routing for the built-in review, retro, triage and prioritize agents only: on one of those entries it opts the repository into CEL routing for that agent, so the trigger, not the dispatch workflow's built-in rules, decides when the agent's job starts (see Choosing when review runs, Choosing when retro runs and Choosing when triage and prioritize run). It does not change CEL dispatch: the harnesstrigger:keeps deciding when custom agents run and what the Jira poller dispatches. A trigger alone makes a valid override-only entry for a built-in agent. Validated when the agent list is validated: it must compile and return a boolean. Like an invalidmodeloreffort, an invalidtriggermakes every agent run in the repository fail withinvalid agent config, not only built-in routing, so check the expression before you merge the change.
In the layered config system, agents use keyed merge by DerivedName() — see Layered Config Reference.
For agent registration and management, see fullsend agent and Bring Your Own Agent.
allowed_remote_resources
URL prefixes allowed for remote resources in harness files — agent sources (agents: entries with URLs), policies, skills, plugins, profiles, providers, and base: composition. The config-level list acts as a fallback for all URL resolution: a URL is accepted if it matches either the harness-level or the config-level list. Default prefixes cover the fullsend and agents repositories.
In the layered config system, this field uses union-with-deny-all semantics: omitted inherits from parent; explicit empty ([]) denies all remote resources; a non-empty list is unioned with the parent's list. See Layered Config Reference.
tracker
The issue tracker, as a single provider-discriminated mapping. tracker holds at most one provider block — github, gitlab, or jira — and the block that is present selects the provider. It is the default backend for the --tracker flag of fullsend issues commands (distinct from forge: a repository hosted on GitHub may track issues in Jira), and its allowed_targets restrict where agents may create issues. Declare a provider with no extra targets as an empty block (for example tracker: {jira: {}}). When omitted, no provider is selected, --tracker is required on every fullsend issues invocation, and agents cannot create issues outside the repository they run in. Validation rejects a tracker mapping with more than one provider block. See Jira Integration for cross-platform setup.
tracker.github.allowed_targets.orgs— GitHub organizations. All repos in these orgs are allowed.tracker.github.allowed_targets.repos— Specific repos in exactlyowner/nameformat.tracker.gitlab.allowed_targets.groups— GitLab groups (full group path). Projects in the group and its subgroups are allowed.tracker.gitlab.allowed_targets.repos— Specific projects by full project path (for examplegroup/subgroup/project).tracker.jira.allowed_targets.projects— Jira project keys (an uppercase letter followed by uppercase letters, digits, or underscores).
tracker.jira connection and polling settings
tracker.jira also holds the Jira site, project, and poller settings. The mapping can be set in .fullsend/config.yaml or in a repository's managed config: block in repos.yaml:
tracker:
jira:
base_url: https://example.atlassian.net
api_url: https://api.atlassian.com
project: PROJ
allowed_targets:
projects: [OTHER]
poll:
enabled: true
query: 'labels = "fullsend" AND statusCategory != Done ORDER BY updated DESC'tracker.jira.base_url— The Jira site URL. It must be anhttpsURL with no credentials, query, or fragment. It has no default.tracker.jira.api_url— The Jira API gateway URL. It is optional and defaults tohttps://api.atlassian.com. It is validated likebase_url. Fullsend's shared Jira client sends REST requests to{api_url}/ex/jira/{cloudId}/rest/api/3/.... It discovers the cloud ID from{base_url}/_edge/tenant_infoand keepsbase_urlfor issue browse links. An override must serve the same gateway path layout. The Jira poller (fullsend poll --input-driver jira-poll) uses this client.fullsend issues, run-status updates, and reconciliation do not use this setting yet. They switch to the shared client in #9078.tracker.jira.project— The key of the single Jira project for this repository. It is not a fallback project, and it has no default.tracker.jira.poll.enabled— Controls the managed poll workflow.trueinstalls or enables it,falseremoves or disables it. Whenpollorpoll.enabledis omitted, the workflow is not installed.fullsend poll --input-driver jira-pollalso reads it: unless it istrue, the poller skips the cycle without contacting Jira, and writes an empty dispatch list to--outputwhen that flag is set. It does not affectfullsend issues.fullsend repos installconverges it. When it istrue, install writes the Jira credentials from--jira-tokenand--jira-emailand ensures the managed poll resources. When it isfalseor omitted, install removes those resources and keeps the credentials. The forge-specific poll resources are not managed yet (see Jira polling).tracker.jira.poll.query— The JQL the poller runs. It defaults tostatusCategory != Done ORDER BY updated DESC. The query is always scoped toproject: the poller runsproject = "<project>" AND (<query>)and keeps a trailingORDER BYafter the closing parenthesis. The parentheses in the query must be balanced. For example, the query above runs asproject = "PROJ" AND (labels = "fullsend" AND statusCategory != Done) ORDER BY updated DESC.
base_url and project are required once base_url, api_url, project, or poll is set. A mapping that only selects Jira (jira: {}) or only sets allowed_targets stays valid. This includes a mapping migrated from the legacy scalar tracker: jira.
Credentials are never part of this mapping. Keys such as token, api_token, email, or password are rejected. Supply the Jira token and the optional account email as the FULLSEND_JIRA_TOKEN and FULLSEND_JIRA_EMAIL repository secrets. The shared Jira client treats the token as a scoped API token. When FULLSEND_JIRA_EMAIL is set, the client uses Basic auth with that email and a scoped user token (a bot user). When it is absent, the client sends the token as a Bearer token from a scoped service account. The token string does not show its type, so the email alone selects the mode. Other unknown keys under tracker.jira, tracker.jira.poll, and tracker.jira.allowed_targets are rejected too.
fullsend poll --input-driver jira-poll reads the Jira site, API gateway, project, and query only from the effective tracker.jira config, so it requires base_url and project. The removed --jira-url, --jira-project, and --jql flags are rejected, and environment variables such as JIRA_BASE_URL are not read. The poller authenticates with the shared Jira client: FULLSEND_JIRA_TOKEN is required and FULLSEND_JIRA_EMAIL is optional, as described above. The legacy JIRA_TOKEN and JIRA_USER_EMAIL variables are not used by the poller.
The fullsend issues commands (get, create, post-comment, link), fullsend run Jira status updates, and fullsend reconcile-status read the Jira connection only from these settings, with no flag or environment fallback: base_url and project must be set, and Jira API calls fail without them. project is the default --project of the issues commands. fullsend issues create refuses any other Jira project unless it is listed in allowed_targets.projects. See Issue commands.
For GitHub and GitLab the scopes are additive: a destination is allowed if it matches a configured org or group, or a configured repo. Malformed entries are rejected: an empty org or group, an org containing /, a GitHub repo not in owner/name form, a GitLab path with empty segments or a repo without a namespace, or an invalid Jira project key.
The repository agents run in is always allowed, so it does not need an entry. The default GitHub per-repo configuration generated without a preset lists both fullsend-ai/fullsend and fullsend-ai/agents in tracker.github.allowed_targets.repos, so retro proposals and triage prerequisites can reach the platform or agent repository that owns the change. These are installation defaults, not an automatic grant for every repository in allowed_remote_resources. Fresh GitLab installations instead record an explicit empty tracker.gitlab.allowed_targets policy: cross-project targets must be configured for the GitLab host. Explicit settings are preserved. Retro and triage post-scripts currently read only the overlay (.fullsend/config.yaml), not the preset (.fullsend/config.base.yaml). Until agents#1728 is fixed, preset users must declare the required config.tracker.<provider>.allowed_targets in the managed repository’s repos.yaml entry and rerun fullsend repos install, or edit .fullsend/config.yaml for an unmanaged installation. Updating only the preset does not enable these cross-repository operations. Include all targets the post-scripts need in the overlay policy. Existing installations retain their permissions on upgrade; on GitHub, to opt in, add fullsend-ai/agents to the existing target list in the managed repository’s repos.yaml configuration, or in .fullsend/config.yaml for an installation not managed through fullsend repos. Preserve any other targets you need.
In the layered config system, tracker replaces the parent's whole object when set, so layers never combine two providers. See Layered Config Reference.
Breaking change — migration from the scalar tracker: tracker used to be a scalar (tracker: jira). A scalar value is no longer accepted: validation (fullsend lint and CLI config writes) and config loading reject it with a migration message. A repos.yaml managed config: block containing it is converted by fullsend repos install (see below). Replace it with the provider block:
# before
tracker: jira
# after
tracker:
jira: {}Migration from create_issues: the top-level create_issues key is removed. Runtime config loading fails with a migration error when either .fullsend/config.yaml or .fullsend/config.base.yaml still contains create_issues; the key is never silently ignored. Validation also rejects it. Move create_issues.allow_targets.orgs and repos to tracker.github.allowed_targets.orgs and repos on GitHub, to tracker.gitlab.allowed_targets.groups and repos on GitLab, or to tracker.jira.allowed_targets.projects on Jira, and keep only one provider block.
Automatic migration of managed configuration: fullsend repos install converts the legacy scalar tracker and create_issues keys in repos.yaml managed config: blocks (defaults.config, the forge section's config, and each repository entry's config) before the manifest is decoded, writes the converted manifest, and re-renders each fullsend-managed .fullsend/config.yaml from it. Each layer that set a legacy key gets the full tracker mapping that was in effect there, because the legacy keys were inherited separately while a tracker mapping replaces the parent's. The effective provider and targets of every repository stay the same:
- A layer with no provider uses the repository's forge (
githuborgitlab). Acreate_issuesblock indefaults.configwith no provider is moved to each forge section'sconfig, becausedefaults.configapplies to both forges. - GitHub
orgsandreposstay organization and repository grants undertracker.github.allowed_targets. GitLabreposmove totracker.gitlab.allowed_targets.repos. - Install stops before any write, naming the manifest path and the edit to make, when a target has no unambiguous equivalent. That covers repository or organization targets under a Jira provider (Jira targets are project keys under
tracker.jira.allowed_targets.projectsand are never derived from repository paths), organization targets on GitLab (list the groups undertracker.gitlab.allowed_targets.groups), and a layer that combines atrackermapping withcreate_issues. - A scalar
trackerwith nocreate_issuesin the manifest becomes a provider block with noallowed_targets. As atrackermapping it replaces anytrackermapping in the preset (.fullsend/config.base.yaml).
Running install again changes nothing. A read-only or HTTPS manifest is converted in memory for that run and install prints the edits to apply to its source. --dry-run reports the conversion without writing. Markerless .fullsend/config.yaml files and presets are not converted; edit them by hand.
authorization
Lists extra sources of permission for triggering agents. There are two providers: owners_file for GitHub events and jira for Jira events.
owners_file
The GitHub collaborator API always applies; this provider is checked first:
- An
approversentry in the repo-rootOWNERSfile gets write-level access (every slash command and custom agent). Areviewersentry gets triage-level access, which covers/fs-triage,/fs-review, and custom agents whose harness setsrole: triageorrole: review. Custom agents with any otherrole:still require write. - An entry that names a key in
OWNERS_ALIASESstands for that alias's members. A GitHub login equal to any alias key never matches, and nested aliases are not expanded. - A user not found in
OWNERSfalls through to the collaborator API. - If
OWNERSorOWNERS_ALIASEScannot be parsed, or two alias keys differ only by case, the OWNERS check is skipped and only the collaborator API decides. - Only the flat root
approvers/reviewerslists are read. Prowfilters:blocks and per-directoryOWNERSfiles are ignored.
jira
Decides which Jira users may trigger agents through the jira-poll input driver. It applies only to events from Jira and does not change GitHub or GitLab authorization (ADR 0153).
| Key | Meaning |
|---|---|
write.groups, write.accounts | Jira groups (by name or group ID) and Jira account IDs granted the fullsend write role |
triage.groups, triage.accounts | Jira groups and account IDs granted the fullsend triage role |
labels | How a label-added event is authorized: jira_editors (default once the entry is present), allowlist, or project_roles |
- Slash commands and other non-label events: when the entry sets a
writeortriagelist (an empty list counts as set, so it denies everyone), the lists decide. An actor listed underwritegetswrite. An actor listed only undertriagegetstriage, which covers/fs-triageand custom agents whose harness setsrole: triageorrole: review. Anyone else is denied. The Jira project roles are not consulted. When neither list is set, slash commands use the Jira project role lookup instead. That lookup needs Administer Projects. Other non-label events, such as an issue being opened or edited, are then not authorized for any agent, unless the entry setslabels: project_roles: with that value and no lists, every event (labels, slash commands, opened, edited, closed, reopened, plain comments, label removals) resolves through the project role lookup, exactly as with nojiraentry. - Label-added events:
jira_editorsauthorizes anyone who could add the label, which needs Jira's Edit Issues permission.allowlistchecks the label author against the same lists as slash commands.project_rolesuses the Jira project role lookup. - Group entries are resolved with Jira's user-groups API, once per actor per poll cycle. That needs the poller account to hold the Browse users and groups global permission. Account entries need no Jira call. Project-role resolution calls the same API for an actor when a project role is granted to a group.
To keep today's behavior explicitly, use the two-line entry - provider: jira with labels: project_roles and no lists.
The provider is opt-in. Without a jira entry, every Jira event, including label-added events, resolves through the project role lookup and takes the per-harness role threshold, which needs Administer Projects. Unknown keys in a jira entry, an unknown labels value, and empty group or account entries fail config loading. See Jira integration for the permissions each setting needs.
Default: absent (owners_file off; Jira resolves every event through the project role lookup). The field is not inherited from config.base.yaml: each repo opts in in its own config.yaml. Unknown or duplicate providers fail config validation, and also fail jira-poll and harness dispatch at load time, and write, triage, and labels are valid only on the jira provider. See OWNERS file authorization for an owners_file setup walkthrough.
status_notifications
Controls the comments and reactions fullsend posts on issues and PRs when agents start and complete.
comment.start—enabled(default) ordisabled.comment.completion—enabled(default),on_failure(comment only on failure), ordisabled.reaction.start—enabledordisabled(default). Reactions are an opt-in alternative that does not generate a GitHub notification.reaction.completion—enabled,on_failure, ordisabled(default).
When status_notifications is omitted entirely, both start and completion comments default to enabled.
Completion comments. comment.completion decides whether a comment is posted when the run ends:
| Value | Behavior |
|---|---|
enabled | Always post a completion comment (default) |
on_failure | Post when the agent fails, is cancelled, or is skipped by a pre-script; the start comment is automatically suppressed to avoid notification noise |
disabled | Never post a completion comment; silently remove the start comment |
on_failure reduces notification noise: successful runs leave no trace, but failures still surface. With on_failure, the start comment is suppressed regardless of comment.start, because posting and then deleting a start comment would still trigger a GitHub notification pointing to a deleted comment.
In enabled mode, a hard crash or cancellation that happens before the agent could post anything at all is also surfaced after the fact: a post-job cleanup step synthesizes an "Interrupted" comment so the run doesn't silently vanish.
Reactions. Emoji reactions don't generate a GitHub notification, so they are a lower-noise way to show that an agent is working and how the run turned out. Unlike comments, they default to disabled. When reaction.start is enabled, a 👀 reaction is added when the agent begins. At completion, the start reaction (if any) is removed and, depending on reaction.completion, replaced with an outcome reaction:
| Value | Behavior |
|---|---|
enabled | Always add a completion reaction: 👍 on success, 😕 on failure/cancelled/skipped |
on_failure | Add a 😕 reaction only on failure/cancelled/skipped; leave no reaction on success |
disabled | Never add a completion reaction (default) |
👎 is deliberately avoided for failures: it overloads GitHub's native up/down-vote convention, so a routine agent failure could be misread as the bot disliking the issue. Because reactions carry no notification cost, on_failure here only means "leave no reaction on success", with no start-suppression workaround.
For runs triggered by an issue or PR event, the reaction is added to the issue or PR itself. For runs triggered by a slash command comment (for example /fs-fix), it is added to the triggering comment instead, so it's clear which command the reaction is responding to.
Known limitations:
- Reactions are currently GitHub-only. Enabling
reaction.*on a GitLab-backed repo is silently a no-op today (#5998). - If a run is hard-killed before it can post its completion reaction, the start reaction (👀) can be left behind indefinitely. Unlike status comments, there's no out-of-process reconciler for orphaned reactions yet.
mint_url
Token mint URL for credential issuance. The mint service issues short-lived credentials to agents based on their role. Default: https://mint.fullsend.sh (the hosted public mint). Custom mint deployments set this to their own URL. See Standalone Mint and Mint Administration.
For GitHub repositories managed with fullsend repos install, a mint_url declared in the managed configuration (config.mint_url in repos.yaml) or the config_base preset also sets the FULLSEND_MINT_URL repository variable. Install, dry run and repos status report a conflict when it differs from the manifest's own mint_url. See Effective infrastructure values.
inference
Groups inference backend settings under a single key. Each subfield resolves independently through the layered config system (an overlay can override project without restating provider).
For repositories managed with fullsend repos install, project, region and wif_provider in the managed configuration or config_base preset feed the installed credentials: a declared region manages the FULLSEND_GCP_REGION variable for vertex-wif and openai-wif repositories (with none declared, an existing variable is kept and global is written only where it is missing), and project / wif_provider are written to the FULLSEND_GCP_PROJECT_ID / FULLSEND_GCP_WIF_PROVIDER secrets unless --vertex-project / --vertex-wif-provider supply them (a disagreeing flag is an error). See Effective infrastructure values.
provider— Inference backend identifier. Currently onlyvertex. Default:vertex.project— GCP project ID for inference requests. No default — must be provided by the installer or an existing secret.region— GCP region for inference. Default:global.wif_provider— Full Workload Identity Federation provider resource name. No default — must be provided by the installer.wif_audience— GitLabvertex-wifonly. The audience the GitLab agent job requests for its ID token (FULLSEND_ID_TOKEN), for an existing WIF provider that does not acceptfullsend.fullsend repos installrenders it into.gitlab/ci/fullsend-agent.yml. Default: unset, which keepsaud: "fullsend". Must be a single line of printable ASCII with no surrounding spaces and no$.service_account— GitLabvertex-wifonly. The email of a service account the agent job impersonates after the federated token exchange (<name>@<project>.iam.gserviceaccount.com).fullsend repos installwrites it as theFULLSEND_GCP_SERVICE_ACCOUNTCI/CD variable. Default: unset, which keeps direct federated identity.Both keys require an explicitly supplied
wif_provider(or--vertex-wif-provider) and are read only at install time. Inrepos.yaml, set them undergitlab.configor ongitlab.reposentries:github.configandgithub.reposentries reject them, and values inherited fromdefaults.configare ignored on GitHub repositories and on GitLab repositories that do not usevertex-wif. See Using an existing WIF provider.openai— OpenAI Workload Identity Federation identifiers (ADR 0092). TheFULLSEND_OPENAI_*runner variables, when set, replace the resolved block entirely. All three fields must come from one source for a run.fullsend repos installchecks the same sources for repositories whoseinference.authisopenai-wif. See OpenAI Workload Identity.gateway— a self-hosted, OpenAI/Anthropic-compatible inference gateway that validates the job's forge OIDC token directly (ADR 0137). Models with thegateway/prefix use this route. The block applies when it is complete and, in the defaultoidcmode, the run has a forge OIDC endpoint (a GitHub Actions job withid-token: write); theapi-keymode (seeauthbelow) has no such condition. In theoidcmode the runner then fetches the job's OIDC assertion foraudienceand puts it behind a run-scoped OpenShell provider with a per-host egress profile. It seeds the provider's placeholder into a runner-owned token file, and re-seeds it before each token's ownexp. OpenShell's placeholders do not say which rotation they belong to. So if the runner does not see a refreshed token's placeholder in the sandbox (the 90-second settle wait runs out, or reading it fails), it does not rotate again. It moves the provider's expiry back to the token the agent holds and stops refreshing, and the run fails closed when that token expires. The re-seed runs only after the sandbox hands out the new placeholder, and it checks the file afterwards. This happens in addition to every other provider:openai/and Vertex models in the same run keep their own routes. If the gateway cannot be reached, or refuses the token, the run fails and does not fall back to another credential. With no block, or in theoidcmode on a local run without an OIDC endpoint, the runner adds nothing on pi, so a harness that loads the inference-gateway extension as a plugin keeps working. codex and Claude Code have no such setup, so there agateway/model without a block that applies is an error. codex sendsgateway/models to the gateway'sPOST /v1/responsesthrough a runner-ownedfullsend-gatewayprovider (Codex › Models through an inference gateway). On Claude Code the runner exportsurlasANTHROPIC_BASE_URLand passes the id aftergateway/to--model; the model list is not used (details). Fields:url— the gateway origin, for examplehttps://gateway.example.com, optionally followed by the path prefix the gateway serves its API under, for examplehttps://gateway.example.com/team-afor a gateway behind a shared ingress. Must behttps(plainhttponly for a loopback test host), with no credentials, query or fragment, and no port other than 443: the runner adds the/v1/...model API paths itself, and the egress profile allows the gateway host on port 443 only. A path prefix must be a clean absolute path: no empty (//),.or..segment, no percent-encoding, only letters, digits,-,.,_and~in each segment, and no trailing/v1(writehttps://gateway.example.com/team-a, not.../team-a/v1). A trailing/is ignored. The prefix applies everywhere the runner builds a model API path: each runtime calls<url>/v1/..., the egress profile binds the credential to<prefix>/v1/**only (never the whole host) and allows only the model API paths under it, andfullsend inference gateway statusprobes<url>/v1/models. A bare origin works as before. The run route accepts a gateway on a private address, butfullsend inference gateway statusrefuses loopback, private and other internal addresses, so it cannot check one. For such a gateway, a run is the check: its first gateway model call fails if the gateway is unreachable or refuses the token.audience— the OIDC audience the runner requests. The token is valid only at the gateway. Required in theoidcmode; not used in theapi-keymode.auth— the credential mode,oidc(the default) orapi-key(ADR 0138). New in fullsend v0.47.0: an older CLI or reusable workflow rejects a config that sets it. There is no precedence and no fallback between them.oidcis the route described above.api-keyis for gateways that cannot trust forge OIDC. It is supported, but it is not the primary or safest option, because it relies on a long-lived secret: use it with caution, and preferoidcwhen the gateway supports it. In theapi-keymode the runner reads the gateway key from theFULLSEND_INFERENCE_GATEWAY_API_KEYforge secret or local environment variable, and fails the run when it is not set. It puts the key behind the same run-scoped provider placeholder, egress profile and guards as the OIDC token. The key is not rotated, so nothing is re-seeded. The block applies on local runs too, so the runner owns the route there as well.messages_auth_header— the header the gateway credential travels in on Messages-dialect requests:authorization(the default, used when the field is absent) orx-api-key(ADR 0142). A CLI or reusable workflow from v0.48.0 or earlier ignores the field and sendsAuthorization: Bearer, so anx-api-keygateway answers 401. Setx-api-keyfor a gateway that reads the caller's key fromx-api-keyon/v1/messages, the header Anthropic documents for API keys on that API. It is valid only withauth: api-key: a resolved block in theoidcmode that setsx-api-keyis a config error, because an OIDC token is a bearer token. Only the Messages dialect moves: pianthropic-messagesmodels send the key asx-api-key, and Claude Code gets it asANTHROPIC_API_KEYinstead ofANTHROPIC_AUTH_TOKEN. piopenai-responsesandopenai-completionsmodels, and codex, keepAuthorization: Bearer. Each request carries the credential in one header only. The header is configured, never guessed: the runner does not retry in the other header after a 401. A gateway that readsx-api-keymust remove the caller's key before it sets its own upstream key; see Header custody for an x-api-key gateway.models— optional inline model list, a map of model id to settings:api(one ofopenai-responses,anthropic-messages,openai-completions), and optionalcompat,contextWindowandmaxTokens.compatholds pi request-feature flags: each value is a boolean, string or number, a flag that pi-inference-gateway v0.1.1 knows must have that flag's type for the model'sapi, list-valued flags such asallowedFallbackModelsare refused, and an unknown flag whose name looks like a credential or header is refused.models_file— optional repository path, for example.fullsend/inference-gateway.json, to a file in the pi-inference-gateway config format. It must hold exactly one entry,providers.gateway, carrying onlymodels,include,excludeanddefaultApi, and every model must set its ownapi: pi runs offline, and the extension offers a configured model only when its entry names anapi. Per-model values follow the extension's types:compatas for inline models,contextWindowandmaxTokenspositive whole numbers,costonlyinput,output,cacheReadandcacheWriteas non-negative numbers, andthinkingLevelMapthinking levels mapped to a string ornull. Duplicate JSON keys are refused. These checks validate the file's shape; they are not secret detection.fullsend github setupcommits the file as written, so keep credentials out of free-text values such as model names.include,excludeanddefaultApionly apply to models discovered from the gateway, so they have no effect on a pi run. A file that setsbaseUrl,baseUrlEnv, a credential key (apiKey*,tokenFile,username*,password*),headers,authHeader,modelsPath,discoveryorfallbackModelsis refused: the runner owns those. The runner reads the file from the repository checkout thatconfig.yamlcame from, so it is read at the same ref (the base branch on pull-request events), even when an org or managed layer set the path.
urlis always required, and in theoidcmodeurlandaudienceare all or none:fullsend github setuprefuses to leave a block without them, and the runner fails a run whose resolved block is partial.modelsandmodels_fileare mutually exclusive. A pi run on agateway/model needs one of them, because pi runs offline and cannot discover the gateway's models. On codex the list is not used: fullsend renders no catalog for codex, so the id aftergateway/is the model it sends (a configuredmodels_filemust still be readable).url,audience,authandmessages_auth_headerlayer independently; the model list (either form) is one unit, and a layer that sets it replaces the inherited list. There is no runner-variable override for this block.fullsend github setup --inference-gateway-*writes it and changes only the keys you pass, so a repository can add its models under aurlandaudienceinherited fromconfig.base.yaml. For a walkthrough, see Using an inference gateway.
For setup instructions, see Getting Inference.
models
Model configuration, currently containing only aliases.
aliases— Per-key overrides of fullsend's pinned model alias table (defaults:opus→claude-opus-5-5,sonnet→claude-sonnet-5-5,haiku→claude-haiku-5-5, each with a built-in fallback chain to the previous default when the Vertex project does not serve it;fable→claude-fable-5-1on pi). An overridden alias has no built-in fallback chain. See Claude Code and pi. Keys are alias names (opus,sonnet,haiku,fable); values are model ids orprovider/idspecs (e.g.,claude-opus-4-6,google-vertex/gemini-3.8-flash). An unknown key is a validation error. A value must not be another alias name — aliases resolve once, sosonnet: opuswould reach the provider as the literal id "opus". In the layered config system, aliases merge per key across layers. See Layered Config Reference.
Installation model
This reference documents the per-repo config format (stored in .fullsend/config.yaml within the target repository). Per-repo is the only supported installation model. Per-org installation was removed (ADR 0044). Fullsend rejects a config.yaml that uses the old per-org format, which is any file with top-level dispatch, repos, or defaults keys.
Layered configuration
Per-repo config supports a two-file layered system:
| File | Role |
|---|---|
config.yaml | Overlay — repo-specific customization (writable) |
config.base.yaml | Base — vendor preset or shared baseline (read-through) |
Fields unset in the overlay fall through to the base layer, then to compiled-in code defaults. For complete merge rules, see Layered Config Reference.
See also
- Layered Config Reference — precedence, merge rules, and code defaults for every field
- Harness Field Reference — fields available in harness YAML files (per-agent configuration)
- Configuring GitHub — initial per-repo setup
- Configuring GitLab — initial per-repo setup
- Bring Your Own Agent — agent registration and harness authoring
- Configuring Agent Behavior — tuning agents via config and harness
fullsend agent— CLI commands for managing registered agents
