Skip to content

ADR-0015: WoT AgentCard alignment

  • Status: Accepted (2026-04-19)
  • Date: 2026-04-14

Context

Trails' CapabilityDescriptor (14 fields, rust/crates/trails-caps/src/lib.rs line 77; canonicalised by design spec §3.2.5 and ADR-0005) is bespoke. Semantic-aware consumers currently have two choices: speak MCP (a downgraded projection that discards preconditions, cost, assurance, policy — see ADR-0005, ADR-0008) or parse Trails-native JSON-LD whose vocabulary exists nowhere else.

Meanwhile, the W3C Web of Things (WoT) Thing Description (TD 1.1) Recommendation is consolidating the adjacent "what is this thing and what can it do" problem with a full JSON-LD 1.1 vocabulary, a mature tooling ecosystem (Eclipse Thingweb's node-wot, TD Playground, SHACL validators), and an ActionAffordance construct whose shape is remarkably close to CapabilityDescriptor. A sibling agent-platform project (Fabrica) already made fabrica:AgentCard rdfs:subClassOf wot:Thing its default and reports zero-glue interoperability with WoT consumers: TD-compliant clients can introspect Fabrica agents as if they were WoT Things.

The gap for Trails is interoperability-shaped:

  • No WoT-native tool can read a Trails agent today.
  • Trails-specific fields (assurance, policy_required, cost_estimate, consent_required, ACT/ECT — ADR-0013) have no WoT analogue and would have to survive any alignment.
  • MCP (ADR-0008) and WoT are non-competing: MCP is a call protocol; WoT TD is a self-describing surface. Both can be projections.

The question this ADR settles: should Trails' self-description be WoT-alignable without abandoning the fields that make Trails Trails?

Decision

Yes, via hybrid alignment. Introduce an AgentCard type whose RDF class is declared rdfs:subClassOf wot:Thing, with capabilities expressed as td:ActionAffordance. Trails-specific metadata stays in a new trails: namespace and rides alongside the WoT fields as owl-compatible extensions.

  • New ontology IRI (proposed): https://trails.dev/ont/core# (prefix trails:). Mirrors Fabrica's pattern (https://fabrica.dev/ont/core#). Resolvable; serves Turtle and HTML per content-negotiation.
  • Subclass axiom: trails:AgentCard rdfs:subClassOf wot:Thing.
  • Capability mapping: each CapabilityDescriptor becomes one td:ActionAffordance entry under td:actions.
  • AgentCard is an additive serializer, not a replacement: the canonical Rust struct (trails-caps::CapabilityDescriptor) and the existing JSON wire format (design spec §3.4.2) are unchanged.

Mapping table — CapabilityDescriptor → WoT / trails:

# Trails field Target IRI / construct Notes
1 id td:name on the ActionAffordance local slot within the Thing
2 version schema:softwareVersion WoT has no version field; reuse schema.org
3 description dct:description WoT uses td:description; we bind both
4 input_shape td:input (DataSchema w/ sh:node pointer) SHACL NodeShape IRI preserved verbatim
5 output_shape td:output (DataSchema w/ sh:node pointer) same
6 preconditions trails:precondition (list of structured refs) no WoT analogue
7 side_effects trails:sideEffect (graph writes, regulated) td:safe=false derived from this
8 cost_estimate trails:costEstimate no WoT analogue
9 policy_required trails:policyRequired Cedar IRIs (ADR-0006); no WoT analogue
10 idempotent td:idempotent (boolean, TD 1.1) exact match
11 deprecates prov:wasRevisionOf reuse PROV (ADR-0009)
12 reasoning trails:reasoningMode none | rdfs | owl-rl
13 assurance trails:assurance L1/L2/L3 per ADR-0013; no WoT analogue
14 version_status trails:versionStatus active/deprecated/retired

Additional Thing-level bindings, independent of per-action fields:

  • @type: [trails:AgentCard, wot:Thing]
  • td:id: DID of the agent (ADR-0011)
  • td:security / td:securityDefinitions: Biscuit bearer scheme (ADR-0010)
  • trails:act, trails:ect: ACT/ECT endpoints (ADR-0013)

Surface sketch — AgentCard (JSON-LD, abridged)

{
  "@context": [
    "https://www.w3.org/2022/wot/td/v1.1",
    {
      "trails": "https://trails.dev/ont/core#",
      "prov":   "http://www.w3.org/ns/prov#",
      "sh":     "http://www.w3.org/ns/shacl#",
      "dct":    "http://purl.org/dc/terms/",
      "schema": "https://schema.org/"
    }
  ],
  "@type": ["trails:AgentCard", "Thing"],
  "id": "did:web:example.org:agents:summarize",
  "title": "Summarize Agent",
  "securityDefinitions": { "biscuit_sc": { "scheme": "bearer", "format": "biscuit" } },
  "security": "biscuit_sc",
  "actions": {
    "summarize": {
      "description": "Return a 3-sentence abstract of an input document.",
      "schema:softwareVersion": "1.2.0",
      "idempotent": true,
      "input":  { "type": "object", "sh:node": "urn:shape:DocumentInput" },
      "output": { "type": "object", "sh:node": "urn:shape:Abstract" },
      "trails:assurance": "L2",
      "trails:costEstimate": { "tokensEstimate": 1200, "usdEstimate": 0.004 },
      "trails:policyRequired": ["urn:policy:summarize.basic"],
      "trails:sideEffect": { "graphWrites": ["urn:g:abstracts"], "regulated": false },
      "trails:reasoningMode": "none",
      "trails:versionStatus": "active",
      "prov:wasRevisionOf": "cap:summarize@1.1.0"
    }
  }
}

Migration plan

  • Phase 1 — Serializer (additive). Add AgentCard JSON-LD emitter in trails-caps. No wire-format change; no existing consumer breaks.
  • Phase 2 — HTTP endpoint. Expose GET /{agent}/card returning the AgentCard with Content-Type: application/td+json (WoT) or application/ld+json.
  • Phase 3 — Ontology file. Publish ontology/trails-core.ttl with the subclass axiom and term definitions; host at https://trails.dev/ont/core (content-negotiated). Mirrors Fabrica.
  • Phase 4 — Endpoint coexistence. Keep /capabilities (MCP-projection endpoint, ADR-0005) and /card (WoT-aligned endpoint) side by side. /capabilities stays because MCP remains primary transport (ADR-0008). No deprecation. tools/list is unchanged.

Consequences

Positive

  • Zero-glue WoT interop. node-wot, Thingweb, TD Playground, and SHACL-over-TD validators can consume Trails agents unmodified.
  • Clearer spec surface. Trails-specific extensions are visibly namespaced as trails:* instead of hidden behind a bespoke descriptor name.
  • Vocabulary reuse. prov:wasRevisionOf, schema:softwareVersion, td:idempotent replace invented terms.
  • Fabrica parity. Makes a cross-project interop story with Fabrica plausible (both are wot:Thing subclasses).

Negative

  • Extra serialization layer (canonical struct → AgentCard JSON-LD) adds code and a test surface. Mitigated by Phase-1 additive scope.
  • @context resolution cost. WoT's context is ~50 KB; naive resolvers re-fetch per request. Mitigated by required client-side cache (documented), bundled context shipped with the crate for offline validation.
  • Third projection to keep consistent (MCP / OpenAPI / AgentCard); drift risk rises. Mitigated by single source of truth in the Rust CapabilityDescriptor and projection-round-trip tests.

Neutral

  • No change to MCP tools/list behaviour. No change to OpenAPI output. No change to the Rust CapabilityDescriptor struct or the 14-field canonical order.

Alternatives considered

  • Status quo — bespoke CapabilityDescriptor only. Rejected: leaves Trails invisible to the entire WoT/Thingweb ecosystem and concedes the "self-describing agent" surface to competitors without a fight.
  • Full WoT replacement — drop CapabilityDescriptor, use only TD. Rejected: TD has no vocabulary for cost, assurance, policy, consent, or ACT/ECT. These are the primitives Trails exists to ship (ADRs 0006, 0010–0013). Adopting TD verbatim would silently erase them.
  • Hybrid — AgentCard ⊑ wot:Thing, Trails extensions in trails:. Accepted. Keeps canonical primitives; gains ecosystem compat; mirrors Fabrica's proven pattern.

Open questions

  • Decorator emission. Does @capability emit an AgentCard entry automatically, or is emission gated behind an opt-in AgentCardBuilder? Preferred: automatic once Phase 1 lands; confirm in ADR-0016.
  • Discovery. Do we also expose /.well-known/wot (WoT TD Discovery directory) so a WoT client can enumerate Trails agents without knowing the URL scheme? Deferred to ADR-0016.
  • MCP coexistence semantics. When tools/list and td:actions disagree (projection drift), which wins for a mixed client? ADR-0005 says canonical wins — but AgentCard is a projection too. Clarify.
  • trails: ontology governance. Who owns https://trails.dev/ont/core? Versioning cadence? Back-compat rules when a field is renamed? Needs a governance ADR before any public publication.
  • SHACL pointer style. sh:node on a TD DataSchema is our own convention; the TD spec is silent. Is this the right embedding, or should we use td:additionalResponses / schema:schemaVersion patterns instead?