Framework Adapter Attestation Guide

JACS provides Python framework adapters for LangChain, FastAPI, CrewAI, and Anthropic. Each adapter can be configured to produce attestations (not just signatures) for tool calls, API requests, and agent actions.

Common Patterns

All framework adapters share these attestation patterns:

Default Claims

When attest=True is enabled on any adapter, it automatically includes these default claims:

[
    {"name": "framework", "value": "langchain", "confidence": 1.0},
    {"name": "tool_name", "value": "my_tool", "confidence": 1.0},
    {"name": "timestamp", "value": "2026-03-04T...", "confidence": 1.0},
]

Custom Claims

Add your own claims to any adapter call:

extra_claims = [
    {"name": "reviewed_by", "value": "human", "confidence": 0.95},
    {"name": "approved", "value": True, "assuranceLevel": "verified"},
]

Evidence Attachment

Attach evidence references from external systems:

evidence = [
    {
        "kind": "custom",
        "digests": {"sha256": "abc123..."},
        "uri": "https://scanner.example.com/report/456",
        "collectedAt": "2026-03-04T00:00:00Z",
        "verifier": {"name": "security-scanner", "version": "2.0"},
    }
]

LangChain

Enabling Attestation on Tool Calls

Use jacs_wrap_tool_call with attest=True:

from jacs.adapters.langchain import jacs_wrap_tool_call
from jacs.client import JacsClient

client = JacsClient.quickstart()

# Wrap a tool call with attestation
@jacs_wrap_tool_call(client, attest=True)
def my_tool(query: str) -> str:
    return f"Result for: {query}"

# The tool call now produces a signed attestation
result = my_tool("test query")
# result.attestation contains the signed attestation document

Using the signed_tool Decorator

from jacs.adapters.langchain import signed_tool

@signed_tool(client, attest=True, claims=[
    {"name": "data_source", "value": "internal_db", "confidence": 1.0}
])
def lookup_customer(customer_id: str) -> dict:
    return {"name": "Alice", "status": "active"}

With LangChain Chains

from jacs.adapters.langchain import with_jacs_signing

# Wrap an entire chain with attestation
signed_chain = with_jacs_signing(
    chain=my_chain,
    client=client,
    attest=True,
)

FastAPI

Attestation Middleware

The JacsMiddleware can be configured to produce attestations for all responses:

from fastapi import FastAPI
from jacs.adapters.fastapi import JacsMiddleware
from jacs.client import JacsClient

app = FastAPI()
client = JacsClient.quickstart()

app.add_middleware(
    JacsMiddleware,
    client=client,
    attest=True,  # Produce attestations, not just signatures
    default_claims=[
        {"name": "service", "value": "my-api", "confidence": 1.0},
    ],
)

Per-Route Attestation

Use jacs_route for route-level attestation control:

from jacs.adapters.fastapi import jacs_route

@app.post("/approve")
@jacs_route(client, attest=True, claims=[
    {"name": "action", "value": "approve", "confidence": 1.0},
    {"name": "requires_review", "value": True},
])
async def approve_request(request_id: str):
    return {"approved": True, "request_id": request_id}

The response headers will include X-JACS-Attestation-Id with the attestation document ID.

CrewAI

Attestation Guardrails

Use jacs_guardrail with attestation mode to create trust-verified task execution:

from jacs.adapters.crewai import jacs_guardrail, JacsSignedTool
from jacs.client import JacsClient

client = JacsClient.quickstart()

@jacs_guardrail(client, attest=True)
def verified_analysis(task_result):
    """Guardrail that attests to analysis quality."""
    return task_result

Signed Tasks

from jacs.adapters.crewai import signed_task

@signed_task(client, attest=True, claims=[
    {"name": "analysis_type", "value": "financial", "confidence": 0.9},
])
def analyze_portfolio(data):
    return {"risk_score": 0.3, "recommendation": "hold"}

JacsSignedTool

class MyTool(JacsSignedTool):
    """A CrewAI tool with built-in attestation."""
    name = "market_data"
    description = "Fetch market data"
    attest = True
    default_claims = [
        {"name": "data_source", "value": "bloomberg"},
    ]

    def _run(self, ticker: str) -> dict:
        return {"ticker": ticker, "price": 150.0}

Anthropic

Tool Hook Attestation

The Anthropic adapter hooks into Claude tool calls to produce attestations:

from jacs.adapters.anthropic import signed_tool, JacsToolHook
from jacs.client import JacsClient

client = JacsClient.quickstart()

@signed_tool(client, attest=True)
def search_database(query: str) -> str:
    return "Found 3 results"

# Or use the hook class for more control
hook = JacsToolHook(
    client=client,
    attest=True,
    default_claims=[
        {"name": "model", "value": "claude-4.6"},
        {"name": "tool_use_id", "value": "auto"},  # Auto-filled from tool call
    ],
)

With the Anthropic SDK

import anthropic
from jacs.adapters.anthropic import JacsToolHook

client = anthropic.Anthropic()
jacs_client = JacsClient.quickstart()
hook = JacsToolHook(jacs_client, attest=True)

# Register tools with JACS attestation
tools = hook.wrap_tools([
    {
        "name": "get_weather",
        "description": "Get weather for a location",
        "input_schema": {"type": "object", "properties": {"location": {"type": "string"}}},
    }
])

Verifying Framework Attestations

All framework attestations use the same JACS verification API:

# Verify any attestation (from any framework adapter)
result = client.verify_attestation(attestation_json, full=True)
print(f"Valid: {result['valid']}")
print(f"Framework: {result['claims'][0]['value']}")
print(f"Evidence: {result.get('evidence', [])}")

Strict vs. Permissive Mode

All adapters respect the strict flag on JacsClient:

  • Permissive (default): Signing/attestation failures log warnings but do not block the operation
  • Strict: Signing/attestation failures raise exceptions and block the operation
# Strict mode: attestation failure = operation failure
client = JacsClient.quickstart(strict=True)

# Permissive mode: attestation failure = warning + continue
client = JacsClient.quickstart(strict=False)