Skip to content

feat(registry): support ad-hoc registry references (workflow@owner/repo)#190

Merged
jrob5756 merged 1 commit into
mainfrom
issue/189-adhoc-registry-refs
May 14, 2026
Merged

feat(registry): support ad-hoc registry references (workflow@owner/repo)#190
jrob5756 merged 1 commit into
mainfrom
issue/189-adhoc-registry-refs

Conversation

@jrob5756
Copy link
Copy Markdown
Collaborator

Closes #189.

What

Adds a new reference syntax that lets you reference a workflow in any GitHub repo without pre-installing a registry:

analysis@myorg/team-a#v1.0.0   # pinned tag
analysis@myorg/team-a#main     # pinned branch (resolves to current HEAD)
analysis@myorg/team-a          # default branch HEAD (no #ref)

Works everywhere existing registry references work — conductor run, conductor validate, conductor resume, and as a sub-workflow inside type: workflow agents (the engine + validator path added in #172 picks it up transparently).

Disambiguation rule

If the part after @ contains /, it's interpreted as a literal owner/repo and fetched directly from GitHub. If it doesn't, it's a configured registry name (existing behavior). Registry names disallow /, so the two forms can't collide.

analysis@team-a#v1.0.0          → named registry (no '/')
analysis@myorg/team-a#v1.0.0    → ad-hoc (contains '/' = owner/repo)

How

Resolver

registry/resolver.py extends ResolvedRef with kind="adhoc" and (adhoc_owner, adhoc_repo) fields. _parse_registry_ref branches on whether the registry slot contains /. The _looks_like_file_path heuristic now yields to the registry parser whenever @ is present in the ref, so refs containing / in the registry slot aren't misclassified as file paths.

Cache

registry/cache.py adds fetch_workflow_adhoc() which constructs a synthetic RegistryEntry(type=github, source=f"{owner}/{repo}") and reuses the existing fetch + cache pipeline. Cache namespace: ~/.conductor/cache/registries/_adhoc/<owner>/<repo>/<workflow>/<sha[:12]>/. The _adhoc segment is reserved (named registries reject names containing /).

Unifier (small refactor)

Adds resolve_and_fetch(ResolvedRef) -> Path as a single dispatcher used by the CLI, engine, and validator so each call site doesn't have to switch on ResolvedRef.kind. Collapses 6 duplicated resolve_ref + fetch_workflow blocks (4 in cli/app.py, 1 in engine, 1 in validator) into one-liners. The engine + validator pick up adhoc support transparently as a result.

Auth + caching unchanged

Same as named GitHub registries — public repos work; private uses gh auth token automatically. SHA-pinned refs are deterministic across resume; mutable refs (#main, omitted) re-resolve.

Tests

Area New tests What they cover
test_resolver.py 10 adhoc parsing (pinned tag/branch/SHA, omitted ref, dashes/dots in names, malformed forms, no config lookup, named-registry not affected, error hint mentions adhoc)
test_cache.py 7 fetch_workflow_adhoc cache layout, isolation from named registries, cache hit, resolve_and_fetch dispatcher per kind
test_integration.py 3 end-to-end resolve+fetch via unifier; works with no registries configured; coexistence with named registries
test_subworkflow.py 2 adhoc as type: workflow agent's workflow: field (success + fetch-failure)
test_validator.py 2 adhoc validation (success + fetch-failure)

All 2472 tests pass; make check clean.

Files

src/conductor/registry/resolver.py      | +110/-7
src/conductor/registry/cache.py         | +120/-0
src/conductor/cli/app.py                |  +9/-49
src/conductor/engine/workflow.py        | +13/-18
src/conductor/config/validator.py       |  +6/-14
docs/design/registry.md                 | +113
docs/workflow-syntax.md                 |  +30/-2
tests (5 files)                         | +571

Closes #189.

Adds a new reference syntax that doesn't require pre-installing a
registry via `conductor registry add`:

    analysis@myorg/team-a#v1.0.0   # pinned tag
    analysis@myorg/team-a#main     # pinned branch (resolves to current HEAD)
    analysis@myorg/team-a          # default branch HEAD (no #ref)

Disambiguation rule: if the part after `@` contains `/`, it's
interpreted as a literal `owner/repo` and fetched directly from GitHub.
If it doesn't, it's a configured registry name (existing behavior).
Registry names disallow `/`, so the two forms can't collide.

Works everywhere existing registry references work — `conductor run`,
`conductor validate`, `conductor resume`, and as a sub-workflow inside
`type: workflow` agents (the engine + validator path added in #172
picks it up transparently).

Cache layout: `~/.conductor/cache/registries/_adhoc/<owner>/<repo>/<workflow>/<sha[:12]>/`.
The `_adhoc` namespace is reserved (named registries reject names
containing `/`).

Auth: same as named GitHub registries — public repos work; private uses
`gh auth token` automatically.

Implementation:

* `registry/resolver.py` — extends `ResolvedRef` with `kind="adhoc"`
  and `(adhoc_owner, adhoc_repo)` fields. `_parse_registry_ref`
  branches on whether the registry slot contains `/`. The
  `_looks_like_file_path` heuristic now yields to the registry parser
  whenever `@` is present in the ref, so refs containing `/` in the
  registry slot aren't misclassified as file paths.

* `registry/cache.py` — adds `fetch_workflow_adhoc()` which constructs
  a synthetic `RegistryEntry(type=github, source=f"{owner}/{repo}")`
  and reuses the existing fetch + cache pipeline. Adds
  `resolve_and_fetch(ResolvedRef) -> Path` as a single dispatcher used
  by the CLI, engine, and validator so each call site doesn't have to
  switch on `ResolvedRef.kind`.

* `cli/app.py` — collapses 4 duplicated `resolve_ref + fetch_workflow`
  blocks (run, validate, resume, +1) to one-liners using
  `resolve_and_fetch`.

* `engine/workflow.py` and `config/validator.py` — replace the
  registry-only branches in `_resolve_subworkflow_path` and
  `_resolve_subworkflow_ref_for_validation` with `resolve_and_fetch`,
  picking up adhoc support transparently.

Tests:

* `test_registry/test_resolver.py` — 10 new tests for adhoc parsing
  (pinned tag/branch/SHA, omitted ref, dashes/dots, malformed forms,
  no config lookup, named-registry not affected, error message hints
  at adhoc fallback)

* `test_registry/test_cache.py` — 7 new tests for `fetch_workflow_adhoc`
  + `resolve_and_fetch` (cache namespace, isolation from named
  registries, cache hit, dispatcher behavior for each kind)

* `test_registry/test_integration.py` — 3 new end-to-end tests
  (resolve + fetch via unifier, no-config-required, coexistence with
  named registries)

* `test_engine/test_subworkflow.py` — 2 new tests for adhoc as
  `type: workflow` agent's `workflow:` field

* `test_config/test_validator.py` — 2 new tests for adhoc validation

* Existing tests updated where the error message changed from
  "Failed to fetch registry sub-workflow" to "Failed to fetch
  sub-workflow" (the unifier uses a kind-agnostic message).

Docs:

* `docs/design/registry.md` — new "Ad-hoc references" section with
  motivation, syntax, disambiguation rule, caching, auth, and
  cross-team composition example.

* `docs/workflow-syntax.md` — adds adhoc form to the sub-workflow
  reference examples.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jrob5756 jrob5756 merged commit af54d69 into main May 14, 2026
9 checks passed
@jrob5756 jrob5756 deleted the issue/189-adhoc-registry-refs branch May 14, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support ad-hoc registry references (no pre-installation required)

1 participant