Strands
Trace Strands agents in Python and TypeScript.
Strands self-instruments; the Neatlogs hook enriches its spans with input/output content so they render fully in the dashboard. Register it on the agent after you construct it.
Prerequisites
pip install -U neatlogs strands-agentsUsage
import os
import neatlogs
neatlogs.init(api_key=os.environ["NEATLOGS_API_KEY"], workflow_name="strands-demo")
from strands import Agent
from strands.models import BedrockModel
agent = Agent(model=BedrockModel(model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0"))
neatlogs.strands_hooks(agent)
print(agent("In one sentence, what is Strands?"))Sessions & end-user
Say a single conversation with your Strands agent runs across a handful of turns and you want them grouped as one session. Strands self-instruments and opens its own trace root, so wrap each turn in identify() and that root — plus every span beneath it — picks up the session and end-user:
# Same session_id every turn → one session; end_user_id attributes it to your user.
with neatlogs.identify(session_id=f"conv_{conversation_id}", end_user_id=user_id):
response = agent(message)Not every run is a conversation. A standalone workflow — a one-off job, not a back-and-forth — is a single trace with no turns to group. You still attribute it to the customer it ran for, sourcing the id and any metadata from your own user or request object:
A standalone workflow must not carry a session_id. Omit it entirely — Neatlogs sets session_id = trace_id, so the run is its own single-turn session, fully attributed to the end-user. Reusing one session_id across unrelated runs wrongly folds them into a single multi-turn conversation.
def summarize(document, user):
# Standalone workflow — attribute to the end-user; do NOT pass session_id.
with neatlogs.identify(
end_user_id=str(user.id),
end_user_metadata={"plan": user.plan},
):
return agent(f"Summarize:\n{document}")For the whole picture, head to Sessions and End-User Identity.
