44. Deprecate per-org installation mode
Date: 2026-06-16
Status
Accepted
Deprecates the per-org installation mode established in ADR 0002, ADR 0011, ADR 0012, ADR 0013, and ADR 0014.
Per-repo installation mode (ADR 0033) becomes the sole supported installation model.
Context
Fullsend's original installation model is per-org: fullsend admin install <org> creates a dedicated .fullsend config repo, per-role GitHub Apps (ADR 0007), shim workflows in enrolled repos, org-level variables and secrets, and a central token mint for OIDC-based credential issuance (ADR 0029). This requires org admin access and assumes all enrolled repos share agent configuration, credentials, and policies through the .fullsend config repo.
ADR 0033 added a per-repo installation mode where fullsend runs entirely within a single repository — no .fullsend config repo, no cross-repo dispatch, no org-level secrets. Per-repo reuses the reusable workflows from ADR 0031, the centralized routing from ADR 0034, and the layered content resolution from ADR 0035. The two modes coexist via a FULLSEND_PER_REPO_INSTALL guard variable that prevents per-org enrollment from overriding per-repo installations.
Maintaining both installation modes has become a significant source of complexity. The per-org model carries substantial surface area:
Dedicated
.fullsendconfig repo — a public repo that must exist forworkflow_callto work across visibility boundaries, creating content exposure risk for private repos whose event payloads transit through it.Org-level infrastructure — org variables (
FULLSEND_MINT_URL), org secrets (legacyFULLSEND_DISPATCH_TOKEN), variable visibility management (SetOrgVariableRepos), and workarounds for dot-prefixed repo bugs.Enrollment machinery —
config.yamlenrollment lists,repo-maintenance.ymlworkflow dispatch, enrollment PRs, enable/disable commands, reconciliation scripts, and drift analysis.Three-level workflow nesting — enrolled-repo shim (70 lines) calls
.fullsend/dispatch.yml(370 lines) which calls thin callers (43-66 lines) which call upstream reusable workflows — compared to per-repo's two-level chain (shim callsreusable-dispatch.ymlcallsreusable-{stage}.yml).Scaffold duplication — per-org installs a full set of scaffold files (
dispatch.yml, thin callers for each stage,repo-maintenance.yml,CODEOWNERS, composite actions) into the.fullsendrepo, creating drift risk despite ADR 0031's effort to minimize it.Dual code paths — CLI commands (
admin install,admin uninstall,admin enable/disable repos,github setup,github enroll/unenroll), layer implementations (ConfigRepoLayer,DispatchTokenLayer,EnrollmentLayer), config structures (OrgConfigvsPerRepoConfig), scaffold templates, shim templates, WIF provisioning, and e2e tests all branch on installation mode.Higher OAuth scope requirements — per-org install requires
admin:orgscope to manage org-level variables, secrets, and app installations. Per-repo requires onlyrepoandworkflowscopes when reusing existing apps.
Meanwhile, the per-repo model — built on top of the same reusable workflows, token mint, and content resolution infrastructure — achieves feature parity with per-org while eliminating these complexities. The key enabling change was ADR 0031's publication of reusable workflows from fullsend-ai/fullsend: once all agent logic lives in upstream reusable workflows, the .fullsend config repo becomes an unnecessary indirection layer.
ADR 0045 further erodes the per-org model's remaining advantages. It moves agent identity (role and slug) out of config.yaml's centralized agents: block and into individual harness files, making config.yaml "purely operational" (kill switch, dispatch mode, URL allowlists). Agents are discovered by file existence (harness/*.yaml), not by central registration — removing one of per-org's key selling points as the single source of truth for agent definitions. ADR 0045 also introduces harness composition via a base field: any repo can reference an upstream harness by URL and override only the fields that differ. This gives per-repo installations the same customization power that previously required a centralized .fullsend config repo — a thin wrapper with base: pointing to an upstream harness gets org defaults with local overrides. Cross-org sharing works the same way, without a shared config repo.
ADR 0038 complements this by making the .fullsend config repo unnecessary as a resource distribution point. Harness resources (agents, skills, policies) can be referenced by URL with mandatory SHA256 integrity hashes rather than requiring local copies in the .fullsend directory structure. A per-repo harness can reference https://raw.githubusercontent.com/fullsend-ai/library/.../agents/code.md#sha256=abc123... directly — no config repo needed to host shared resources. Combined with ADR 0045's base composition, ADR 0038 fully replaces the config repo's role as both a customization hub (ADR 0045) and a resource distribution point (ADR 0038).
One governance consideration: ADR 0038's allowed_remote_resources allowlist in config.yaml controls which URL domains are trusted for remote resource fetches. In per-org mode, this allowlist lives in the .fullsend repo — a single trust boundary governing all enrolled repos. In per-repo mode, each repo controls its own allowlist independently, meaning an org cannot centrally enforce which remote resource domains are trusted. This is a specific instance of the broader "no centralized policy enforcement" trade-off discussed below.
Pros and cons of removing per-org
Pros
Eliminates content exposure risk for private repos. Per-org routes event payloads through the public
.fullsendconfig repo. Workflow run logs and event context are visible in the public repo's Actions tab. Per-repo keeps all event data within the target repo's own context — the primary security benefit and the reason per-repo is already the recommended default for private repos.Reduces CLI and layer code by ~40%. Removing per-org eliminates the
ConfigRepoLayer,DispatchTokenLayer,EnrollmentLayer, org-level variable/secret management and repo listing in the forge interface (11 methods), enrollment commands (enable/disable repos,enroll/unenroll), the uninstall command, theOrgConfigstruct, per-org scaffold templates,dispatch.yml, thin caller workflows, and the e2e test suite.Simplifies the workflow nesting model. Per-repo uses two levels of
workflow_call(shim →reusable-dispatch.yml→reusable-{stage}.yml) vs per-org's three levels (shim →dispatch.yml→ thin caller →reusable-{stage}.yml). Fewer levels mean simpler debugging, faster workflow startup, and more headroom under GitHub's 4-level nesting limit.Removes the enrollment system. Per-org requires explicit repo enrollment via
config.yaml, reconciliation scripts, enrollment PRs, and drift analysis. Per-repo repos are always self-enrolled — no enrollment state to manage, no drift to detect, no reconciliation to run.Lowers the scope floor. Per-repo installs reusing existing apps need only
repoandworkflowOAuth scopes. Per-org always requiresadmin:orgfor org-level variable and secret management.Self-contained repos. Each repo carries its own config (
.fullsend/directory), workflow (.github/workflows/fullsend.yml), and customizations (.fullsend/customized/). No cross-repo dependencies, no shared state, no coordination through a central config repo.Unified distribution model. Reusable workflows (ADR 0031) become the sole distribution mechanism. Infrastructure patches ship once in
fullsend-ai/fullsendand propagate to all repos — no scaffold re-install, no thin-caller drift.Simpler credential model. Per-repo WIF providers are scoped to individual repos (
attribute.repository == "owner/repo") vs per-org's org-wide scope (attribute.repository_owner == "org"). A credential compromise in per-repo affects only that single repo.
Cons
No centralized policy enforcement. Per-org provides a single
.fullsendconfig repo where org admins can enforce uniform agent behavior, policies, and roles across all enrolled repos. Per-repo delegates policy to each repo independently — an org cannot guarantee all repos use the same sandbox policies, agent prompts, or skill sets. Organizations needing uniform policy must rely on CODEOWNERS conventions and organizational processes rather than a centralized config repo. This is partially mitigated by ADR 0045'sbasecomposition: repos can reference shared upstream harnesses by URL and override only what differs, providing a distribution mechanism for org defaults without requiring a.fullsendconfig repo.Weaker config governance model. In per-org, agent config lives in a separate
.fullsendrepo with its own CODEOWNERS, isolated from code contributors. In per-repo,.fullsend/config lives alongside code — a code contributor could modify agent behavior in a PR. CODEOWNERS entries on.fullsend/and.github/workflows/fullsend.ymlmitigate this, but the separation is weaker than a dedicated config repo.Per-repo setup overhead for large orgs. Organizations with many repos must run
fullsend admin install <owner/repo>for each one individually. Per-org's--enroll-allflag enrolled all repos in a single command. Tooling (scripts, CI automation) can mitigate this, but the out-of-the-box experience for large-scale adoption is less streamlined.Credential separation collapses. In per-org mode, the
.fullsendconfig repo holds credentials separate from enrolled repos — a compromised code contributor in an enrolled repo cannot access PEM secrets in.fullsend. In per-repo, the repo that triggers workflows also holds repo-level secrets (FULLSEND_GCP_WIF_PROVIDER,FULLSEND_GCP_PROJECT_ID). The token mint mitigates this (PEMs remain in Secret Manager), but repo-level secrets are co-located with code.No single source of truth for enrollment. Per-org's
config.yamlprovides a clear, queryable list of which repos have fullsend enabled and what roles they use. Without it, discovering fullsend-enabled repos requires scanning forFULLSEND_PER_REPO_INSTALLvariables orfullsend.ymlworkflow files across all repos.App installation still requires org admin. Even though per-repo removes the need for org admin to run
fullsend admin install, an org admin must still approve GitHub App installations on individual repos. This is a GitHub platform constraint, not a per-org vs per-repo distinction, but it limits the "no org admin needed" benefit.Migration burden for existing per-org users. All existing per-org installations must migrate to per-repo. This involves per-repo CLI runs, workflow changes in each enrolled repo, variable/secret updates, and cleanup of the
.fullsendconfig repo and org-level resources.
User impact
Org admins managing per-org installations are the most affected. They must execute a migration for each enrolled repo: run fullsend admin install <owner/repo>, verify the per-repo shim is active, and remove per-org enrollment. After all repos migrate, the .fullsend config repo and org-level variables can be cleaned up.
Per-repo users are unaffected — their installation model becomes the standard.
Users who valued centralized policy lose the .fullsend config repo as a single policy enforcement point. They can replicate policy consistency through ADR 0045's base composition (per-repo harnesses reference shared upstream harnesses by URL, inheriting org defaults and overriding only what differs), organizational processes (e.g., a shared .fullsend/customized/ template copied to each repo), or CI checks that validate repo-level configs against an org standard.
Users with private repos benefit — per-repo is already the recommended default for private repos, and removing per-org eliminates the risk of accidentally using the less-secure per-org model.
Options
Option A: Deprecate and remove per-org (chosen)
Deprecate per-org with a migration period, then remove. Per-repo becomes the sole installation model. Simplifies the codebase, eliminates dual-mode maintenance, and removes the content exposure risk inherent in per-org's public config repo.
Option B: Maintain both modes indefinitely
Continue supporting per-org and per-repo as equal alternatives.
Rejected: The ongoing maintenance cost of dual code paths, dual testing, dual documentation, and coexistence logic (guard variables, enrollment skipping) outweighs the benefit. Per-repo achieves feature parity with per-org while being simpler and more secure by default. The centralized policy benefit of per-org can be replicated through organizational processes without fullsend-level infrastructure.
Option C: Merge per-org into per-repo with org-wide automation
Build an org-level automation layer on top of per-repo: a script or CI workflow that runs fullsend admin install <owner/repo> for every repo in an org, enforces consistent .fullsend/customized/ configs, and provides a centralized dashboard.
Deferred: This could be built as a follow-up after per-org removal, but it is not a prerequisite. The core decision (per-repo as sole mode) stands independently. An org-wide automation tool is an enhancement, not a replacement for per-org's config repo model.
Decision
1. Deprecation plan
Deprecate the per-org installation mode in two phases:
Phase 1 — Deprecation (v1.x release):
- Add deprecation warnings to all per-org CLI commands (
admin install <org>,admin uninstall,admin enable/disable repos,github enroll/unenroll,github setup <org>). - Warnings direct users to
fullsend admin install <owner/repo>and link to migration documentation. - Per-org commands continue to function but emit a deprecation notice on every invocation.
- Update all user-facing documentation to present per-repo as the primary and recommended installation mode.
- Publish a migration guide (see section 3 below).
- Migrate existing per-org e2e tests to cover per-repo mode, ensuring test coverage is preserved before per-org code paths are removed.
Phase 2 — Removal (v2.0 release, breaking change):
Prerequisite: ADR 0045 Phases 1–2 must be complete before Phase 2 begins — harness composition via base URLs replaces the .fullsend config repo as the mechanism for sharing org defaults. ADR 0045 is accepted and its implementation (Phases 1–4) is complete.
- Remove all per-org CLI commands and flags (
--enroll-all,--enroll-none). - Remove per-org layers (
ConfigRepoLayer,DispatchTokenLayer,EnrollmentLayer). - Remove per-org config structures (
OrgConfig,NewOrgConfig,ParseOrgConfig,EnabledRepos,DisabledRepos). - Remove per-org scaffold templates (
dispatch.yml, thin callers,shim-workflow-call.yaml,repo-maintenance.yml). - Remove org-level forge methods (
CreateOrUpdateOrgVariable,OrgVariableExists,DeleteOrgVariable,SetOrgVariableRepos,GetOrgVariableRepos,CreateOrgSecret,OrgSecretExists,DeleteOrgSecret,SetOrgSecretRepos,GetOrgSecretRepos,ListOrgRepos). KeepListOrgInstallations(per-repo app discovery) andGetOrgPlan(plan detection). - Remove the
FULLSEND_PER_REPO_INSTALLguard variable and all guard-checking logic (no longer needed when per-org enrollment does not exist). - Convert per-org e2e tests to per-repo mode (preserving coverage) and remove org pool locking.
- Remove
fullsenddispatch role from default agent roles (per-repo already excludes it). - Remove the
admin uninstallcommand (per-repo cleanup is repo-level: delete the workflow file, remove variables/secrets). - Simplify the
admin installcommand to accept only<owner/repo>format. - Tag as v2.0 with
BREAKING CHANGE:trailer per CONTRIBUTING.md.
2. Implementation plan
The detailed, per-file implementation plan is maintained in a companion document: docs/plans/deprecate-per-org-install.md. It contains a 15-PR dependency graph, per-function change lists, and a release checklist. The summary below captures the high-level ordering.
Phase 1 (5 PRs):
- Add deprecation warnings to all per-org CLI paths.
- Update user-facing documentation to present per-repo as primary.
- Publish migration guide.
- Add
fullsend admin migratecommand (with--alland--dry-run). - Add migration e2e test.
Phase 2 (10 PRs, ordered by dependency — see plan for full graph):
- PRs 6–7: Remove per-org CLI commands and layer stack (
ConfigRepoLayer,DispatchTokenLayer,EnrollmentLayer). - PR 7: Remove per-org harness wrappers and workflow layer.
- PR 8: Remove per-org config structures; rename
PerRepoConfig→Config,PerRepoDefaultRoles()→DefaultRoles(). - PR 9: Remove per-org scaffold templates; rename per-repo scaffolds.
- PR 10: Remove org-level forge methods (10 secret/variable methods,
ListOrgRepos); removePerRepoGuardVar; keepListOrgInstallations(per-repo app discovery) andGetOrgPlan. - PR 11: Simplify dispatch provisioner; retain
Config.GitHubOrgs(needed byEnsureOrgInMint). - PR 12: Simplify appsetup; remove per-org branching.
- PR 13: Update mint validation (
ValidateWorkflowRef); keepValidateOrgAllowed. - PR 14: Remove
install_modefrom reusable workflows and actions. - PR 15: Remove per-org e2e tests, TypeScript cleanup, final grep sweep.
3. Migration plan for existing per-org users
Prerequisites
- Identify all repos enrolled in per-org by reading
.fullsend/config.yaml(repos:section,enabled: true). - Ensure token mint is deployed and accessible (same mint serves both per-org and per-repo).
- Ensure per-role GitHub Apps are installed on each target repo (or are shared public apps already installed at the org level).
- Have
repoandworkflowOAuth scopes for the migration token.
Per-repo migration steps (for each enrolled repo)
Run per-repo install.
bashfullsend admin install <owner>/<repo> \ --mint-url <existing-mint-url> \ --inference-project <existing-project> \ --skip-app-setup \ --skip-mint-deployThis creates the per-repo shim workflow (
.github/workflows/fullsend.yml),.fullsend/config.yaml,.fullsend/customized/directories, and sets repo-level variables/secrets.Alternatively, use the migration command:
bashfullsend admin migrate <org> <repo>Copy customizations. If the org
.fullsendconfig repo has customizations incustomized/(agent overrides, policies, skills, scripts), copy them to the target repo's.fullsend/customized/directory:bash# Clone the .fullsend config repo git clone https://github.com/<org>/.fullsend /tmp/fullsend-config # Copy customizations to target repo cp -r /tmp/fullsend-config/customized/* <target-repo>/.fullsend/customized/ # Commit and push cd <target-repo> git add .fullsend/customized/ git commit -m "feat: migrate fullsend customizations from per-org config" git pushVerify per-repo installation. Create a test issue or PR to confirm the per-repo shim triggers correctly and agents respond:
bashgh issue create --repo <owner>/<repo> \ --title "Test: verify per-repo fullsend migration" \ --body "Testing per-repo installation after migration from per-org."Verify triage agent responds with a comment.
Remove per-org enrollment. After verifying per-repo works:
bashfullsend admin disable repos <org> <repo>This removes the repo from
.fullsend/config.yamland triggers a PR to remove the per-org shim workflow from the repo.Merge the unenrollment PR. The
repo-maintenance.ymlworkflow creates a PR removing the per-org shim. Merge it. The per-repo shim (installed in step 1) takes over.Add CODEOWNERS protection. Add entries to the repo's
CODEOWNERSto protect per-repo config files:.github/workflows/fullsend.yml @<admin-team> .fullsend/ @<admin-team>
Org-level cleanup (after all repos are migrated)
Delete org-level variables.
bashgh variable delete FULLSEND_MINT_URL --org <org> gh variable delete FULLSEND_GCP_REGION --org <org>Delete legacy org secrets (if any remain from PAT dispatch mode):
bashgh secret delete FULLSEND_DISPATCH_TOKEN --org <org>Archive or delete the
.fullsendconfig repo.bash# Archive (preserves history) gh repo archive <org>/.fullsend # Or delete gh repo delete <org>/.fullsend --yesOptionally clean up GitHub Apps. Per-role apps (
fullsend-ai-triage,fullsend-ai-coder, etc.) can remain installed — they are still used by per-repo installations via the token mint. Only delete apps that are no longer needed.
Rollback
If a per-repo installation fails during migration:
- The per-org enrollment is still active (step 4 has not run yet).
- Delete the per-repo shim:
git rm .github/workflows/fullsend.ymland push. - The per-org shim (still in place) continues to function.
- Remove per-repo variables:bash
gh variable delete FULLSEND_PER_REPO_INSTALL --repo <owner>/<repo>
Migration is designed to be repo-by-repo with verification between each repo. An org can run per-org and per-repo simultaneously (the coexistence model from ADR 0033) throughout the migration window.
Automation
For orgs with many repos, the fullsend admin migrate --all command automates the migration:
fullsend admin migrate <org> --all \
--mint-url <existing-mint-url> \
--inference-project <existing-project>This reads config.yaml, iterates over all enabled: true repos, runs per-repo install on each, verifies the shim is active, and disables per-org enrollment. It processes repos sequentially (per-repo WIF registration is not safe for concurrent calls) and reports progress.
Consequences
Positive
- Single code path. All installation, setup, and maintenance code follows one model. No mode detection, no guard variables, no enrollment machinery.
- Smaller codebase. Removes ~40% of the CLI and layer code, simplifies the forge interface, eliminates per-org scaffold templates.
- Better security default. Per-repo eliminates the content exposure risk of routing event payloads through a public
.fullsendconfig repo. Per-repo WIF providers are scoped to individual repos rather than entire orgs. - Simpler onboarding. New users learn one installation model. The SaaS profile (shared public apps + upstream reusable workflows) can onboard a repo in under 15 minutes.
- Faster infrastructure patches. Reusable workflows are the sole distribution mechanism — patches ship once in
fullsend-ai/fullsendand propagate to all repos with zero per-repo action. - Reduced GitHub scope requirements. Per-repo installs reusing existing apps need only
repoandworkflowscopes.
Negative
- Migration effort for existing users. Every per-org installation must be migrated repo-by-repo. The
fullsend admin migratecommand and--allflag reduce manual work, but verification is still per-repo. - No centralized policy enforcement. Org admins lose the
.fullsendconfig repo as a single point of policy control. Consistent policy can be achieved through ADR 0045'sbaseharness composition (repos reference shared upstream harnesses by URL), organizational processes, shared templates, or future org-wide automation tooling. - Per-repo setup overhead at scale. Large orgs must run
fullsend admin install <owner/repo>for each repo. The migration command's--allflag and scripting mitigate this for initial setup, but each new repo requires its own install invocation. - Redundant org-level credentials. Per-repo install creates repo-level copies of shared credentials (
FULLSEND_MINT_URL,FULLSEND_GCP_REGION, etc.) that are identical across repos in an org. GitHub natively supports org-level secret/variable inheritance, but the current per-repo install flow does not leverage it. A futurefullsend admin setup-org-credentialscommand or--org-level-credentialsflag could detect and reuse existing org-level credentials without requiring the per-org installation model (see Option C). - Breaking change. v2.0 removes per-org commands. Users who have not migrated will encounter errors. The deprecation period (Phase 1) provides advance notice.
Risks
Ordered by the project's threat priority:
- Migration window exposure. During migration, some repos run per-org and others run per-repo. The coexistence model (ADR 0033's guard variable) handles this, but org admins must complete migration before the v2.0 removal deadline.
- Incomplete migration. Users who ignore deprecation warnings and upgrade to v2.0 without migrating will lose per-org functionality. The v2.0 release notes and CLI error messages must clearly direct users to the migration guide.
- Policy drift. Without centralized config, repos may diverge in agent behavior, sandbox policies, and skills. ADR 0045's
baseharness composition provides a distribution mechanism for shared defaults, but orgs must still enforce that repos use the sharedbasereference rather than diverging independently.
References
- ADR 0002: Initial fullsend design — original per-org model
- ADR 0007: Per-role GitHub Apps — app model preserved in per-repo
- ADR 0011: Admin install org config YAML v1 — deprecated by this ADR
- ADR 0012: Admin install fullsend repo files v1 — deprecated by this ADR
- ADR 0013: Admin install repo enrollment v1 — deprecated by this ADR
- ADR 0014: Admin install GitHub Apps secrets v1 — deprecated by this ADR
- ADR 0029: Central token mint — shared infrastructure, preserved
- ADR 0031: Reusable workflows — sole distribution model after removal
- ADR 0033: Per-repo installation mode — becomes the standard
- ADR 0034: Centralized shim routing —
reusable-dispatch.ymlreplaces per-orgdispatch.yml - ADR 0035: Layered content resolution —
.fullsend/directory replaces.fullsendrepo - ADR 0038: Universal harness access — URL-based resource references replace config repo as resource distribution point
- ADR 0045: Forge-portable harness schema — agent identity moves to harness files;
basecomposition replaces centralized config repo overrides - Implementation plan — PR dependency graph, per-file changes, release checklist
