Skip to main content
Glama

Reference implementation of the Context Validity Declaration (CVD) from the position paper The Agent Steward: Context Provenance and Temporal Validity as a Missing Governance Dimension in Multi-Agent Enterprise Ecosystems (Kefreen Lacerda, 2026).

The silent failure

An agent serving organizational context — prices, policies, product facts — that has silently gone out of date passes every technical health check. It responds within SLA. It returns success. It raises no exception. It is simply wrong.

A stale spreadsheet at least shows an old timestamp. A stale agent answer carries no observable signal at all:

{
  "sku": "WIDGET-PRO",
  "unit_price_usd": 4800.00
}

Price list changed 9 days ago. The agent doesn't know. Neither do you. Confidently wrong — and invisible.

{
  "status": "refused",
  "reason": "context_expired",
  "disclosure": "…last validated 14 days
   ago… The accountable steward is
   Ana Souza; contact them to revalidate."
}

Visibly stale — and recoverable. Someone accountable is one message away.

The CVD attaches a small, declarative validity contract to any exposed context (an MCP tool/resource, or an Agent Card) so that obsolescence becomes detectable at runtime.

How it works

flowchart LR
    A["agent calls<br/>guarded tool"] --> B{"age =<br/>now − last_validated"}
    B -->|"age ≤ freshness_slo"| C["✅ FRESH<br/>answer, unchanged"]
    B -->|"age > freshness_slo"| D{"decay_policy<br/>.on_violation"}
    D -->|refuse_with_disclosure| E["🛑 refusal that<br/>names the steward"]
    D -->|warn| F["⚠️ answers,<br/>but flagged"]
    D -->|escalate_to_steward| G["📣 routed to<br/>escalation target"]
    D -->|"unknown policy"| E

One decision point, four outcomes — and the unsafe path (answering on expired context as if nothing happened) does not exist.

The five primitives

A CVD manifest declares exactly five fields. Nothing more:

#

Primitive

Meaning

1

steward

Named identity of the accountable human — a person, not a functional mailbox

2

source_of_record

The system this context derives from

3

freshness_slo

Maximum tolerated staleness, as an ISO 8601 duration (P7D, PT12H, P1DT6H)

4

last_validated

ISO 8601 timestamp of the last affirmative human validation

5

decay_policy

on_violation (required) + optional fallback

As a manifest (examples/pricing_context.json):

{
  "steward": "Ana Souza (Head of Pricing Operations)",
  "source_of_record": "SAP ERP price list PL-2026-Q3",
  "freshness_slo": "P7D",
  "last_validated": "2026-07-15T09:00:00Z",
  "decay_policy": {
    "on_violation": "refuse_with_disclosure",
    "fallback": "escalate_to_steward"
  }
}
NOTE

freshness_slo rejects years and months (P1Y, P1M) — they don't map to a fixed duration. Express the window in days: P30D, P365D. Timestamps accept a trailing Z; naive timestamps are treated as UTC.

Install

pip install -e .            # core — zero dependencies, Python ≥ 3.9
pip install -e ".[mcp]"     # + official MCP Python SDK, for the example server
pip install -e ".[dev]"     # + pytest

The core (cvd.models, cvd.evaluator, cvd.guard) is standard library only. It installs, imports, and tests without mcp — the SDK is needed only for the example server.

Quickstart

Two lines of ceremony: declare the contract, guard the function.

from cvd import ContextValidity, DecayPolicy, cvd_guard

pricing_cv = ContextValidity(
    steward="Ana Souza (Head of Pricing Operations)",
    source_of_record="SAP ERP price list PL-2026-Q3",
    freshness_slo="P7D",
    last_validated="2026-07-15T09:00:00Z",
    decay_policy=DecayPolicy(on_violation="refuse_with_disclosure"),
)

@cvd_guard(pricing_cv)
def get_enterprise_price(sku: str) -> dict:
    return {"sku": sku, "unit_price_usd": 4800.00}

While the context is fresh, the function behaves as if the guard weren't there. Once it expires, the function is not even called — the caller gets the structured refusal instead.

Need the decision itself, or prefer exceptions?

from cvd import evaluate, ContextExpired

decision = evaluate(pricing_cv)          # Decision(freshness, allowed, action, age, slo, …)

@cvd_guard(pricing_cv, raise_on_expired=True)
def get_price(sku: str) -> dict: ...     # raises ContextExpired, carrying the Decision

on_violation

Wrapped call

Result when stale

refuse_with_disclosure / refuse

not invoked

{"status": "refused", "reason": "context_expired", "disclosure": …}

warn

invoked

{"status": "stale_warning", "disclosure": …, "result": <original>}

escalate_to_steward

not invoked

refusal + escalate_to = fallback, or the steward if absent

anything unknown

not invoked

fails safe as refuse_with_disclosure, noting the unknown policy

Guarding an MCP tool

The guard stacks directly under @mcp.tool()functools.wraps keeps the signature intact for the SDK's introspection (src/cvd/mcp_server.py):

from mcp.server.fastmcp import FastMCP
from cvd import cvd_guard

mcp = FastMCP("pricing-context-server")

@mcp.tool()
@cvd_guard(PRICING_CV)
def get_enterprise_price(sku: str) -> dict:
    ...
pip install -e ".[mcp]"
python -m cvd.mcp_server

The bundled manifest is deliberately past its window, so the tool refuses out of the box. Update last_validated to a recent timestamp and it answers normally.

See it fail loud

python demo.py — deterministic, no third-party packages. It evaluates the same manifest at two explicit instants:

=== 2. STALE — past the 7-day window (age: 14 days) ===
freshness      : STALE
allowed        : False
action         : refuse
tool result    : {
  "status": "refused",
  "reason": "context_expired",
  "disclosure": "This context derives from SAP ERP price list PL-2026-Q3 and has
    exceeded its freshness SLO of P7D: it was last validated 14 days ago, on
    2026-07-15T09:00:00Z. The accountable steward is Ana Souza (Head of Pricing
    Operations); contact them to revalidate."
}
CVD demo — fail loud, not fail confident
manifest: examples/pricing_context.json

=== 1. FRESH — within the 7-day window (age: 2 days) ===
now            : 2026-07-17T09:00:00+00:00
last_validated : 2026-07-15T09:00:00Z
freshness_slo  : P7D
freshness      : FRESH
allowed        : True
action         : answer
tool result    : {
  "sku": "WIDGET-PRO",
  "unit_price_usd": 4800.0,
  "currency": "USD"
}

=== 2. STALE — past the 7-day window (age: 14 days) ===
now            : 2026-07-29T09:00:00+00:00
last_validated : 2026-07-15T09:00:00Z
freshness_slo  : P7D
freshness      : STALE
allowed        : False
action         : refuse
tool result    : {
  "status": "refused",
  "reason": "context_expired",
  "disclosure": "This context derives from SAP ERP price list PL-2026-Q3 and has exceeded its freshness SLO of P7D: it was last validated 14 days ago, on 2026-07-15T09:00:00Z. The accountable steward is Ana Souza (Head of Pricing Operations); contact them to revalidate.",
  "escalate_to": null
}

Run the tests with pytest — 34 cases covering the freshness boundary, all four decay policies, both parsers, and the guard's no-call guarantee.

Why refuse instead of answer?

Because a visible refusal is a recoverable operational event; a confident wrong answer is an invisible one.

An agent whose context has exceeded its freshness window must refuse to answer with confidence — returning a disclosure that names the accountable steward — rather than answer incorrectly with confidence. The refusal is not a failure of the system. It is the system working: obsolescence, which used to be silent, now produces a signal, an owner, and a path back to validity.

  • age == slo counts as FRESH. The SLO is a tolerance, not a fuse; the boundary belongs to the caller.

  • Unknown policies fail safe. A typo in decay_policy must never silently become "answer anyway".

  • The steward is a person. Disclosure that names pricing-team@ names no one. Accountability needs a face.

  • Years/months rejected in SLOs. P1M is 28–31 days depending on when you ask — an ambiguous freshness window is a contradiction in terms.

  • The clock is injectable (evaluate(cv, now=…), cvd_guard(cv, now_fn=…)), so every behavior in this README is reproducible in a test.

Citation

Lacerda, K. (2026). The Agent Steward: Context Provenance and Temporal Validity
as a Missing Governance Dimension in Multi-Agent Enterprise Ecosystems.
Zenodo. https://doi.org/10.5281/zenodo.21660205