Skip to content

Python SDK

PyPI version

The Directory Python SDK (agntcy-dir) provides a high-level client for interacting with the Directory gRPC API from Python 3.10+ applications.

Source and issue tracker: github.com/agntcy/dir-sdk-python

Features

API Methods
Store push, pull, lookup, delete, push_referrer, pull_referrer, delete_referrer
Search search_records, search_cids
Routing publish, unpublish, list, search_routing
Publication create_publication, get_publication, list_publication
Naming resolve, get_verification_info
Sync create_sync, get_sync, list_syncs, delete_sync
Events listen (server-streaming)
Signing sign (local via dirctl), verify (remote gRPC)

Installation

Requires uv.

  1. Initialize the project:

    uv init
    
  2. Add the SDK — the buf.build index is required for the generated protobuf packages:

    uv add agntcy-dir --index https://buf.build/gen/python
    

Configuration

Environment variables

# Insecure (local development)
export DIRECTORY_CLIENT_SERVER_ADDRESS="localhost:8888"
export DIRCTL_PATH="/path/to/dirctl"

# X.509 / SPIFFE
export DIRECTORY_CLIENT_SERVER_ADDRESS="localhost:8888"
export DIRECTORY_CLIENT_AUTH_MODE="x509"
export DIRECTORY_CLIENT_SPIFFE_SOCKET_PATH="/tmp/agent.sock"

# JWT / SPIFFE
export DIRECTORY_CLIENT_AUTH_MODE="jwt"
export DIRECTORY_CLIENT_SPIFFE_SOCKET_PATH="/tmp/agent.sock"
export DIRECTORY_CLIENT_JWT_AUDIENCE="spiffe://example.org/dir-server"

# TLS (custom CA / mTLS)
export DIRECTORY_CLIENT_AUTH_MODE="tls"
export DIRECTORY_CLIENT_TLS_CA_FILE="/path/to/ca.crt"
export DIRECTORY_CLIENT_TLS_CERT_FILE="/path/to/client.crt"
export DIRECTORY_CLIENT_TLS_KEY_FILE="/path/to/client.key"

Then create a client from the environment:

from agntcy.dir_sdk.client import Client

client = Client()  # reads DIRECTORY_CLIENT_* env vars

Direct instantiation

from agntcy.dir_sdk.client import Config, Client

# Insecure (local development only)
config = Config(
    server_address="localhost:8888",
    dirctl_path="/usr/local/bin/dirctl",
)
client = Client(config)

# X.509 with SPIRE
config = Config(
    server_address="localhost:8888",
    spiffe_socket_path="/tmp/agent.sock",
    auth_mode="x509",
)
client = Client(config)

# JWT with SPIRE
config = Config(
    server_address="localhost:8888",
    spiffe_socket_path="/tmp/agent.sock",
    auth_mode="jwt",
    jwt_audience="spiffe://example.org/dir-server",
)
client = Client(config)

Docker-based dirctl

If you don't have dirctl installed locally you can run it via Docker by passing a DockerConfig instead of dirctl_path:

from agntcy.dir_sdk.client import Config, Client
from agntcy.dir_sdk.client.config import DockerConfig

config = Config(
    server_address="localhost:8888",
    docker_config=DockerConfig(
        dirctl_image="ghcr.io/agntcy/dir-ctl",
        dirctl_image_tag="latest",
    ),
)
client = Client(config)

Note

dirctl_path and docker_config are mutually exclusive. Setting both raises a ValueError.

OAuth 2.0 / OIDC bearer auth

Use auth_mode="oidc" when your deployment expects a Bearer token on gRPC (e.g. via an Envoy gateway that validates OIDC tokens).

export DIRECTORY_CLIENT_AUTH_MODE="oidc"
export DIRECTORY_CLIENT_SERVER_ADDRESS="directory.example.com:443"
export DIRECTORY_CLIENT_OIDC_ISSUER="https://your-idp-provider.example.com"
export DIRECTORY_CLIENT_OIDC_CLIENT_ID="your-app-client-id"
# Optional — use a random placeholder for public clients:
export DIRECTORY_CLIENT_OIDC_CLIENT_SECRET="random-non-secret-string"
export DIRECTORY_CLIENT_OIDC_REDIRECT_URI="http://localhost:8484/callback"
# Optional: comma-separated extra scopes
export DIRECTORY_CLIENT_OIDC_SCOPES="openid,profile,email"
# Optional: non-interactive use — skip the browser flow
export DIRECTORY_CLIENT_AUTH_TOKEN="your-access-token"

Interactive PKCE login (opens a browser):

from agntcy.dir_sdk.client import Client, Config, OAuthPkceError

config = Config(
    server_address="directory.example.com:443",
    auth_mode="oidc",
    oidc_issuer="https://your-idp-provider.example.com",
    oidc_client_id="your-app-client-id",
    oidc_redirect_uri="http://localhost:8484/callback",
)
client = Client(config)

try:
    if not client.has_cached_oauth_token():
        client.authenticate_oauth_pkce()
except OAuthPkceError as e:
    print(f"Login failed: {e}")

Non-interactive (CI / pre-issued token):

config = Config(
    server_address="directory.example.com:443",
    auth_mode="oidc",
    auth_token="your-access-token",
)
client = Client(config)

Interactive sessions are cached at $XDG_CONFIG_HOME/dirctl/auth-token.json (or ~/.config/dirctl/auth-token.json) and shared with the dirctl CLI.

Note

TLS_SKIP_VERIFY applies only to HTTPS calls to the OIDC issuer (discovery and token endpoints), not to the gRPC TLS connection to the Directory server.

Usage

Store — push and pull records

import json
from agntcy.dir_sdk.client import Client
from agntcy.dir_sdk.models import core_v1

client = Client()

# Push a record
record = core_v1.Record()
record.blob = json.dumps({"name": "my-agent", "version": "1.0.0"}).encode()

refs = client.push([record])
cid = refs[0].cid
print(f"Pushed record CID: {cid}")

# Pull it back
records = client.pull(refs)

# Look up metadata only (no content download)
metas = client.lookup(refs)

Routing — publish and discover

from agntcy.dir_sdk.models import routing_v1

# Publish a CID to the routing layer
client.publish(routing_v1.PublishRequest(cid=cid))

# List published records
responses = client.list(routing_v1.ListRequest())
for resp in responses:
    print(resp.cid)

# Search the routing layer
results = client.search_routing(routing_v1.SearchRequest(query="my skill"))

# Unpublish
client.unpublish(routing_v1.UnpublishRequest(cid=cid))
from agntcy.dir_sdk.models import search_v1

# Full-text / semantic record search
results = client.search_records(
    search_v1.SearchRecordsRequest(query="my-agent")
)

# CID-only search
cids = client.search_cids(
    search_v1.SearchCIDsRequest(query="my-agent")
)

Naming — resolve and verify

# Resolve a name to a record reference
resp = client.resolve("my-agent", version="1.0.0")
print(resp.ref.cid)

# Get signing/verification metadata
info = client.get_verification_info(name="my-agent", version="1.0.0")

Events — real-time streaming

from agntcy.dir_sdk.models import events_v1

stream = client.listen(events_v1.ListenRequest())
for event in stream:
    print(event)

Signing and verification

from agntcy.dir_sdk.models import sign_v1

# Sign a record locally (requires dirctl binary)
client.sign(sign_v1.SignRequest(cid=cid))

# Verify a record signature via gRPC
resp = client.verify(sign_v1.VerifyRequest(cid=cid))
print(resp.valid)

Error handling

The SDK raises grpc.RpcError for gRPC errors and RuntimeError for configuration problems:

import grpc
from agntcy.dir_sdk.client import Client, OAuthPkceError

try:
    client = Client()
    records = client.pull([ref])
except grpc.RpcError as e:
    if e.code() == grpc.StatusCode.NOT_FOUND:
        print("Record not found")
    elif e.code() == grpc.StatusCode.UNAVAILABLE:
        print("Server unavailable")
    else:
        print(f"gRPC error: {e.details()}")
except RuntimeError as e:
    print(f"Configuration error: {e}")
except OAuthPkceError as e:
    print(f"OAuth error: {e}")

Common status codes: NOT_FOUND, ALREADY_EXISTS, UNAVAILABLE, PERMISSION_DENIED, INVALID_ARGUMENT.

Prerequisites

Examples

examples/example.py

Source: examples/example.py

Demonstrates the core store and routing workflow end-to-end:

  1. Pushes two OASF records and prints their CIDs
  2. Pulls the records back and prints their content
  3. Looks up record metadata (without re-downloading content)
  4. Publishes one record to the routing layer
  5. Lists routing entries filtered by skill
  6. Searches CIDs by version pattern (v1.*)
  7. Searches CIDs by annotation key:value (env:prod)
  8. Unpublishes the record
  9. Deletes both records from the store

Run:

cd examples
uv sync
uv run example.py

examples/example_interactive_oidc.py

Source: examples/example_interactive_oidc.py

Demonstrates OIDC/PKCE interactive login. Reuses a cached token if one exists, otherwise opens the system browser for the authorization flow. Once authenticated, runs a SearchCIDs query filtered by version.

Required environment variable:

export DIRECTORY_CLIENT_OIDC_CLIENT_ID="your-client-id"

Optional overrides: DIRECTORY_CLIENT_SERVER_ADDRESS, DIRECTORY_CLIENT_OIDC_CLIENT_SECRET, DIRECTORY_CLIENT_OIDC_REDIRECT_URI, DIRECTORY_CLIENT_OIDC_CALLBACK_PORT, DIRECTORY_CLIENT_AUTH_TOKEN (skips the browser flow when pre-issued token is available).

Run:

cd examples
uv sync
uv run example_interactive_oidc.py --version "v1*" --limit 3