---
title: "122. Declaring managed repository configuration in repos.yaml"
status: Accepted
relates_to:
  - agent-infrastructure
  - agent-architecture
topics:
  - configuration
  - installation
  - repos
---

# 122. Declaring managed repository configuration in repos.yaml

Date: 2026-09-18

## Status

Accepted

**Implementation note (#7632, #7749):** The install/converge/status
integration in PR #7638 ships the ownership marker and the first-adoption
gate described below. Issue #7749 adds the pre-write safety-gate comparison
against the current effective configuration through the full runtime
accessor chain (`IsKillSwitchActive()`, `AllowedResources()`,
`ConfigRoles()`, agent `enabled: false` suppressions,
`IssueCreationConfig()`/`create_issues.allow_targets`) on first creation,
first adoption, and subsequent convergence. A less-restrictive candidate
is rejected unless the manifest explicitly declares that relaxation; status
and install output identify the affected keys.

## Context

The `fullsend repos` manifest manages installations across many repositories,
but repository configuration overrides are not yet declarative in that
manifest. Operators need one repeatable source of desired configuration for a
fleet while retaining the existing typed `config.yaml` schema and merge
semantics. A repository entry should be able to specify only the values it
needs to override.

This ADR covers management of `.fullsend/config.yaml` only. It builds on the
manifest and command model in
[ADR 0057](0057-repos-management.md) and
[ADR 0074](0074-repos-command-consolidation.md).

## Options

1. **Special-case selected fields, such as `agents`.** This keeps the initial
   manifest schema small, but every new configuration field would require a
   separate manifest feature and merge implementation.
2. **Treat manifest values as an unmanaged patch.** This would preserve local
   edits, but ownership and drift would be ambiguous and convergence could not
   compare one complete desired file with the installed file.
3. **Use generic, schema-backed configuration and manage the complete output.**
   This gives supported configuration fields the same behavior and makes drift
   and convergence deterministic.

## Decision

`repos.yaml` will support managed configuration blocks using the same schema as
`.fullsend/config.yaml`:

- A manifest-wide `defaults.config` block applies to every repository in the
  manifest.
- A repository entry's `config` block applies only to that repository.
- The manifest layers are merged from `defaults.config` and the repository's
  `config`; the repository value is more specific and wins. Unspecified values
  remain absent from the generated file and retain the existing `config.yaml`
  read-time behavior.
- Managed configuration is generic for fields not already represented by
  manifest shorthands. `agents` and other supported configuration fields use
  the normal schema and merge rules. Existing `runtime` and
  `allowed_remote_resources` manifest fields remain authoritative for those
  paths; a `config` block must not set either path, avoiding two competing
  manifest representations.

Managed configuration is opt-in per repository. `defaults.config` opts every
repository in the manifest into management; a repository `config` block opts
in only that repository. A repository with neither declaration is not managed,
even when another repository has one: install and status leave its existing
`.fullsend/config.yaml` untouched and skip drift checks for that file. For an
opted-in repository, the generated managed file contains only the explicitly
supplied manifest values (including authoritative manifest shorthands). Values
from an existing `config.yaml` that are not represented in the manifest are
not carried forward after adoption; unmanaged repositories are the only ones
whose existing files remain untouched.

Every managed `.fullsend/config.yaml` begins with these stable ownership
comments:

```yaml
# This file is managed by 'fullsend repos': repos-config-v1
# Do not edit this file directly; changes will be overwritten.
```

The marker is metadata rather than a configuration field and must survive
canonical rendering. If a repository is opted in and has no existing
`.fullsend/config.yaml`, install evaluates the candidate against the effective
configuration from an empty managed layer, then creates the file and writes the
marker if the safety gate passes; no adoption acknowledgement is needed when
there is no relaxation. If it has an existing `config.yaml` without the
marker, it is an adoption case: status reports that adoption is required, and
install refuses to replace the file until the operator explicitly acknowledges
the handoff and the safety gate passes. The first successful adoption writes
the marker. Once the marker is present, differences from the manifest are
ordinary managed drift and follow the normal convergence rules.

The installer will merge the manifest layers using the existing per-field
rules, then render the complete desired managed `.fullsend/config.yaml` from
the explicitly supplied manifest values. Scalars use specific-value-wins;
`agents` uses keyed merging by derived name; and replace-if-set fields such as
`roles` replace the less-specific value when supplied, including an explicit
empty value. These merge rules apply only while producing the desired file;
comparison with the installed file and convergence are whole-file operations,
not a second merge with local edits. Any difference in the managed file is
drift, including a change to a single key. Convergence rewrites the file with
the generated desired content; manual edits are therefore reported as drift
rather than preserved as an unmanaged patch.

Before any write of a managed file—first creation, first adoption, or
subsequent convergence—the operation must compare the candidate effective
configuration with the current effective configuration through the full
runtime accessor chain. The comparison covers `IsKillSwitchActive()`,
`AllowedResources()`, `ConfigRoles()`, agent keyed entries including
`enabled: false` suppressions, and `IssueCreationConfig()` including
`create_issues.allow_targets`.

Generated omission is evaluated as fallthrough through the parent chain, not
ignored because the sparse file lacks the key. The operation must reject a
less-restrictive candidate unless the manifest explicitly declares that
relaxation. This includes dropping `kill_switch: true`, `roles: []`, an
explicit `allowed_remote_resources: []` deny-all value, an agent suppression,
or widening a previously narrowed effective
`create_issues.allow_targets` list. An explicit empty `allowed_remote_resources`
remains deny-all and must not be treated as
equivalent to an omitted value. A blanket adoption acknowledgement is not
sufficient, and status/adoption output must identify the affected keys.

Manifest configuration must be decoded strictly and validated against the
typed `config.yaml` schema. Unknown or misspelled fields fail with a clear
error instead of being silently ignored. Each sparse manifest layer is first
validated for known fields and forbidden shorthand paths. Before rendering,
the resulting managed configuration (merged manifest blocks plus authoritative
manifest shorthands) must be validated through the existing full runtime
accessor chain, including separately managed parent configuration and code
defaults. This ADR does not define or manage those parent layers.

## Consequences

- A manifest can reproducibly install and converge configuration across a
  repository fleet, including future supported configuration fields.
- Local changes to `.fullsend/config.yaml` become visible as whole-file drift
  and are overwritten by convergence, so operators must make intentional
  changes in the manifest.
- The manifest and `config.yaml` share one schema, reducing duplicated
  configuration concepts but requiring manifest validation to track schema
  changes.
- Generated output is canonical managed content; formatting or comments that
  are not represented by the schema may not survive convergence, except for
  the stable ownership marker, which is required for adoption detection.
- Existing repositories require an explicit ownership handoff before a
  markerless `config.yaml` can be replaced, preventing first adoption from
  silently discarding locally maintained restrictions. Subsequent convergence
  also rejects less-restrictive generated security values unless the manifest
  explicitly declares them.
- Installation and status flows need to expose validation errors and drift
  clearly so a fleet operator can correct the manifest before converging.
