Skip to content

feat(registry,federation): namespaces, BM25 search, capability manifests#81

Open
dgenio wants to merge 1 commit into
mainfrom
claude/triage-issues-69bSW
Open

feat(registry,federation): namespaces, BM25 search, capability manifests#81
dgenio wants to merge 1 commit into
mainfrom
claude/triage-issues-69bSW

Conversation

@dgenio
Copy link
Copy Markdown
Owner

@dgenio dgenio commented May 21, 2026

Implements the capability-discovery group from triage: #45 (namespaces &
hierarchical discovery) + #52 (marketplace part 1 — manifest format &
local registry). Both extend the same discovery seam and share models.py.

#45 — CapabilityRegistry

  • Dot-notation capability_ids now expose list_namespaces() and
    list_namespace(prefix). Flat IDs continue to work unchanged.
  • register_namespace(prefix, loader=...) enables deferred registration
    for large tool ecosystems; the loader runs at most once on first
    access (search/list/get).
  • search() gained an offset kwarg, strips a small stop-word set,
    and now scores with a BM25-flavoured ranker that weights matches on
    capability_id and tags above description. Determinism preserved
    via stable capability_id tie-breaking — required by AGENTS.md.

#52 — Capability marketplace (local)

  • New CapabilityDescriptor and CapabilityManifest dataclasses with
    to_dict/from_dict for JSON portability. Internal driver IDs and
    operation names are stripped on serialisation.
  • New agent_kernel.federation module: build_manifest(),
    import_manifest(), merge_sensitivity(). Three trust policies
    honoured at import time (most_restrictive default, local_only,
    remote_deferred).
  • Kernel.advertise(endpoint=...) and Kernel.import_remote(manifest, driver=..., trust_policy=...) thin wrappers; Kernel gained a
    kernel_id kwarg used as the publisher identity (defaults to
    "agent-kernel").
  • Imported capabilities flow through the full local pipeline (policy
    → token → firewall → trace). HMAC tokens remain kernel-scoped — a
    token from a kernel with a different secret fails signature check.

New errors in errors.py: NamespaceNotFound, FederationError,
ManifestError, TrustPolicyError.

Tests: +32 net (450 → 482). New tests/test_federation.py covers
manifest round-trip, descriptor stripping, all three trust policies,
end-to-end imported-capability invocation, and kernel-scoped HMAC
isolation. tests/test_registry.py extended with namespace ops,
deferred-loader semantics, BM25 ranking, pagination, stop-word
stripping, and a 500-capability scale sanity check.

Docs: new docs/federation.md, namespace section added to
docs/capabilities.md, CHANGELOG [Unreleased] entries.

Out of scope (left for follow-ups, per the triage report):

Closes #45
Closes #52

https://claude.ai/code/session_015PFMci84T2TBNhqKsV3Hyo

Implements the capability-discovery group from triage: #45 (namespaces &
hierarchical discovery) + #52 (marketplace part 1 — manifest format &
local registry). Both extend the same discovery seam and share models.py.

#45 — CapabilityRegistry
- Dot-notation `capability_id`s now expose `list_namespaces()` and
  `list_namespace(prefix)`. Flat IDs continue to work unchanged.
- `register_namespace(prefix, loader=...)` enables deferred registration
  for large tool ecosystems; the loader runs at most once on first
  access (search/list/get).
- `search()` gained an `offset` kwarg, strips a small stop-word set,
  and now scores with a BM25-flavoured ranker that weights matches on
  `capability_id` and `tags` above `description`. Determinism preserved
  via stable `capability_id` tie-breaking — required by AGENTS.md.

#52 — Capability marketplace (local)
- New `CapabilityDescriptor` and `CapabilityManifest` dataclasses with
  `to_dict`/`from_dict` for JSON portability. Internal driver IDs and
  operation names are stripped on serialisation.
- New `agent_kernel.federation` module: `build_manifest()`,
  `import_manifest()`, `merge_sensitivity()`. Three trust policies
  honoured at import time (`most_restrictive` default, `local_only`,
  `remote_deferred`).
- `Kernel.advertise(endpoint=...)` and `Kernel.import_remote(manifest,
  driver=..., trust_policy=...)` thin wrappers; `Kernel` gained a
  `kernel_id` kwarg used as the publisher identity (defaults to
  `"agent-kernel"`).
- Imported capabilities flow through the full local pipeline (policy
  → token → firewall → trace). HMAC tokens remain kernel-scoped — a
  token from a kernel with a different secret fails signature check.

New errors in `errors.py`: `NamespaceNotFound`, `FederationError`,
`ManifestError`, `TrustPolicyError`.

Tests: +32 net (450 → 482). New `tests/test_federation.py` covers
manifest round-trip, descriptor stripping, all three trust policies,
end-to-end imported-capability invocation, and kernel-scoped HMAC
isolation. `tests/test_registry.py` extended with namespace ops,
deferred-loader semantics, BM25 ranking, pagination, stop-word
stripping, and a 500-capability scale sanity check.

Docs: new `docs/federation.md`, namespace section added to
`docs/capabilities.md`, CHANGELOG `[Unreleased]` entries.

Out of scope (left for follow-ups, per the triage report):
- Discovery over a network transport (#51, marketplace part 2).
- Decomposition of `policy_dsl.py` / `kernel.py` / `models.py`
  beyond AGENTS.md's 300-line budget (#68).

Closes #45
Closes #52

https://claude.ai/code/session_015PFMci84T2TBNhqKsV3Hyo
Copilot AI review requested due to automatic review settings May 21, 2026 06:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends agent-kernel’s capability discovery seam with (1) hierarchical namespaces + deferred namespace loaders + improved ranked search in CapabilityRegistry, and (2) a local “capability marketplace” foundation via serializable manifests that can be advertised/imported while preserving the local policy→token→firewall pipeline.

Changes:

  • Added namespace operations to CapabilityRegistry (list_namespaces, list_namespace, register_namespace) plus deferred loader support.
  • Replaced keyword-overlap scoring with a deterministic BM25-flavoured scorer, added stop-word stripping and offset pagination.
  • Introduced federation/marketplace primitives: CapabilityDescriptor/CapabilityManifest, build_manifest/import_manifest, and Kernel.advertise/Kernel.import_remote, with new federation-related errors and docs.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tests/test_registry.py Adds coverage for namespaces, deferred loaders, BM25 ranking, pagination, stop words, and a 500-capability scale sanity check.
tests/test_federation.py New tests for manifest round-trips, stripping internal fields, trust policies, imported invocation flow, and HMAC token isolation.
src/agent_kernel/registry.py Implements namespaces, deferred namespace loading, BM25-style ranked search, stop-word stripping, and pagination.
src/agent_kernel/models.py Adds NamespaceMetadata, CapabilityDescriptor, and CapabilityManifest models with to_dict/from_dict.
src/agent_kernel/kernel.py Adds kernel_id, plus advertise() and import_remote() wrappers to publish/import manifests.
src/agent_kernel/federation.py New local federation module implementing manifest build/import and sensitivity merge logic.
src/agent_kernel/errors.py Adds NamespaceNotFound and federation-specific error types.
src/agent_kernel/init.py Re-exports federation models/functions/errors from the top-level package.
docs/federation.md Documents the marketplace part 1 workflow, trust policies, and non-goals.
docs/capabilities.md Documents namespaces, deferred loading, and new search behavior.
CHANGELOG.md Adds Unreleased entries describing the new registry and federation features.

NamespaceNotFound: If no declared namespace or registered capability
lives under *prefix*.
"""
self._maybe_load_namespace(prefix)
Comment on lines +364 to +366
head, _, _ = capability_id.partition(".")
candidates = [head, capability_id]
for prefix in candidates:
Comment on lines +370 to +380
def _maybe_load_namespace(self, prefix: str) -> None:
"""Invoke the deferred loader for *prefix* if it has not run yet."""
meta = self._namespaces.get(prefix)
if meta is None or meta.loaded or meta.loader is None:
return
loader = meta.loader
# Mark as loaded *before* calling so a recursive load doesn't re-enter.
meta.loaded = True
for cap in loader():
self.register(cap)

scored.append((score, cap))

scored.sort(key=lambda x: (-x[0], x[1].capability_id))

Comment on lines +1 to +8
"""Capability registry: register, lookup, namespaced discovery, and ranked search.

Supports dot-notation namespaces (``"billing.invoices.list"``), deferred
namespace loaders for large tool ecosystems, and a BM25-flavoured score
that weights matches on ``capability_id`` and ``tags`` higher than
``description``. Flat (un-namespaced) capability IDs continue to work — they
are treated as living in a single-segment namespace.
"""
"""Dot-notation namespace prefix (e.g. ``"billing"`` or ``"billing.invoices"``)."""

description: str = ""
"""Optional human-readable description shown by ``list_namespaces``."""
Comment on lines +527 to +532
return cls(
capability_id=data["capability_id"],
name=data["name"],
description=data["description"],
safety_class=SafetyClass(data["safety_class"]),
sensitivity=SensitivityTag(data.get("sensitivity", SensitivityTag.NONE.value)),
Comment on lines +592 to +597
return cls(
kernel_id=data["kernel_id"],
version=data["version"],
endpoint=data["endpoint"],
trust_level=data.get("trust_level", "unverified"),
capabilities=[CapabilityDescriptor.from_dict(c) for c in data["capabilities"]],
Comment on lines +687 to +691
# Route each imported capability to its driver so existing
# ``Kernel.invoke`` works unchanged.
router_add = getattr(self._router, "add_route", None)
if router_add is not None:
for cap in imported:
Comment thread tests/test_registry.py
Comment on lines +299 to +305
start = time.perf_counter()
results = reg.search("billing thing", max_results=10)
elapsed = time.perf_counter() - start
assert len(results) == 10
# Generous bound: BM25 over 500 docs with ~5 tokens each should be
# well under a second on any developer machine.
assert elapsed < 1.0, f"search took {elapsed:.3f}s for 500 capabilities"
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.

Capability marketplace part 1: manifest format & local registry Capability namespaces & hierarchical discovery

3 participants