Context
We have completed the prerequisite catalog identity cleanups:
- SQLite/internal catalog IDs are implementation details.
- Public catalog refs are the stable human-facing identifiers.
- New intelligence catalog assets require explicit public refs.
- The daemon no longer derives scoped refs implicitly from scope/name.
This issue builds the GitHub-backed catalog delegation feature on top of that cleaner model.
The goal is to let organizations manage intelligence catalog additions and edits collaboratively through GitHub, while keeping agent composition and workflow ownership inside the daemon.
Problem
The daemon currently stores the intelligence catalog in SQLite. That is the correct default for local/single-daemon operation, but it does not scale well as the collaboration surface for an organization.
Developers already use GitHub for:
- review;
- history;
- ownership;
- CI;
- discussion;
- change control.
Reusable intelligence catalog assets should be able to follow that same workflow.
At the same time, the daemon must preserve a strict authority boundary:
- developers may edit reusable intelligence assets through GitHub;
- daemon admins still decide which assets are assigned to agents and workflows;
- GitHub catalog edits must not be able to change daemon-owned workflow behavior.
Goal
Add support for delegating the intelligence catalog source of truth to a GitHub repository.
By default, the daemon remains SQLite-backed.
When a daemon admin enables delegation, the daemon exports the current SQLite intelligence catalog to a configured GitHub repository as catalog.yml, commits it, records the resulting branch commit SHA, and from that point treats the GitHub repo as authoritative for catalog assets.
SQLite remains the daemon’s local runtime mirror/cache.
Non-goals
This issue must not delegate daemon workflow configuration to GitHub.
The delegated repo must not own or define:
- agents;
- agent-to-prompt assignment;
- agent-to-skill assignment;
- workspace assignment;
- repo bindings;
- labels;
- events;
- cron schedules;
- dispatch wiring;
- backend selection;
- model selection;
- token budgets;
- graph layout;
- workflow definitions.
Those remain daemon-admin configuration in SQLite.
Delegated catalog scope
v1 delegates the full intelligence catalog.
Delegated asset kinds:
prompt
skill
- operator-defined
guardrail
Runtime/system guardrails that are not editable catalog assets remain daemon-owned and outside catalog.yml.
GitHub delegation configuration
Add daemon configuration for catalog delegation, persisted in SQLite.
Suggested fields:
enabled
repo, owner/name
branch, default main
catalog_path, default catalog.yml
last_synced_commit
last_successful_sync_at
last_sync_status
last_sync_error
disabled_at, nullable
- credential reference or encrypted token storage
- created/updated timestamps
Secrets must never be returned by /config, /export, MCP, or UI responses except as redacted status.
The UI should expose a configuration flow:
Delegate intelligence catalog to GitHub repository
Required inputs:
- GitHub owner/repo
- branch, default
main
- catalog file path, default
catalog.yml
- GitHub token/PAT or credential reference
Validation before activation:
- repo exists;
- repo is accessible;
- token has read access;
- token has write access;
- branch exists;
- target file can be created or updated;
- private repos work with the provided credentials;
- if CI/bootstrap workflow files are created, token permissions support that operation.
Delegation must only become active after validation and first sync succeed.
Initial sync / migration path
Existing installations may already have catalog state in SQLite, so activation must include a first sync.
On activation:
- SQLite is authoritative.
- The daemon reads the current SQLite intelligence catalog.
- The daemon renders a canonical
catalog.yml.
- The daemon commits
catalog.yml to the configured branch/path.
- The daemon optionally commits catalog validation CI/workflow files.
- The daemon records the resulting branch commit SHA as
last_synced_commit.
- Delegation becomes active only after the commit SHA is persisted.
If any step fails, delegation remains disabled and SQLite remains authoritative.
The first sync is the migration point from SQLite-owned catalog state to GitHub-owned catalog state.
catalog.yml format
The delegated repo should not require a special directory structure. Teams should be able to organize the repository however they want, as long as the configured catalog file exists.
Canonical file:
version: 1
assets:
- id: coder
kind: prompt
name: coder
description: Implements approved work end-to-end.
body: |
Implement the requested change.
Run focused tests.
Open a PR when ready.
- id: go-api
kind: skill
name: go-api
body: |
For Go HTTP APIs, route each HTTP method to a distinct handler.
- id: security
kind: guardrail
name: security
description: Security baseline.
enabled: true
position: 10
body: |
Do not expose secrets.
Rules:
version is required.
assets is required.
- every asset requires
id;
- every asset requires
kind;
- every asset requires
name;
- every asset requires
body;
description is optional where supported;
enabled and position are only valid for guardrails;
id maps to the stable public catalog ref;
- internal SQLite primary keys must never appear;
- catalog version IDs must never appear;
- workspace scope must not appear;
- repo scope must not appear;
- agent assignment must not appear;
- backend/runtime/workflow fields must not appear;
- renaming
name is allowed if id remains stable;
- changing
id is treated as delete + create;
- deleting an asset still referenced by daemon-owned config fails sync.
Sync model
Avoid bidirectional sync for v1.
Once delegation is active, GitHub is authoritative for the catalog and SQLite is the daemon’s local mirror.
A separate goroutine should periodically check the configured branch, ideally once per minute.
Loop behavior:
- Read current branch head commit SHA.
- Compare it with
last_synced_commit.
- If unchanged, do nothing.
- If changed:
- fetch
catalog.yml at that commit;
- validate schema;
- validate catalog refs;
- validate forbidden fields are absent;
- reconcile into SQLite in one transaction;
- publish new internal catalog versions where content changed;
- update
last_synced_commit only after successful SQLite commit.
- If validation or persistence fails:
- do not update
last_synced_commit;
- do not partially apply changes;
- keep the last known-good SQLite catalog active;
- expose the sync error in UI/status/API;
- retry on the next interval.
Add a manual “Sync now” UI/API action if practical.
Reconciliation rules
GitHub catalog.yml reconciles into SQLite as the current runtime mirror.
Rules:
assets[].id maps to catalog public refs.
- SQLite internal auto-increment IDs remain local implementation details.
- Content/body changes create new internal catalog versions.
- Name/description/body changes update the same asset when
id is unchanged.
- Removing an unreferenced asset removes it from the SQLite mirror, following existing store semantics.
- Removing a referenced asset fails the sync transaction.
- Changing
id is delete old + create new.
- Duplicate refs fail validation.
- Invalid refs fail validation.
- Unsupported asset kinds fail validation.
- Sync is all-or-nothing.
Write-block enforcement
When catalog delegation is active, direct catalog mutations must be blocked in the service layer.
Do not rely only on REST/UI/MCP checks. Those surfaces should still expose good user-facing errors and disabled controls, but the invariant belongs in internal/service, because all mutable catalog use cases should pass through it.
Blocked while delegated:
- create/update/delete prompt;
- create/update/delete skill;
- create/update/delete guardrail;
- import paths that include delegated catalog sections;
- self-improvement publish paths that would directly mutate catalog assets.
Allowed while delegated:
- listing/reading catalog assets from the SQLite mirror;
- agent creation/editing;
- assigning existing catalog assets to agents;
- workspace guardrail selection;
- repo bindings;
- dispatch wiring;
- backend/runtime configuration;
- budgets;
- graph layout.
Use a typed error and map it consistently across REST/MCP.
Suggested message:
intelligence catalog is delegated to GitHub; edit catalog.yml in <owner>/<repo> instead
Built-in guardrails and daemon-originated catalog changes
The daemon must not silently mutate delegated catalog assets behind GitHub’s back.
Rules:
- runtime/system guardrails that are not editable catalog assets remain daemon-owned;
- operator-defined guardrails are delegated;
- built-in guardrails that are exposed as editable catalog assets need explicit treatment;
- if an editable guardrail is delegated, changes must come from GitHub;
- reset/update/delete actions for delegated guardrails must be blocked through the service layer.
For self-improvement flows:
- if delegation is inactive, existing direct publish behavior can continue;
- if delegation is active, self-improvement publish must not mutate SQLite catalog assets directly;
- preferred behavior is to generate a PR against the delegated catalog repo with the proposed
catalog.yml change;
- if PR generation is too large for this issue, mark the proposal as requiring manual GitHub editing and show the exact suggested patch/body.
GitHub write boundary
This feature introduces a narrow, explicit exception to the current daemon rule that it does not write to GitHub directly.
The exception is only for catalog delegation.
Allowed daemon GitHub writes:
- initial
catalog.yml commit to the configured delegated catalog repo;
- validation workflow/check files for that same repo, if implemented;
- future proposal PRs against that same repo, if implemented.
Not allowed:
- writes to application/code repos unless that repo is explicitly configured as the delegated catalog repo;
- agent runtime access expansion;
- workflow assignment edits through GitHub;
- direct mutation of daemon-owned agent/workflow config through GitHub.
All daemon GitHub writes must be auditable through Git commits or PRs.
CI validation
On first sync, the daemon should bootstrap catalog validation in the delegated repo if practical.
The validator should catch:
- malformed YAML;
- unsupported schema version;
- missing
id;
- duplicate
id;
- invalid public ref format;
- missing required fields per asset kind;
- unsupported asset kind;
- forbidden fields such as workspace, repo binding, agent assignment, backend, schedule, or dispatch wiring;
- invalid guardrail-only fields on non-guardrail assets.
The delegated repo contains the full catalog after first sync, so duplicate-ref validation can be done from catalog.yml alone. CI should not need access to the daemon’s SQLite database.
Prefer sharing parser/validator logic between daemon sync and CI if practical.
Undelegation behavior
Delegation must be reversible, but conservatively.
When a daemon admin disables catalog delegation:
- The daemon stops polling the GitHub repo.
- The daemon leaves the GitHub repo untouched.
- The current SQLite mirror becomes authoritative again.
- Catalog mutation APIs/UI/MCP become writable again.
- The daemon preserves delegation metadata for audit/debugging:
- repo;
- branch;
- catalog path;
- last synced commit;
- last successful sync time;
- last sync status/error;
- disabled timestamp.
Disabling delegation must not attempt to reconcile unsynced GitHub changes automatically.
If the latest GitHub commit was not successfully synced before undelegation, SQLite remains at the last known-good catalog state.
Re-enabling delegation must not silently choose a direction if SQLite and GitHub may have diverged.
On re-enable, require an explicit admin choice:
resume_from_repo: treat GitHub as authoritative, validate current catalog.yml, and sync GitHub → SQLite;
overwrite_repo_from_sqlite: treat current SQLite as authoritative, render catalog.yml, commit it to the configured branch, then store the resulting commit SHA.
If the repo has changed since last_synced_commit, overwrite_repo_from_sqlite should fail with a clear conflict unless the admin explicitly confirms overwrite/force behavior.
UI requirements
Configuration page:
- add “Delegate intelligence catalog to GitHub repository”;
- collect repo, branch, catalog path, token/credential;
- validate access before activation;
- show last synced commit;
- show last successful sync time;
- show last sync status/error;
- expose “Sync now” if practical;
- expose “Disable delegation”;
- on re-enable, require explicit direction if needed.
Catalog pages:
- when delegated, render catalog entries read-only;
- disable create/edit/delete/reset actions;
- link to the configured
catalog.yml;
- explain that catalog assets are GitHub-owned;
- explain that agent assignment and workflow configuration remain daemon-owned.
API/MCP behavior
Add read surfaces for delegation status.
When delegated:
- catalog mutations return the typed delegated/read-only error;
- agent/workspace/repo/backend/runtime/budget/graph mutations continue to work;
- import rejects delegated catalog sections;
- import may still allow daemon-owned sections if the existing import mode supports partial imports;
- export should avoid implying that delegated catalog sections are SQLite-authoritative.
Define export behavior explicitly in implementation:
- either include catalog mirror sections with delegation metadata;
- or omit delegated catalog sections and point to the configured GitHub repo.
Do not expose secrets.
Failure handling
The daemon should fail closed.
Handle:
- repo unreachable;
- token expired/revoked;
- no read permission;
- no write permission;
- branch missing;
- catalog file missing after delegation;
- malformed catalog;
- duplicate refs;
- invalid refs;
- unsupported schema version;
- unsupported asset kind;
- forbidden workflow/assignment fields;
- referenced asset deleted;
- GitHub API rate limit;
- repo changed between validation and write;
- daemon shutdown during sync.
Expected behavior:
- keep last known-good SQLite catalog active;
- expose error clearly;
- retry safely;
- never partially apply a broken catalog;
- never advance
last_synced_commit on failed sync.
Tests
Add tests for:
catalog.yml parser;
- schema validation;
- duplicate refs;
- invalid refs;
- unsupported asset kinds;
- forbidden assignment/workflow fields;
- first-sync export from SQLite to catalog format;
- activation records commit SHA only after successful commit;
- activation failure leaves delegation disabled;
- periodic sync no-op when commit unchanged;
- periodic sync applies changed catalog transactionally;
- failed sync keeps previous SQLite state;
- referenced asset deletion fails;
- REST catalog mutations are blocked while delegated;
- MCP catalog mutations are blocked while delegated;
- service-layer blocking cannot be bypassed by handlers;
- UI read-only state when delegated;
- import rejects delegated catalog sections;
- daemon-owned import sections remain possible if supported;
- secret redaction;
- sync loop cancellation/shutdown;
- undelegation stops polling and makes SQLite authoritative;
- re-enable requires explicit direction when repo and SQLite may have diverged.
Use a fake GitHub client or httptest.Server; tests must not depend on live GitHub.
Documentation
Update:
- README;
- docs/configuration.md;
- docs/api.md;
- docs/mcp.md;
- AGENTS.md;
- config.example.yaml if needed.
Docs must clearly explain:
- SQLite is the default source of truth;
- GitHub delegation is opt-in;
- GitHub owns catalog assets only;
- daemon still owns agent composition/workflow assignment;
- public refs are the catalog identity in
catalog.yml;
- internal DB IDs and version IDs never appear in
catalog.yml;
- direct catalog edits are blocked while delegated;
- undelegation behavior;
- re-enable behavior;
- the narrow GitHub write-boundary exception.
Acceptance criteria
- Daemon admin can delegate the full intelligence catalog to a GitHub repo.
- Delegated asset kinds include prompts, skills, and operator-defined guardrails.
- Existing SQLite catalog state is dumped to
catalog.yml and committed during first sync.
- Delegation becomes active only after the first sync commit succeeds.
- The daemon stores the synced branch commit SHA.
- Periodic sync detects branch changes and reconciles GitHub → SQLite.
- Sync is all-or-nothing.
- Sync failures keep the last known-good SQLite catalog active.
- Catalog mutation blocking is enforced in
internal/service.
- REST/UI/MCP expose clear delegated/read-only behavior.
- Agent composition and workflow configuration remain daemon-owned and editable.
catalog.yml contains only catalog assets and explicit stable refs.
- No internal DB IDs or catalog version IDs are exported to
catalog.yml.
- CI/catalog validation is bootstrapped or a reusable validator artifact is provided.
- Disabling delegation stops polling and makes SQLite authoritative without mutating GitHub.
- Re-enabling delegation requires an explicit direction when divergence is possible.
- Feature behavior and boundaries are documented.
Context
We have completed the prerequisite catalog identity cleanups:
This issue builds the GitHub-backed catalog delegation feature on top of that cleaner model.
The goal is to let organizations manage intelligence catalog additions and edits collaboratively through GitHub, while keeping agent composition and workflow ownership inside the daemon.
Problem
The daemon currently stores the intelligence catalog in SQLite. That is the correct default for local/single-daemon operation, but it does not scale well as the collaboration surface for an organization.
Developers already use GitHub for:
Reusable intelligence catalog assets should be able to follow that same workflow.
At the same time, the daemon must preserve a strict authority boundary:
Goal
Add support for delegating the intelligence catalog source of truth to a GitHub repository.
By default, the daemon remains SQLite-backed.
When a daemon admin enables delegation, the daemon exports the current SQLite intelligence catalog to a configured GitHub repository as
catalog.yml, commits it, records the resulting branch commit SHA, and from that point treats the GitHub repo as authoritative for catalog assets.SQLite remains the daemon’s local runtime mirror/cache.
Non-goals
This issue must not delegate daemon workflow configuration to GitHub.
The delegated repo must not own or define:
Those remain daemon-admin configuration in SQLite.
Delegated catalog scope
v1 delegates the full intelligence catalog.
Delegated asset kinds:
promptskillguardrailRuntime/system guardrails that are not editable catalog assets remain daemon-owned and outside
catalog.yml.GitHub delegation configuration
Add daemon configuration for catalog delegation, persisted in SQLite.
Suggested fields:
enabledrepo, owner/namebranch, defaultmaincatalog_path, defaultcatalog.ymllast_synced_commitlast_successful_sync_atlast_sync_statuslast_sync_errordisabled_at, nullableSecrets must never be returned by
/config,/export, MCP, or UI responses except as redacted status.The UI should expose a configuration flow:
Required inputs:
maincatalog.ymlValidation before activation:
Delegation must only become active after validation and first sync succeed.
Initial sync / migration path
Existing installations may already have catalog state in SQLite, so activation must include a first sync.
On activation:
catalog.yml.catalog.ymlto the configured branch/path.last_synced_commit.If any step fails, delegation remains disabled and SQLite remains authoritative.
The first sync is the migration point from SQLite-owned catalog state to GitHub-owned catalog state.
catalog.ymlformatThe delegated repo should not require a special directory structure. Teams should be able to organize the repository however they want, as long as the configured catalog file exists.
Canonical file:
Rules:
versionis required.assetsis required.id;kind;name;body;descriptionis optional where supported;enabledandpositionare only valid for guardrails;idmaps to the stable public catalog ref;nameis allowed ifidremains stable;idis treated as delete + create;Sync model
Avoid bidirectional sync for v1.
Once delegation is active, GitHub is authoritative for the catalog and SQLite is the daemon’s local mirror.
A separate goroutine should periodically check the configured branch, ideally once per minute.
Loop behavior:
last_synced_commit.catalog.ymlat that commit;last_synced_commitonly after successful SQLite commit.last_synced_commit;Add a manual “Sync now” UI/API action if practical.
Reconciliation rules
GitHub
catalog.ymlreconciles into SQLite as the current runtime mirror.Rules:
assets[].idmaps to catalog public refs.idis unchanged.idis delete old + create new.Write-block enforcement
When catalog delegation is active, direct catalog mutations must be blocked in the service layer.
Do not rely only on REST/UI/MCP checks. Those surfaces should still expose good user-facing errors and disabled controls, but the invariant belongs in
internal/service, because all mutable catalog use cases should pass through it.Blocked while delegated:
Allowed while delegated:
Use a typed error and map it consistently across REST/MCP.
Suggested message:
Built-in guardrails and daemon-originated catalog changes
The daemon must not silently mutate delegated catalog assets behind GitHub’s back.
Rules:
For self-improvement flows:
catalog.ymlchange;GitHub write boundary
This feature introduces a narrow, explicit exception to the current daemon rule that it does not write to GitHub directly.
The exception is only for catalog delegation.
Allowed daemon GitHub writes:
catalog.ymlcommit to the configured delegated catalog repo;Not allowed:
All daemon GitHub writes must be auditable through Git commits or PRs.
CI validation
On first sync, the daemon should bootstrap catalog validation in the delegated repo if practical.
The validator should catch:
id;id;The delegated repo contains the full catalog after first sync, so duplicate-ref validation can be done from
catalog.ymlalone. CI should not need access to the daemon’s SQLite database.Prefer sharing parser/validator logic between daemon sync and CI if practical.
Undelegation behavior
Delegation must be reversible, but conservatively.
When a daemon admin disables catalog delegation:
Disabling delegation must not attempt to reconcile unsynced GitHub changes automatically.
If the latest GitHub commit was not successfully synced before undelegation, SQLite remains at the last known-good catalog state.
Re-enabling delegation must not silently choose a direction if SQLite and GitHub may have diverged.
On re-enable, require an explicit admin choice:
resume_from_repo: treat GitHub as authoritative, validate currentcatalog.yml, and sync GitHub → SQLite;overwrite_repo_from_sqlite: treat current SQLite as authoritative, rendercatalog.yml, commit it to the configured branch, then store the resulting commit SHA.If the repo has changed since
last_synced_commit,overwrite_repo_from_sqliteshould fail with a clear conflict unless the admin explicitly confirms overwrite/force behavior.UI requirements
Configuration page:
Catalog pages:
catalog.yml;API/MCP behavior
Add read surfaces for delegation status.
When delegated:
Define export behavior explicitly in implementation:
Do not expose secrets.
Failure handling
The daemon should fail closed.
Handle:
Expected behavior:
last_synced_commiton failed sync.Tests
Add tests for:
catalog.ymlparser;Use a fake GitHub client or
httptest.Server; tests must not depend on live GitHub.Documentation
Update:
Docs must clearly explain:
catalog.yml;catalog.yml;Acceptance criteria
catalog.ymland committed during first sync.internal/service.catalog.ymlcontains only catalog assets and explicit stable refs.catalog.yml.