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:
| Attribute | Type | Example |
|---|---|---|
coresdk.intent | string | "submit order on behalf of customer" |
coresdk.intent.function | string | "submit_order" |
coresdk.intent.outcome | string | "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