Skip to main content

akgentic-catalog

CI Coverage

Configuration management for the Akgentic multi-agent framework. Store, query, clone, validate, and resolve versioned configuration entries (teams, agents, tools, prompts, models, and any allowlisted Pydantic model) through a single unified Catalog service backed by a pluggable EntryRepository.

Table of Contents

Overview

Version 2 of akgentic-catalog replaces the v1 four-catalog split (templates, tools, agents, teams each with its own service, repository, model, and query) with a single Entry model, a single Catalog service, and a single EntryRepository protocol. An entry is identified by the compound key (kind, namespace, id) and carries an opaque, schema-validated payload sized for any allowlisted Pydantic model type.

Key properties:

  • Unified Entry model — one Pydantic shape for every kind of configuration. Built-in kinds include team, agent, tool, prompt, and model, and arbitrary new kinds are allowed as long as the payload's model_type resolves through the configured prefix allowlist (akgentic. always, widenable per deployment).
  • Namespaces as tenancy / environment boundaries. Each namespace is a self-contained bundle: one team root entry plus any number of sub-entries referencing it.
  • Two-phase ref model — sub-entries embed sentinel {"__ref__": "<id>", "__type__": "<model_type>"} dicts where the team references them; the resolver walks these refs (with cycle detection) to produce a fully-populated runtime object.
  • Pluggable storage — YAML-file-per-entry and MongoDB single-collection backends ship in the box behind the EntryRepository protocol.
  • Namespace bundles — export/import a whole namespace (team + all sub-entries) as a single YAML document for round-tripping between environments.
  • CLI and REST API — manage entries and bundles outside of Python.

Installation

Workspace Installation (Recommended)

This package is designed for use within the Akgentic monorepo workspace:

git clone git@github.com:b12consulting/akgentic-quick-start.git
cd akgentic-quick-start
git submodule update --init --recursive

uv venv
source .venv/bin/activate
uv sync --all-packages --all-extras

All dependencies (akgentic-core, akgentic-llm, akgentic-tool, akgentic-team) resolve automatically via workspace configuration.

Optional Extras

Extra Packages pulled in Enables
api fastapi, uvicorn create_app() FastAPI factory
cli typer, rich ak-catalog console script
mongo pymongo MongoEntryRepository
postgres nagra, psycopg[binary] PostgresEntryRepository, init_db
uv sync --extra api
uv sync --extra cli
uv sync --extra mongo
uv sync --extra postgres
uv sync --all-extras

Quick Start

Create a fresh YAML-backed catalog, seed a team namespace, and resolve it:

import tempfile
from pathlib import Path

from akgentic.catalog import (
    Catalog,
    Entry,
    UNSET_NAMESPACE,
    YamlEntryRepository,
)

with tempfile.TemporaryDirectory() as tmp:
    repo = YamlEntryRepository(Path(tmp))
    catalog = Catalog(repo)

    # Create the team root with a to-be-minted namespace.
    team = Entry(
        id="research-team",
        kind="team",
        namespace=UNSET_NAMESPACE,
        user_id="u1",
        model_type="akgentic.team.models.TeamCard",
        payload={
            "name": "Research Team",
            "entry_point": {
                "__ref__": "lead-agent",
                "__type__": "akgentic.core.AgentCard",
            },
            "members": [],
        },
    )
    team = catalog.create(team)          # namespace replaced by a fresh UUID
    namespace = team.namespace

    # Create a sub-entry in the same namespace.
    agent = catalog.create(Entry(
        id="lead-agent",
        kind="agent",
        namespace=namespace,
        user_id="u1",
        model_type="akgentic.core.AgentCard",
        payload={"role": "Lead", "description": "Coordinates the team"},
    ))

    # Read / resolve.
    stored_team = catalog.get(namespace=namespace, id="research-team")
    team_card = catalog.load_team(namespace)  # TeamCard with refs populated

See the architecture shards for a namespace-bundle walkthrough and YAML authoring guidance.

Architecture

flowchart LR
    PY[Python API] --> CAT
    CLI[ak-catalog CLI] --> CAT
    API[FastAPI /catalog] --> CAT
    CAT[Catalog service] --> RES[resolver.py]
    CAT --> REPO[EntryRepository]
    REPO --> YAML[(YamlEntryRepository)]
    REPO --> MONGO[(MongoEntryRepository)]
    REPO --> POSTGRES[(PostgresEntryRepository)]

The runtime layout under src/akgentic/catalog/ mirrors shard 10:

src/akgentic/catalog/
    __init__.py          Public API (Catalog, Entry, EntryKind, EntryQuery, ...)
    catalog.py           Unified Catalog service (CRUD + clone + resolve + load_team)
    resolver.py          Two-phase ref resolver + allowlisted model loader
    env.py               ${VAR} substitution for YAML payloads
    serialization.py     Namespace bundle load/dump
    validation.py        Namespace-level validation report
    models/              Entry, EntryKind, EntryQuery, CloneRequest, errors
    repositories/        EntryRepository protocol + YAML + Mongo impls
    api/                 FastAPI app + /catalog router
    cli/                 Typer ak-catalog app

Layered invariants (enforced by Catalog)

  • Namespace bootstrap — non-team entries require a pre-existing team entry in the same namespace.
  • Namespace minting — creating a team with namespace=UNSET_NAMESPACE mints a fresh UUID before any other pipeline step runs.
  • Ownership propagation — every sub-entry inherits the team's user_id.
  • Delete guards — deleting an entry referenced by another entry in the same namespace raises CatalogValidationError listing inbound referrers.
  • Clone atomicity — clone collects every intended write in memory and emits them in a single pass; partial failures leave the destination untouched.

The Entry Model

Every catalog row is an Entry:

from akgentic.catalog import Entry, EntryKind

Entry(
    id="lead-agent",              # stable within (kind, namespace)
    kind=EntryKind.AGENT,          # "team" | "agent" | "tool" | "prompt" | "model" | ...
    namespace="tenant-42",         # tenancy / environment boundary
    user_id="u1",                  # ownership; propagated from the team
    model_type="akgentic.core.AgentCard",  # allowlisted Pydantic class
    payload={"role": "Lead", "description": "..."},
)

model_type is a dotted path to a Pydantic BaseModel subclass whose prefix is on the configured allowlist — akgentic. always, plus whatever the deployment authorized (see Registering customer model types). The resolver calls akgentic.catalog.resolver.load_model_type to materialize it. Payloads validate against that class at create/update time.

Registering customer model types

model_type prefixes are a deployment policy. akgentic. is always allowed and is never removable; configuration only widens the set. Point AKGENTIC_CATALOG_MODEL_TYPE_PREFIXES at your own namespace — as a comma-separated list or a JSON array, both parse identically — and catalog entries may name your classes:

# Comma-separated — or, equivalently, a JSON array:
export AKGENTIC_CATALOG_MODEL_TYPE_PREFIXES=acme.core.models.,contoso.models.
export AKGENTIC_CATALOG_MODEL_TYPE_PREFIXES='["acme.core.models.","contoso.models."]'
id: case-ingestion
kind: tool
namespace: acme-prod
user_id: anonymous
model_type: acme.core.models.CaseIngestionConfig
description: Case ingestion settings
payload: { source: sftp, batch_size: 200 }

Prefer the narrowest prefix — acme.core.models., not acme.: every module under an allowed prefix becomes something a catalog entry can cause to be imported, so a prefix is a blast radius, not just a gate. Give every process the same value — server, worker, and CLI — or one process will accept an entry another refuses to resolve. The setting is startup-only, process-wide, and never reachable from the HTTP surface; set_allowed_prefixes(["acme.core.models."]) from akgentic.catalog, called during startup wiring before the first Entry is constructed or resolved, is the in-code equivalent.

Storage Backends

YAML (default)

YamlEntryRepository(root) lays out one file per entry, namespaced directory per namespace, partitioned by kind:

<root>/
  <namespace>/
    team/research-team.yaml
    agent/lead-agent.yaml
    tool/web-search.yaml
from akgentic.catalog import Catalog, YamlEntryRepository
catalog = Catalog(YamlEntryRepository("./catalog"))

MongoDB

MongoEntryRepository stores every entry in a single collection indexed by the compound (kind, namespace, id) key. Install the mongo extra and provide a connection:

from akgentic.catalog import Catalog, MongoCatalogConfig, MongoEntryRepository

cfg = MongoCatalogConfig(
    connection_string="mongodb://localhost:27017",
    database="akgentic",
)
catalog = Catalog(MongoEntryRepository(cfg))

PostgreSQL

PostgresEntryRepository stores every entry in a single catalog_entries table keyed by the compound (namespace, id) primary key. Install the postgres extra and provide a DSN via one of three supply channels (flag-wins precedence on the CLI, explicit kwarg on the API factory):

Supply channel Consumer
PostgresCatalogConfig(connection_string=) create_app() factory
--postgres-conn-string CLI flag ak-catalog
DB_CONN_STRING_PERSISTENCE env var CLI + init-container
from akgentic.catalog import Catalog
from akgentic.catalog.api.app import create_app
from akgentic.catalog.repositories.postgres import (
    PostgresCatalogConfig,
    PostgresEntryRepository,
)

# Programmatic / API path.
cfg = PostgresCatalogConfig(
    connection_string="postgresql://postgres:pw@localhost:5432/catalog",
)
app = create_app(backend="postgres", postgres_config=cfg)

# Direct repository path.
catalog = Catalog(PostgresEntryRepository(cfg.connection_string))

Deployment prerequisite. The PostgresEntryRepository constructor does NOT create the schema — it only validates the DSN. Before starting the catalog service against a fresh database, run the runnable init-container module once per environment (Kubernetes initContainer / Nomad prestart pattern):

DB_CONN_STRING_PERSISTENCE=postgresql://postgres:pw@localhost:5432/catalog \
  python -m akgentic.catalog.scripts.init_db

Exit code 0 on success, 2 when the env var is missing, 1 on any other failure (unreachable host, malformed DSN, driver error).

All three backends expose the same EntryRepository protocol; parity tests under tests/repositories/test_entry_repository_contract.py keep them interchangeable.

Sharing scalars between entries

When several entries need to reuse the same bare scalar — a prompt body, a default role label, a model id — the catalog ships a sanctioned wrapper called NativeValue. A NativeValue entry carries a single value field; the resolver unwraps the value at the ref-splice site so a typed str / int / bool field on the consuming entry receives the bare scalar instead of the wrapper.

# data/catalog/agent-team/prompt/id_team_template.yaml
id: id_team_template
kind: prompt
namespace: agent-team
user_id: anonymous
model_type: akgentic.catalog.NativeValue
description: System-prompt template body for team members
payload:
  value: "You are {role}. Collaborate with your team."

# data/catalog/agent-team/prompt/id_team_role.yaml
id: id_team_role
kind: prompt
namespace: agent-team
user_id: anonymous
model_type: akgentic.catalog.NativeValue
description: Default role label for team members
payload:
  value: "a helpful team member"

# data/catalog/agent-team/prompt/id_team_prompt.yaml
id: id_team_prompt
kind: prompt
namespace: agent-team
user_id: anonymous
model_type: akgentic.llm.prompts.PromptTemplate
description: Default system prompt for team members
payload:
  template: { __ref__: "id_team_template" }   # resolves to str
  params:
    role: { __ref__: "id_team_role" }         # resolves to str

Two things are worth pinning explicitly:

  • The resolver unwraps .value at ref-splice time. From every other layer's perspective — repositories, CLI, HTTP, bundle export — a NativeValue entry is a normal entry with a {"value": <scalar>} payload. The unwrap fires only when a __ref__ marker targets a NativeValue; direct retrieval via Catalog.get returns the Entry like any other entry.
  • NativeValue.value: dict[str, Any] is for JSON literals at boundaries, NOT for structured catalog content. Storing a typed structure under value as an untyped dict effectively bypasses the "payload is a BaseModel" invariant — the consuming side has nothing to validate against. If you need typed structured content, write a real BaseModel. The catalog does not mechanically block this anti-pattern; the discipline is on the catalog author.

See _bmad-output/akgentic-catalog/decisions/adr-15-native-value-refs.md for the full rationale and design.

References Between Entries

Sub-entries are embedded in the team payload (and in each other) as sentinel ref dicts, not by plain ID strings. A ref is a two-key dict:

{"__ref__": "<entry-id>", "__type__": "<model_type>"}

The constants REF_KEY and TYPE_KEY are re-exported from akgentic.catalog for construction/inspection. The resolver walks these refs in two phases — populate_refs (ensures every ref resolves to a known entry) and resolve (materializes the runtime Pydantic object) — with cycle detection. See architecture/05-validation.md and architecture/06-service-and-env.md for the full rules.

Querying the Catalog

EntryQuery is the single query model for all kinds. Any subset of filters may be provided; unspecified filters are ignored.

from akgentic.catalog import EntryQuery

# Every entry in a namespace.
catalog.list_by_namespace("tenant-42")

# Cross-namespace filter.
catalog.list(EntryQuery(kind="agent", user_id="u1"))

# Description substring search.
catalog.list(EntryQuery(kind="tool", description_contains="search"))

CLI

The optional ak-catalog console script (enabled by --extra cli) mounts a Typer app with one subcommand group per kind plus top-level verbs for namespace-scoped and schema operations.

# Kind-scoped CRUD.
ak-catalog --root ./catalog team list --namespace tenant-42
ak-catalog --root ./catalog agent get --namespace tenant-42 lead-agent
ak-catalog --root ./catalog agent create ./lead-agent.yaml

# Namespace bundle round-trip.
ak-catalog --root ./catalog export --namespace tenant-42 > tenant-42.yaml
ak-catalog --root ./catalog import ./tenant-42.yaml

# Validation & schema.
ak-catalog --root ./catalog validate --namespace tenant-42
ak-catalog --root ./catalog validate ./tenant-42.yaml   # dry-run from bundle
ak-catalog schema akgentic.core.AgentCard
ak-catalog model-types                                  # list allowlisted types

Full reference: docs/cli-usage-guide.md.

REST API

The optional FastAPI app (enabled by --extra api) mounts the /catalog router. Start it in-process:

uvicorn "akgentic.catalog:create_app" --factory

Backend is selected via environment variables at app-factory time (YAML by default; set AKGENTIC_CATALOG_BACKEND=mongo plus connection fields for MongoDB). Error responses map catalog exceptions to HTTP:

Status Cause
404 EntryNotFoundError
409 CatalogValidationError
422 Pydantic ValidationError on payload

See src/akgentic/catalog/api/router.py for the full endpoint surface (CRUD per kind, namespace bundle export/import, schema, resolve, validate).

Development

Prerequisites

  • Python 3.12+
  • uv package manager

Setup

uv sync --all-extras

Commands

# Run tests
uv run pytest tests/

# Run tests with coverage
uv run pytest tests/ --cov=akgentic.catalog --cov-fail-under=80

# Lint
uv run ruff check src/ tests/

# Format
uv run ruff format src/ tests/

# Type check
uv run mypy src/

License

This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).

Dual licensing & CLA — Akgentic is available under the AGPL-3.0 open-source license. A commercial license is also planned for organizations that require alternative terms. Contact Yuma for more information. External contributions will be accepted once a Contributor License Agreement (CLA) is in place. Until then, please hold off on submitting pull requests.

Metadata

Release files for akgentic-catalog 2.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for akgentic-catalog 2.2.2
File Size Uploaded
akgentic_catalog-2.2.2.tar.gz 313.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for akgentic-catalog 2.2.2
File Interpreter ABI Platform
akgentic_catalog-2.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 450.7 kB

Release files / akgentic_catalog-2.2.2.tar.gz

Download URL akgentic_catalog-2.2.2.tar.gz
Size 313.3 kB
Tags Source
SHA-256 checksum
How to use checksums
02dac397e87f6c409109d8241509c4831bf50fe2e0995dfaf4b9947fd31ca830
BLAKE2b-256 checksum
How to use checksums
43f76f8454f41c03faa0be4b40d4bcbb4af827d6e14d6217c12ffcac9da23732
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release files / akgentic_catalog-2.2.2-py3-none-any.whl

Download URL akgentic_catalog-2.2.2-py3-none-any.whl
Size 137.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
463541fb94c9e56133baa614daa984e20797c0117ccc9aaa0a31946e014ebb92
BLAKE2b-256 checksum
How to use checksums
a87335d8565ea854b6122fa93d8e9072135bc4bee9121b6138be4b9f1cca1ab3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release history Release notifications | RSS feed

2.2.6

2 release files

2.2.5

2 release files

2.2.4

2 release files

2.2.3

2 release files

This release

2.2.2 This release

2 release files

2.2.0

2 release files

1.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page