Context
Observal is install-first: a person builds an Agent from registry components, publishes it, and pulls it into a harness before a session starts. That model is right for repeatable, governed workflows and wrong for the moment a developer is mid-task and needs a capability that already exists in the registry. It also means the assistant only knows about resources someone installed ahead of time, so it rebuilds things that exist and tells users a capability is unavailable when it is one search away. The Agentic Resource Discovery specification (ARD) defines a common way to describe a resource and a common search API so any client can ask “what exists for this task?” across registries. It deliberately does not define installation, invocation, authentication, review, or telemetry. Observal already has all of those. The fit is: ARD says what exists, Observal says how to use it safely. This record fixes the decisions the discovery work depends on so later phases can be built against a stable target. The full plan lives outside the repo; this ADR is the part that must not drift.Decision 1: Pin the specification
Observal implements ARD v0.91 (Proposal) as published at commitb76f235a8f461876ad4f1e77abd0eb0eb302b48d of
ards-project/ard-spec
(2026-09-12). The JSON Schema, JSON-LD context, CDDL grammar, OpenAPI file and
the official conformance CLI from that commit are vendored unmodified under
observal-server/vendor/ard/ (see VENDORED.md there for provenance and the
update procedure).
The spec is a proposal and will change. Observal tracks it by re-vendoring at
a new commit, updating this ADR, and re-running the conformance tests, never
by hand-editing vendored files or silently accepting whatever the live site
says.
Decision 2: Native models stay authoritative; ARD is a projection
The existingAgent/AgentVersion, McpListing/McpVersion,
SkillListing/SkillVersion, hook, prompt and sandbox tables remain the
source of truth for installation, lifecycle, review and ownership. Discovery
adds a read model, discovery_entries, populated from approved versions and
refreshed on lifecycle events. Nothing that exists today is replaced or
repurposed. The /api/v1/agents and component contracts are unchanged.
Decision 3: Identifier scheme
Every native resource gets exactly one permanent ARD identifier:<deployment-domain>is the host of the deployment’s public URL (deployment.public_urldynamic setting), which satisfies ARD’s requirement that the publisher segment be a fully qualified domain name.<kind>is one ofagent,mcp,skill,hook,prompt,sandboxand occupies ARD’s namespace segment.<uuid>is the native listing’s primary key and occupies ARD’s name segment.
urn:air:observal.acme.com:skill:5f2c1a9e-8b1d-4d0c-9c4e-1f2a3b4c5d6e
Why the UUID and not namespace/slug. ARD requires the identifier to be
a stable handle that survives the resource moving. Observal’s namespace/slug
changes on rename and on transfer-owner. The UUID never changes. The
human-readable identity is still carried, as obs:nativeRef
(namespace/slug@version), so clients can pivot to existing commands.
The publisher domain is pinned. It is derived from deployment.public_url
the first time a real fully qualified domain is seen and stored in the
discovery.publisher_domain setting; after that, identifiers do not follow the
public URL if the deployment moves hosts. Until a real domain is configured,
identifiers use the placeholder observal.local and are expected to change
once. Changing the pinned setting deliberately renames every identifier and is
an administrator action. The artifact URL, by contrast, is a location and
always follows the current public URL. Single-label hosts and IP addresses are
never accepted as publishers.
Externally ingested entries keep the identifier their publisher assigned.
Observal never rewrites another publisher’s URN.
Decision 4: Endpoint base and well-known paths
https://<deployment>/api/v1/ard as its operational
base through an entry of type application/ai-registry+json in the manifest.
ARD defines no operation for fetching a complete entry by identifier; clients
are expected to fetch the artifact url. Observal adds /entries/{identifier}
for its own CLI and UI. It is not part of the conformance surface.
Decision 5: federation when omitted
The spec’s default for the federation field is auto. Observal honours that
literally: an omitted field is treated as auto, bounded by the
administrator’s upstream allowlist. With the default empty allowlist this is
functionally local-only. When an admin allows upstreams, auto and
referrals only ever reach those.
Rejected alternative: default to none. That would make a conformant client
receive different behaviour than the spec promises, and the privacy goal is
achieved just as well by an empty allowlist.
Search text may contain sensitive information (incident details, unreleased
names, internal service names). It never leaves the deployment unless an admin
has allowed a specific upstream, and results from upstreams always carry their
source.
Decision 6: Visibility
Discovery narrows the signed-in user’s registry view to their own namespace and teamspaces they currently belong to. This is intentionally stricter than registry browsing/install for public resources in other namespaces: a public resource can be installed by reference without appearing in a user’s ARD search. The same scope applies to search, list, inspect, and versioned artifact reads, including imported A2A agents. Membership is checked at query time, so removing a member revokes their ARD access to that team’s public and private entries. Submitter status alone does not grant access to an old teamspace. Two filters apply, in order:- Who can see the listing — ordinary signed-in users see personal listings they own (public or private) and listings in all teamspaces where they are current members (public or private). Global reviewers retain their public review scope; admins and super-admins still see everything. Anonymous calls retain the public-catalog behavior described below.
- Which lifecycle states are returned — approved versions to everyone
who passes filter 1; pending and rejected versions only to users whose
effective component permission is
owner(the same rule install uses for owner fallback); archived entries excluded unless the query filters onobs:lifecycleexplicitly.
deployment.public_registry_enabled setting is on (default
off), the same switch that governs anonymous browsing elsewhere. When it is
off, unauthenticated search returns an empty result set and the well-known
manifest carries only the registry’s own application/ai-registry+json entry,
both of which are conformant. The well-known manifest is always computed as an
unauthenticated request.
Decision 7: Media types and compatibility inputs
Emitted (canonical) types:
An Observal Agent is an installable configuration, not a running service, so it
is never emitted as
application/a2a-agent-card+json. (ADR 0002 adds remote A2A
agents as imported entries of that type, and delegation to both kinds.)
Accepted and normalised on input, with the original preserved in raw_entry:
Decision 8: Relevance is the only thing in score
ARD’s score (0–100) is semantic relevance and nothing else. Approval,
publisher verification, liveness, harness compatibility and adoption are
returned as separate, named fields (obs:approval, obs:trust,
obs:availability, obs:supportedHarnesses, …) and are never blended into
score. Policy eligibility is applied as a filter before ranking, not as a
weight inside it.
Decision 9: What is deliberately deferred
- Web ingestion. ARD §5.2 says every registry MUST crawl entry sources. Observal defers this to the external-sources phase. The vendored conformance tool passes without it; until that phase ships, Observal is an ARD publisher and search endpoint, not a complete ARD registry, and says so here rather than claiming otherwise.
- Explore. Optional in the spec; added with search-quality work.
- Trust manifest verification. Entries Observal publishes carry a
trustManifest.identityofhttps://<publisher-domain>(an HTTPS FQDN URI string,identityType: "https") bound to the URN’s publisher segment. Verification of third-party manifests arrives with external ingestion.
Decision 10: Search baseline needs no external service
The first search implementation is PostgreSQL full-text search pluspg_trgm
(already enabled). It must remain a complete, supported configuration. Dense
retrieval is an optional later addition behind a provider interface and an
explicit infrastructure change (pgvector); its absence never disables
discovery.
Consequences
- Discovery reaches all ten harnesses through the bundled skill and the CLI, which already ship to every harness on login. No new runtime is required for the first milestone.
- Per-harness behaviour (how MCP is installed, whether tools refresh mid-session, whether a hook can inject context) is recorded as verified facts in the shared harness registry and tested, so later phases gate on data rather than assumptions.
- Anything that permanently changes a developer’s setup, or that can write or delete, keeps asking first. Approved skills and prompts, being text, may be loaded automatically.
- Conformance is tested in CI against the vendored tool at the pinned commit. A spec change is a deliberate re-vendor, reviewed like any other dependency update.

