The SDK on its own
examples/01_plain.py uses no provider, no framework and no auto-instrumentation. This is the floor: everything it records comes from the explicit surface alone, and every other example adds spans on top of exactly this shape.
python examples/01_plain.pyA visit before anyone speaks
Somebody loaded the widget and typed nothing. A decorator or a turn only fires once someone has engaged, so it cannot express "loaded but silent" — which is why open_conversation() is a separate, explicit call:
import agentsight as ags
ags.open_conversation("demo-visitor", device="mobile", source="web", language="en")One conversation, several shapes of turn
The conversation scope takes its metadata once, at the top. Inside it the script runs through the shapes real traffic produces:
with ags.conversation(
"demo-plain",
customer_id="user-12345",
device="desktop",
source="web",
language="en",
environment="development",
name="Order questions",
):
# the common shape: one message in, one out, a tool call between
with ags.turn("ask"):
ags.user_message("Where is my order?")
search_orders("user-12345") # @ags.tool
ags.agent_message("Order A-1 ships tomorrow.")
# messages have no rules: three in, two out, internal work between
with ags.turn("burst"):
ags.user_message("actually")
ags.user_message("two of them")
ags.user_message("the second one is urgent")
rerank(["b", "a"]) # @ags.task
ags.agent_message("Got it — checking both.")
ags.agent_message("The urgent one is already out for delivery.")search_orders and rerank are ordinary functions wearing @ags.tool and @ags.task; the decorated call inside an active turn is all it takes for the span to appear in the right place.
Things no call stack can observe
A button click happens in the browser; a file arrived through your upload endpoint. Neither is visible from the Python call stack, so both are explicit:
ags.button("feedback", "Was this helpful?", "yes")
ags.record_attachments([{"filename": "receipt.pdf", "mime_type": "application/pdf"}])record_attachments() only notes that a file exists. upload_attachments() is the one that moves bytes — this script writes spans to disk rather than talking to the API, so it uses the recorder.
Metadata learned late
What the conversation turned out to be about was not known at the top, where the scope took its metadata. update_metadata() merges rather than replaces — customer_id and everything else set earlier survives:
ags.update_metadata({"topic": "delivery", "self_served": False})A falsy value like False is stored as data; remove= is the only thing that deletes a key.
Turns that do not end well
The script closes with three endings the dashboard has to distinguish: a silent escalation (a turn with a user message and a tool call but no agent message at all), an abandoned turn, and a turn that raised:
with ags.turn("abandoned"):
ags.user_message("wait, never mind")
ags.abandon_turn()
try:
with ags.turn("boom"):
ags.user_message("do the thing")
raise RuntimeError("the thing exploded")
except RuntimeError:
passNeither writes transcript rows — there was no complete exchange — but the spans, their durations and any spend they carried are all recorded, marked as what they are.
What to look for in the output
The printed span tree shows each turn complete or INCOMPLETE(abandoned) / INCOMPLETE(error), the tool spans nested under their turns, and the visit conversation in a file of its own. The JSON under examples/traces/01_plain/ is the payload itself.