Wyrd docs v0.2.0 Reference GitHub

Cards, versions, and relationships

A Card is a versioned declaration: one model, prompt, agent, or dataset in your system, or one rule for checking them. You declare a whole service as a folder of Card files in YAML, whatever language the service is written in, and a Service Card ties the set together by naming its components.

Wyrd has 14 Card kinds. These are the ones a service declaration uses most:

CardDeclares
ServiceThe deployed service and the components it is built from
AgentAn agent: its prompt, model, and the tools it may call
PromptA versioned prompt template
ModelA trained model and its artifact
DataA dataset, such as a training set or a baseline
VerifierA check on how a component behaves
TriggerWhen a Verifier runs
OperatorWhat happens when a check fails

Not every Card starts in YAML. In a traditional ML workflow, you declare Data and Model Cards from code as they are produced, with the Python SDK.

The examples on this page use the support-desk Service in examples/support-desk-app/service/, the same Cards the quickstart runs: an agent that answers customer questions, the prompt it runs, three Verifiers that check it, and the judge Agent one of those Verifiers calls.

Every Card has the same shape

Each Card is a YAML document with six top-level fields. You write the first four; the server writes the rest. Switch tabs to see how the Service, its Agent, and the Prompt fit together:

service/support-desk.yaml yaml
apiVersion: wyrd/v1
kind: Service
metadata:
  name: support-desk
  version: 1.0.0
  space: default
spec:
  service_type: agent
  # The Service's own Verifier checks every answer the desk gives.
  verified_by:
    - verifier: ./no-refund-promise.yaml
  tables:
    - name: tickets
      schema:
        type: object
        properties:
          question: {type: string}
          answer: {type: string}
        required: [question, answer]
  components:
    - alias: agent
      ref: ./support-agent.yaml
      verified_by:
        - verifier: ./ticket-complete.yaml
        - verifier: ./follows-policy.yaml
    - alias: prompt
      ref: ./support-prompt.yaml
FieldWho writes itHolds
apiVersionYouAlways wyrd/v1; a Card without it is rejected
kindYouOne of the Card kinds above
metadataYouspace, name, and optionally version, bump, labels, and annotations
specYouThe kind-specific body
relationshipsThe serveroutbound_refs and inbound_refs: every Card this one references and every Card that references it
statusThe serverphase (pending, active, deprecated, failed, expired, or deleted), message, updated_at, and verification, the state of its bindings and Triggers

A Card is identified by space, kind, name, and version. A space groups related Cards, such as a team or a domain. Leave it out and the CLI and SDKs fill it in from wyrd.toml, [kind.<Kind>] before [defaults], or use default.

A Service names its components

The Service Card is the root of the declaration. Each component gets an alias, the name your code uses to reach it at runtime, such as run.for_card("agent"). verified_by binds Verifiers: on the Service itself, or on one component. tables declares vala.datasets.tickets, which registration creates in Bifrost.

Declaring support-desk.yaml declares every Card it reaches, dependencies first. One root is required: Cards nothing references are allowed only if exactly one of them is a Service, and with a Service root every Verifier in the folder must be bound. Define a Service and Declare and inspect walk through it.

Three ways to reference another Card

Wherever one Card points at another, you can write the reference three ways.

FormExampleUse it when
Declared Cardref: {kind: Model, name: churn-classifier, version: 1.4.2}The Card already exists in the registry
File pathref: ./support-agent.yamlThe Card file is in your declaration; Wyrd declares it first
Inlineprompt: {provider: openai, model: gpt-6-luna, messages: [...]}The body only makes sense inside its parent

Rules for each form:

FormRule
Declared CardNames an exact version, such as 1.4.2
File pathRelative to the file that contains it, not to where you run wyrd declare; the Card in that file sets its own metadata.version
InlineWritten directly under the field, with no wrapper key; allowed only for an Agent’s prompt, a Verifier’s agent, and a binding’s runs_on and on_failure

An inline body gets no identity of its own, with one exception: an Agent’s inline prompt is declared as a Prompt Card named <agent-name>-prompt, in the Agent’s space, so state.agent(alias).prompt() returns it like any other Prompt Card.

Versions are exact

Every reference names an exact version, so a declared Service records exactly which components it was built from. On the Card itself, version is optional:

You write in metadataWyrd records
No version, first declaration0.1.0
No version, content changedThe next patch after the latest declared version
No version, content unchangedNothing new; the existing version stands
version: 1 or version: 1.2The next version within that line, or 1.0.0 / 1.2.0 if the line is empty
version: 2.1.0Exactly 2.1.0; re-declaring it with different content fails with WYRD_REGISTRY_409_SPEC_DRIFT
bump: minor with no version or a partial oneBumps that part instead of the patch; major, minor, and patch (the default) are allowed, and an exact version refuses it

So support-desk@1.0.0 is stored with support-agent@1.0.0, support-prompt@1.0.0, and the exact Verifier versions it binds. The same holds for a declared reference: if the desk referenced churn-classifier@1.4.2, declaring churn-classifier@1.5.0 later would not change the Service. To pick up the new model, change the reference to 1.5.0 and re-apply; the Service content changed, so Wyrd records a new Service version.

This is what makes a verdict reproducible. A verdict always names the Service version it checked, and that version names exactly which model, prompt, and agent ran.

Declare Cards from code

Data and Model Cards carry bytes, such as a training set or model weights, so you usually declare them from the code that produces them. support-desk has no trained model, but say you add one that scores how likely a customer is to churn. The Python SDK saves the artifact, uploads it, and declares the Card in one call.

train.py python
import pandas as pd
from sklearn.linear_model import LogisticRegression

from wyrd import Cards
from wyrd.data import DataCard, PandasInterface
from wyrd.model import ModelCard, ModelCardMetadata, ModelSignature, SklearnInterface

cards = Cards()  # server URL and credential come from your Wyrd config

train = pd.read_parquet("churn-training.parquet")
data = DataCard(PandasInterface(data=train), space="default", name="churn-training", version="1.4.2")
cards.data.declare(data)

features = train[["tenure_months", "monthly_spend"]]
model = LogisticRegression().fit(features, train["churned"])
signature = ModelSignature.from_data(inputs=features, outputs=model.predict_proba(features)[:, 1])
card = ModelCard(
    SklearnInterface(model=model),
    space="default",
    name="churn-classifier",
    version="1.4.2",
    metadata=ModelCardMetadata(task_type="binary_classification", signature=signature),
)
cards.model.declare(card)

A ModelCard needs a signature, the names and types of the model’s inputs and outputs; without one, construction fails with WYRD_MODEL_400_MISSING_SIGNATURE. Code-built Cards default to version 0.1.0, so set version yourself. declare updates the card object in place with its server-assigned uid. The Service can now list it as a component with ref: { kind: Model, name: churn-classifier, version: 1.4.2 }.

Python can declare a Prompt from code the same way, with cards.prompt.declare. The Rust and TypeScript SDKs do not build Cards in code; they declare Card files, including Data and Model files that list artifacts:, with declare_from_path and declareFromPath.

Relationships come from references

You never write relationships yourself. When a Card is declared, the server reads every reference in its spec, components and bindings alike, and records the edge in both directions. For support-desk that graph is:

text
Service support-desk
├── agent:  Agent support-agent
│           └── Prompt support-prompt
├── prompt: Prompt support-prompt
├── Verifier no-refund-promise
├── Verifier ticket-complete
└── Verifier follows-policy
            └── Agent policy-judge

Components carry their alias in outbound_refs; binding edges have none.

The graph gives you lineage. From any Card you can walk to everything it depends on and everything that depends on it, so before changing support-prompt you can see every Agent and Service that uses it.

It also scopes observations. A running service may only record observations about Cards in its own graph: support-desk can record them for support-agent or support-prompt, but not for a Card it does not reference. See Runs, observations, and retained evidence.

What can go wrong

Error codeCauseFix
WYRD_CLI_400_CARD_LOAD from wyrd declare --check or wyrd declare, WYRD_LOADER_400_INVALID_ENVELOPE from an SDKA file failed to load; each problem is listed with its fileFix the files listed
WYRD_REGISTRY_400_UNRESOLVED_PATH_REF, after WYRD_LOADER_400_IOA ref: path points at no file; the message names the file that holds the refFix the path, relative to that file
WYRD_SPEC_400_INVALID_SERVICE_COMPONENT_KINDA Verifier, Trigger, Operator, Audit, or Source is listed as a componentBind it with verified_by instead
WYRD_SPEC_400_UNBOUND_VERIFIER_PEERA Verifier file in the folder is not bound to anythingBind it, or move it out of the folder
WYRD_REGISTRY_409_SPEC_DRIFTAn exact version is already declared with different contentBump the version
WYRD_REGISTRY_422_UNRESOLVED_DEPENDENCYA declared-Card reference names a Card that does not existDeclare it first, or fix the reference
WYRD_PERMISSION_403_DENIED_RBACYour credential lacks cards:writeUse a credential that can declare Cards

Next, How verification works shows how Verifiers attach to these Cards.

Last updated