Skip to main content
CoreSDK
Core Concepts

Intent Annotations

Attach semantic intent to traced functions using the @trace(intent="...") decorator. Captured as an OTel span attribute, works with sync and async Python functions.

Intent Annotations

Intent annotations let you attach a human-readable purpose to any traced function. The intent string is captured as a coresdk.intent OTel span attribute. When a policy evaluation or auth check fires inside that function, the trace record carries the human-readable intent alongside the machine outcome.

Why intent matters

A span that records coresdk.policy.result=denied for POST /api/orders tells you a request was blocked. A span that also records coresdk.intent="submit order on behalf of customer" tells you what the caller was trying to accomplish — critical context when the same endpoint is called by multiple code paths with different business meanings.

Python: @trace(intent="...")

Import the decorator from coresdk.tracing.decorator:

from coresdk.tracing.decorator import trace

@trace(intent="submit order on behalf of customer")
async def submit_order(order: dict) -> dict:
    # CoreSDK opens a span named "submit_order" with:
    #   coresdk.intent = "submit order on behalf of customer"
    return await db.insert_order(order)

@trace(intent="cancel subscription at period end")
async def cancel_subscription(sub_id: str) -> None:
    await billing.schedule_cancellation(sub_id)

The decorator works with both sync and async functions:

from coresdk.tracing.decorator import trace

@trace(intent="compute invoice total")
def compute_total(line_items: list) -> float:
    return sum(item["amount"] for item in line_items)

Annotating FastAPI route handlers

Stack @trace with your route decorator. Order does not matter — CoreSDK attaches to the active span regardless of nesting.

from fastapi import FastAPI
from coresdk.tracing.decorator import trace

app = FastAPI()

@app.post("/api/orders")
@trace(intent="submit order on behalf of customer")
async def submit_order_handler(request: Request) -> dict:
    user = request.state.coresdk_user
    return await submit_order(user, request.json())

@app.delete("/api/subscriptions/{sub_id}")
@trace(intent="cancel subscription at period end")
async def cancel_subscription_handler(sub_id: str, request: Request) -> None:
    await cancel_subscription(sub_id)

Rust: #[trace(intent = "...")]

use coresdk_engine::telemetry::trace;

#[trace(intent = "submit order on behalf of customer")]
async fn submit_order(order: NewOrder) -> Result<Order, coresdk_engine::error::ProblemDetail> {
    // Span carries:
    //   coresdk.intent = "submit order on behalf of customer"
    let order = db::insert_order(&order).await?;
    Ok(order)
}

#[trace(intent = "cancel subscription at period end")]
async fn cancel_subscription(sub_id: SubId) -> Result<(), coresdk_engine::error::ProblemDetail> {
    billing::schedule_cancellation(sub_id).await?;
    Ok(())
}

OTel span attributes set by intent annotations

When a function is annotated, CoreSDK sets the following attributes on the enclosing span:

AttributeTypeExample
coresdk.intentstring"submit order on behalf of customer"
coresdk.intent.functionstring"submit_order"
coresdk.intent.outcomestring"allowed" or "denied"

If the annotated function is called inside an existing span (e.g., the request span opened by the middleware), the intent attributes are added to that parent span rather than creating a new one.

Zero overhead when tracing is disabled

The decorator is a no-op when no TracerProvider is configured. It adds no runtime overhead in environments where tracing is off.

Next steps

  • Middleware Pipeline — where the tracing middleware sits and how intent annotations interact with it
  • Observability — full list of span attributes and export targets
  • Error Handling — how CoreSDK maps errors to RFC 9457 responses

On this page