Skip to content

35. Layered content resolution

Date: 2026-05-09

Status

Accepted

Context

ADR 0003 designed a three-tier layering model — fullsend defaults < org .fullsend config < per-repo overrides — but the runtime never implemented it. The scaffold (internal/scaffold/scaffold.go) copies all ~82 files from internal/scaffold/fullsend-repo/ into every .fullsend repo via the Git Trees API, including agents, skills, harness configs, policies, and schemas that are identical across orgs.

This causes three problems: upstream improvements require every org to re-run fullsend admin install, the installer overwrites all files on upgrade (silently reverting org customizations), and there is no visible distinction between upstream defaults and org-specific content.

With reusable workflows (thin callers in .fullsend that delegate to fullsend-ai/fullsend via workflow_call), the pipeline checks out .fullsend at runtime, enabling a workspace merge that implements the layering model.

Decision

Three coordinated changes, applied uniformly to both installation modes (per-org and per-repo):

A. Customizations live in customized/. The scaffold installs a customized/ directory with empty subdirs (agents/, skills/, schemas/, harness/, plugins/, policies/, scripts/, env/) containing .gitkeep files. The location differs by install mode:

  • Per-org (fullsend admin install <org>): customized/ in the org-level <org>/.fullsend repo. Orgs add overrides here that apply to all enrolled repos.
  • Per-repo (fullsend admin install <owner/repo>): .fullsend/customized/ in the target repo itself. The repo adds overrides that apply only to that repo.

In both cases the main dirs (agents/, skills/, etc.) are not committed — they are populated at runtime from upstream.

B. Runtime layering via reusable workflows. Each reusable workflow adds a "Prepare workspace" step that checks out upstream defaults from fullsend-ai/fullsend at the ref supplied via fullsend_ai_ref (PR #1278 replaced the earlier checkout at @v0 with a checkout at a caller-controlled ref; since superseded by job.workflow_sha which eliminates the need for the caller to pass a ref — see PR #2689), copies them into the main dirs (agents/, skills/, etc.), then copies customizations on top so override files replace upstream defaults. When --vendor has committed upstream mirror content under .defaults/, the sparse checkout is skipped (see ADR 0047). The workflow inspects install_mode to resolve the correct customization base:

  • per-org: reads from customized/
  • per-repo: reads from .fullsend/customized/

The harness sees a single flat workspace in both modes — no changes to ResolveRelativeTo() or --fullsend-dir.

C. Scaffold stops writing upstream defaults. WalkFullsendRepo skips files in layered directories (agents/, skills/, schemas/, harness/, plugins/, policies/, scripts/, env/) and upstream-only directories (.github/actions/, .github/scripts/). The installer writes only mode-specific files and customized/ gitkeeps. CustomizedDirs() returns paths for per-org installs; PerRepoCustomizedDirs() returns .fullsend/customized/ paths for per-repo installs.

File categories after this change:

Per-org install (<org>/.fullsend repo):

  • Org-only (~11 files): dispatch.yml, thin callers, shim template, AGENTS.md — always installed, never overwritten by upstream.
  • Org overrides (8 .gitkeep files): customized/{agents,skills,schemas,harness,plugins,policies,scripts,env}/ — scaffold creates the structure, orgs add real files.
  • Upstream defaults (~60 files): agents, skills, schemas, harness, policies, scripts, env — authoritative in fullsend-ai/fullsend, provided at runtime via checkout of the release tag.
  • Upstream infrastructure (~5 files): composite actions, setup-agent-env.sh — referenced directly from upstream, never in .fullsend.

Per-repo install (target repo):

  • Repo scaffolding (~2 files): .github/workflows/fullsend.yaml (shim workflow), .fullsend/config.yaml (per-repo config).
  • Repo overrides (8 .gitkeep files): .fullsend/customized/{agents,skills,schemas,harness,plugins,policies,scripts,env}/ — scaffold creates the structure, repo owners add real files.
  • Upstream defaults and Upstream infrastructure: same as per-org — provided at runtime, never committed to the repo.

Consequences

  • Fresh per-org install produces a slim .fullsend repo (~24 files instead of ~82). Per-repo install adds only ~16 files to the target repo.
  • Upgrades never overwrite customized content — the installer does not touch files in customized/ (per-org) or .fullsend/customized/ (per-repo).
  • Upstream improvements to agents, skills, and schemas appear automatically at runtime without re-install, regardless of install mode.
  • Overrides are explicit and auditable: in customized/ for per-org, in .fullsend/customized/ for per-repo.
  • Requires a public upstream repo (fullsend-ai/fullsend is already public).
  • Runtime availability: checkout of upstream defaults requires github.com to be reachable at workflow execution time. GitHub Actions already depends on github.com, so this adds no new availability boundary.
  • Migration for existing orgs: orgs that customized files in top-level dirs (e.g., agents/triage.md) must move them to customized/ (e.g., customized/agents/triage.md) before upgrading. The installer can detect and warn about this during fullsend admin install.