PackageContentResolver never fetches package content for REGISTRY references — agents.<name>/skills:/routes.<name> get the §8.2.2 stub instead of real config #135

Open
opened 2026-08-10 22:18:03 +00:00 by CoreRasurae · 0 comments
Member

Metadata

  • Commit Message: fix(registry): fetch and parse package content for resolved registry references
  • Branch: bugfix/m1-registry-content-fetch

Background and context

PackageContentResolver._resolve_registry_async (src/cleveractors/registry/reference_resolver.py) is the single shared resolution path behind every REGISTRY-scheme package reference in this project: bare agents.<name> references (ADR-2037 D-2/D-4), routes.<name> references (ADR-2037 D-7/D-8), skills: references (cleveractors.agents.skill_resolution.SkillReferenceResolver, which mirrors the same pattern per ADR-2037's Context), and template-driven package references (cleveractors.templates.base._resolve_package_ref).

The Package Registry Standard (docs/actor-registry-standard.md) defines package retrieval as two distinct endpoints:

  • §8.2.2 GET /{package_type}/{namespace}/{name}?version={version} — "Resolves a package reference to its concrete Package ID." Response: {"package_id": "pkg_...", "type": "<package_type>"} only.
  • §8.2.1 GET /packages/{package_id} — "Returns the raw package content for the specified package ID." Response: {"content": "<YAML string>"}.

docs/index.md §4.1.1 (added by ADR-2037 D-1) normatively requires: "A compliant implementation MUST resolve the reference to an agent-type package ... and use that package's type/config fields exactly as if they had been written inline under the agents.<name> mapping form." ADR-2037 D-3 explicitly assumes "the agent reference resolver returns the raw resolved package content dict unchanged" with type/config "already sit[ting] at the top level."

Current behavior

_resolve_registry_async calls only resolve_package() (RegistryClient.resolve_package / RegistryCache.resolve_package, both of which hit exactly §8.2.2 and return only {"package_id", "type"}) and merges that response directly into the dict it returns as if it were the resolved package content:

content: dict[str, Any] = {
    "_original_reference": original_reference, "_server": server,
    "_namespace": namespace, "_name": name, "_version": version,
}
if isinstance(resolved, dict):
    content.update(resolved)   # only ever {"package_id": ..., "type": <package_type>}
...
return copy.deepcopy(content)

It never calls RegistryClient.get_package(package_id) / RegistryCache.get_package (§8.2.1) to fetch the actual package payload, and never parses the YAML string under that response's content key. Confirmed by grep: no caller anywhere in src/cleveractors outside registry/client.py/registry/cache.py themselves invokes get_package/get_package_content.

Repro:

  1. A graph references an agent-type package by a bare REGISTRY-scheme string, e.g.:
    agents:
      calculator_builder: "192.168.1.53:luis-mendes/calculator-app-builder"
    
    where the referenced package (as published on the registry) is a real type: llm agent (system_prompt, tools, provider, model, etc. — confirmed present in the equivalent local package, calculator-app-builder.yaml).
  2. Run: python test_app.py test --graph <file>.yaml --prompt "..." --registry-key "<key>" against that registry.
  3. Observe: Error: Execution failed: Unknown agent type: agent.

This happens because the dict AgentFactory.acreate_agent receives has type == "agent" (the §8.2.2 stub's package-type field, echoed straight through), not "llm" (the real agent's type, which lives in the §8.2.1 content payload that was never fetched) — and none of system_prompt/tools/provider/model are present either, since none of the actual package content was ever retrieved.

Expected behavior

PackageContentResolver.resolve()/aresolve(), for a REGISTRY reference, performs both required calls in sequence — §8.2.2 to obtain package_id, then §8.2.1 to fetch and parse that package's real content — and returns the parsed content (with type/config at the top level, per docs/index.md §4.1.1) merged with the existing _original_reference/_server/_namespace/_name/_version/package_id enrichment fields. A type: llm agent referenced this way constructs successfully via AgentFactory.acreate_agent/AgentFactory.create_agent, matching the behavior already correct for local: references (which read a LocalPackage's already-parsed content directly).

Acceptance criteria

  • PackageContentResolver.resolve()/aresolve() for a REGISTRY reference calls get_package/get_package_content with the package_id obtained from resolve_package(), and parses the returned content YAML string into a dict.
  • The dict returned by resolve()/aresolve() for a REGISTRY reference has type/config (or whatever fields the real package defines) from the actual package payload — never the §8.2.2 stub's package-type-name type field.
  • A type: llm agent referenced via a bare REGISTRY-scheme string in agents.<name> (e.g. agents.worker: "host:ns/name") creates successfully through AgentFactory.acreate_agent, instead of raising AgentCreationError("Unknown agent type: <package_type>").
  • The existing resolution cache (PackageContentResolver.cache, keyed via _compute_cache_key) caches the fully resolved content (post-get_package), not the intermediate §8.2.2 stub.
  • The per-server RegistryCache content tier (get_package) is used when available (mirroring the existing resolve_package cache/no-cache branching already in _resolve_registry_async), falling back to the raw RegistryClient.get_package otherwise.
  • No regression to local:/ID: reference resolution, which is unaffected by this defect.

Supporting information

  • src/cleveractors/registry/reference_resolver.py (_RegistryReferenceStrategy, PackageContentResolver._resolve_registry_async)
  • src/cleveractors/registry/client.py (RegistryClient.get_package §8.2.1, RegistryClient.resolve_package §8.2.2)
  • src/cleveractors/registry/cache.py (RegistryCache.get_package, RegistryCache.resolve_package)
  • docs/actor-registry-standard.md §8.2.1, §8.2.2
  • docs/index.md §4.1.1 (Agent Package References), §5.1.1 (Route Package References)
  • docs/adr/ADR-2037-agent-and-route-package-reference-resolution.md (D-2, D-3, D-4, D-7, D-8 — this defect violates D-3's assumption that the resolved dict already has type/config at the top level)
  • docs/adr/ADR-2034-skill-package-support.md (the skills: resolution path this ADR mirrors shares the identical defect, via the same PackageContentResolver)
  • Sibling registry bug (already fixed, different defect): #131/#132 — that fix corrected the §8.2.2 URL's {package_type} segment (full type name vs. Package-ID prefix); it did not touch the missing §8.2.1 fetch this issue describes.

Subtasks

  • Fix _resolve_registry_async (src/cleveractors/registry/reference_resolver.py) to call get_package/get_package_content with the resolved package_id, parse the returned content YAML string, and merge that parsed content (not the §8.2.2 stub) into the dict returned by resolve()/aresolve().
  • Route the get_package call through the per-server RegistryCache when available, mirroring the existing resolve_package cache/no-cache branching in _resolve_registry_async.
  • Ensure the resolution cache (_put_cache/self.cache) stores the fully-resolved (post-get_package) content, not the intermediate stub.
  • Tests (Behave): extend features/registry_http_client.feature with scenarios proving an agents.<name> (and skills:, routes.<name>) REGISTRY reference resolved through PackageContentResolver.resolve()/aresolve() returns real package content (type/config from the package payload), not the resolve-endpoint stub.
  • Tests (Robot): add/extend an integration scenario exercising a type: llm agent referenced via a REGISTRY-scheme string end-to-end against a registry test double.
  • Verify coverage >= 97% via nox -s coverage_report.
  • Run nox (all default sessions), fix any errors.

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details.
  • The commit is pushed to the branch matching the Branch in Metadata exactly.
  • The commit is submitted as a pull request to master, reviewed, and merged.
## Metadata - **Commit Message:** `fix(registry): fetch and parse package content for resolved registry references` - **Branch:** `bugfix/m1-registry-content-fetch` ## Background and context `PackageContentResolver._resolve_registry_async` (`src/cleveractors/registry/reference_resolver.py`) is the single shared resolution path behind every REGISTRY-scheme package reference in this project: bare `agents.<name>` references (ADR-2037 D-2/D-4), `routes.<name>` references (ADR-2037 D-7/D-8), `skills:` references (`cleveractors.agents.skill_resolution.SkillReferenceResolver`, which mirrors the same pattern per ADR-2037's Context), and template-driven package references (`cleveractors.templates.base._resolve_package_ref`). The Package Registry Standard (`docs/actor-registry-standard.md`) defines package retrieval as **two distinct endpoints**: - §8.2.2 `GET /{package_type}/{namespace}/{name}?version={version}` — "Resolves a package reference to its concrete Package ID." Response: `{"package_id": "pkg_...", "type": "<package_type>"}` only. - §8.2.1 `GET /packages/{package_id}` — "Returns the raw package content for the specified package ID." Response: `{"content": "<YAML string>"}`. `docs/index.md` §4.1.1 (added by ADR-2037 D-1) normatively requires: "A compliant implementation MUST resolve the reference to an `agent`-type package ... and use that package's `type`/`config` fields exactly as if they had been written inline under the `agents.<name>` mapping form." ADR-2037 D-3 explicitly assumes "the agent reference resolver returns the raw resolved package content dict unchanged" with `type`/`config` "already sit[ting] at the top level." ## Current behavior `_resolve_registry_async` calls only `resolve_package()` (`RegistryClient.resolve_package` / `RegistryCache.resolve_package`, both of which hit exactly §8.2.2 and return only `{"package_id", "type"}`) and merges that response directly into the dict it returns as if it were the resolved package content: ```python content: dict[str, Any] = { "_original_reference": original_reference, "_server": server, "_namespace": namespace, "_name": name, "_version": version, } if isinstance(resolved, dict): content.update(resolved) # only ever {"package_id": ..., "type": <package_type>} ... return copy.deepcopy(content) ``` It never calls `RegistryClient.get_package(package_id)` / `RegistryCache.get_package` (§8.2.1) to fetch the actual package payload, and never parses the YAML string under that response's `content` key. Confirmed by grep: no caller anywhere in `src/cleveractors` outside `registry/client.py`/`registry/cache.py` themselves invokes `get_package`/`get_package_content`. Repro: 1. A graph references an `agent`-type package by a bare REGISTRY-scheme string, e.g.: ```yaml agents: calculator_builder: "192.168.1.53:luis-mendes/calculator-app-builder" ``` where the referenced package (as published on the registry) is a real `type: llm` agent (`system_prompt`, `tools`, `provider`, `model`, etc. — confirmed present in the equivalent local package, `calculator-app-builder.yaml`). 2. Run: `python test_app.py test --graph <file>.yaml --prompt "..." --registry-key "<key>"` against that registry. 3. Observe: `Error: Execution failed: Unknown agent type: agent`. This happens because the dict `AgentFactory.acreate_agent` receives has `type` == `"agent"` (the §8.2.2 stub's package-type field, echoed straight through), not `"llm"` (the real agent's `type`, which lives in the §8.2.1 `content` payload that was never fetched) — and none of `system_prompt`/`tools`/`provider`/`model` are present either, since none of the actual package content was ever retrieved. ## Expected behavior `PackageContentResolver.resolve()`/`aresolve()`, for a REGISTRY reference, performs both required calls in sequence — §8.2.2 to obtain `package_id`, then §8.2.1 to fetch and parse that package's real `content` — and returns the parsed content (with `type`/`config` at the top level, per `docs/index.md` §4.1.1) merged with the existing `_original_reference`/`_server`/`_namespace`/`_name`/`_version`/`package_id` enrichment fields. A `type: llm` agent referenced this way constructs successfully via `AgentFactory.acreate_agent`/`AgentFactory.create_agent`, matching the behavior already correct for `local:` references (which read a `LocalPackage`'s already-parsed `content` directly). ## Acceptance criteria - [ ] `PackageContentResolver.resolve()`/`aresolve()` for a REGISTRY reference calls `get_package`/`get_package_content` with the `package_id` obtained from `resolve_package()`, and parses the returned `content` YAML string into a dict. - [ ] The dict returned by `resolve()`/`aresolve()` for a REGISTRY reference has `type`/`config` (or whatever fields the real package defines) from the actual package payload — never the §8.2.2 stub's package-type-name `type` field. - [ ] A `type: llm` agent referenced via a bare REGISTRY-scheme string in `agents.<name>` (e.g. `agents.worker: "host:ns/name"`) creates successfully through `AgentFactory.acreate_agent`, instead of raising `AgentCreationError("Unknown agent type: <package_type>")`. - [ ] The existing resolution cache (`PackageContentResolver.cache`, keyed via `_compute_cache_key`) caches the *fully resolved* content (post-`get_package`), not the intermediate §8.2.2 stub. - [ ] The per-server `RegistryCache` content tier (`get_package`) is used when available (mirroring the existing `resolve_package` cache/no-cache branching already in `_resolve_registry_async`), falling back to the raw `RegistryClient.get_package` otherwise. - [ ] No regression to `local:`/`ID:` reference resolution, which is unaffected by this defect. ## Supporting information - `src/cleveractors/registry/reference_resolver.py` (`_RegistryReferenceStrategy`, `PackageContentResolver._resolve_registry_async`) - `src/cleveractors/registry/client.py` (`RegistryClient.get_package` §8.2.1, `RegistryClient.resolve_package` §8.2.2) - `src/cleveractors/registry/cache.py` (`RegistryCache.get_package`, `RegistryCache.resolve_package`) - `docs/actor-registry-standard.md` §8.2.1, §8.2.2 - `docs/index.md` §4.1.1 (Agent Package References), §5.1.1 (Route Package References) - `docs/adr/ADR-2037-agent-and-route-package-reference-resolution.md` (D-2, D-3, D-4, D-7, D-8 — this defect violates D-3's assumption that the resolved dict already has `type`/`config` at the top level) - `docs/adr/ADR-2034-skill-package-support.md` (the `skills:` resolution path this ADR mirrors shares the identical defect, via the same `PackageContentResolver`) - Sibling registry bug (already fixed, different defect): #131/#132 — that fix corrected the §8.2.2 URL's `{package_type}` segment (full type name vs. Package-ID prefix); it did not touch the missing §8.2.1 fetch this issue describes. ## Subtasks - [ ] Fix `_resolve_registry_async` (`src/cleveractors/registry/reference_resolver.py`) to call `get_package`/`get_package_content` with the resolved `package_id`, parse the returned `content` YAML string, and merge that parsed content (not the §8.2.2 stub) into the dict returned by `resolve()`/`aresolve()`. - [ ] Route the `get_package` call through the per-server `RegistryCache` when available, mirroring the existing `resolve_package` cache/no-cache branching in `_resolve_registry_async`. - [ ] Ensure the resolution cache (`_put_cache`/`self.cache`) stores the fully-resolved (post-`get_package`) content, not the intermediate stub. - [ ] Tests (Behave): extend `features/registry_http_client.feature` with scenarios proving an `agents.<name>` (and `skills:`, `routes.<name>`) REGISTRY reference resolved through `PackageContentResolver.resolve()`/`aresolve()` returns real package content (`type`/`config` from the package payload), not the resolve-endpoint stub. - [ ] Tests (Robot): add/extend an integration scenario exercising a `type: llm` agent referenced via a REGISTRY-scheme string end-to-end against a registry test double. - [ ] Verify coverage >= 97% via `nox -s coverage_report`. - [ ] Run `nox` (all default sessions), fix any errors. ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the first line matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details. - The commit is pushed to the branch matching the Branch in Metadata exactly. - The commit is submitted as a pull request to `master`, reviewed, and merged.
CoreRasurae added this to the v2.1.0 milestone 2026-08-20 21:39:16 +00:00
CoreRasurae added reference bugfix/m1-registry-content-fetch 2026-08-20 21:45:11 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveractors-core#135
No description provided.