neosigma-sdk
Trace your AI agents and ship the results to NeoSigma. Add a few lines, run your agents as usual, and every run's model calls, tool calls, and token usage lands in NeoSigma as a structured OpenTelemetry trace.
- Dark by default: with no API key (and
NEOSIGMA_CONSOLE_EXPORT=false) the SDK is a complete no-op and never touches your application's own OpenTelemetry setup, so it's safe to leave in place. - Provider-agnostic: a small core with thin adapters that wrap the agent framework you already use, with no hard dependency on any provider SDK.
Install
pip install neosigma-sdk
# or: uv add neosigma-sdk
Quickstart
import neosigma_sdk as neosigma
neosigma.init() # reads NEOSIGMA_API_KEY from the environment
# ... trace your agent with the decorators or an adapter (below) and run as usual ...
neosigma.shutdown() # flush before exit (long-running servers flush in the background)
Tracing your agents
A few ways to produce spans, and they compose: anything traced while an interaction is active nests under it, so one run is one trace.
-
Decorators mark a run and its steps, with no framework required.
@interactionis the run;@toolis a step inside it.@neosigma.tool() def search(query: str) -> list[str]: ... @neosigma.interaction() def answer(question: str) -> str: hits = search(question) # nested under the interaction ...
-
turn()/finish()track a run whose lifecycle spans multiple functions, where a single decorator cannot wrap the whole thing. One user message is one trace:turn()always opens a fresh root, tied to asession_idand aturn_id(minted, or supplied) that is also the join key forcapture()events below.t = neosigma.turn(session_id="sess_123", user_message=question, distinct_id="user_123") t.set_attributes({"plan": "pro"}) # attach metadata/tags to the run reply = run_agent(question) # tool and LLM calls nest under this run t.finish(output=reply)
-
Auto-instrumentation traces raw LLM clients (Anthropic, OpenAI) with no per-call code: install the
instrumentationextra and callneosigma.init(tracing_enabled=True).import anthropic neosigma.init(tracing_enabled=True) # turn on the off-the-shelf instrumentors client = anthropic.Anthropic() client.messages.create(...) # this call is now a traced span
See the documentation for the full API and configuration.
Product events and the turn_id spine
Agent traces tell you what the model did. Product events tell you what the user did
(a button click, a feature used, a conversion). NeoSigma joins the two streams on a
single id, the turn_id, so you can go from "this user clicked rewind" to "here is
the exact agent trace behind it" without stitching timestamps.
turn_id is the durable correlation key. One turn is one user message plus
everything the agent did in response; a session is a series of turns. You supply
the id, bind it once, and from then on:
- every span opened inside the turn carries it (stamped by the
CorrelationSpanProcessor, so adapters, auto-instrumented LLM clients, and your own spans all pick it up with no per-framework code), and - every product event you
capture()inside the turn carries the same value.
Both land in NeoSigma keyed on turn_id, and join there.
Binding the turn
Use trace() when the span is produced elsewhere (an adapter or auto-instrumented
client), or begin() / @interaction to also open a root span. Both bind the same
ambient ids:
import neosigma_sdk as neosigma
neosigma.init()
with neosigma.trace(turn_id="turn_abc", distinct_id="user_123"):
reply = run_agent(question) # any spans here carry turn_abc
neosigma.capture("agent_answered", # this event carries turn_abc too
{"helpful": True, "latency_ms": 820})
Contextvars propagate across await within a task, but not across a process or queue
hop. Across such a boundary, thread the turn_id into the job payload and re-bind it on
the far side (with neosigma.trace(turn_id=...) or begin(turn_id=...)).
capture() and identify()
capture(event_name, properties=None)emits a product event stamped with the ambientturn_id/distinct_id/session_id(and the active span'strace_id, best effort). Property values are scalar (str,int,float,bool). Each event gets anevent_uuididempotency key, so a retried delivery de-dupes rather than double-counts.identify(distinct_id, properties=None)bindsdistinct_id(the analytics actor) for every later event and span in the task, and emits an$identifyevent. Call it once at login; a per-turntrace()that omitsdistinct_idwill not clobber it.
neosigma.identify("user_123", {"plan": "pro"})
# ... later, anywhere in the same task ...
neosigma.capture("rewind_clicked", {"surface": "chat"}) # distinct_id rides along
Both calls are fail-open: a telemetry failure drops the event, it never raises into your application.
Where events go: the EventSink
capture() hands each built ProductEvent to the active EventSink, it never writes a
datastore directly (the SDK runs in your process and has no such access). When you call
init() with an API key, the SDK installs an HttpEventSink that batches events on a
background daemon thread and POSTs them to the events endpoint with your API key, the same
auth path traces use. It is bounded and fail-open: a full queue drops newest, an
unreachable ingest is swallowed, and your hot path never blocks. With no API key, a
default in-process BufferSink keeps capture() usable (and testable) but ships nothing.
shutdown() stops the flush thread and drains anything queued, so call it before exit.
Already using PostHog or Mixpanel?
If your product is already instrumented with PostHog or Mixpanel, you do not need to
re-instrument. Wrap the client once and every event you already send also flows into
NeoSigma, sharing the same turn_id spine. Your existing provider keeps receiving every
event unchanged (this mirrors, it does not redirect):
import posthog
import neosigma_sdk as neosigma
neosigma.init()
ph = neosigma.wrap_posthog(posthog) # the posthog module or a Posthog() instance
# Use it exactly as before. Each call ALSO reaches NeoSigma.
ph.capture("user_123", "rewind_clicked", {"surface": "chat"})
ph.identify("user_123", {"plan": "pro"})
Mixpanel works the same way via wrap_mixpanel(Mixpanel(token)): track(...) mirrors to
capture() and people_set(...) to identify(). Both wraps are transparent (all other
attributes delegate unchanged), duck-typed (the SDK never imports posthog / mixpanel,
so no new dependency), and fail-open (the mirror is best-effort and can never break your
analytics call). An event fired inside a trace() / begin() block joins to that agent
trace on turn_id; one fired outside is still a valid event, joinable by distinct_id.
Adapters
Thin wrappers that trace an agent framework you already use, feeding the same trace contract as the decorators. More are on the way.
- Anthropic Managed Agents:
wrap_managed_agents(client)traces a session's model and tool calls (syncAnthropicandAsyncAnthropic). - Claude Agent SDK:
trace_claude(stream)traces aquery(...)run; for the statefulClaudeSDKClient,ClaudeTracingProcessor().configure()is the zero-touch option.
Example: Anthropic Managed Agents
import anthropic
import neosigma_sdk as neosigma
neosigma.init()
client = neosigma.wrap_managed_agents(anthropic.Anthropic())
# Build and run a Managed Agents session as you normally would. Streaming the
# session produces one NeoSigma trace: model calls, tool calls, and token usage.
session = client.beta.sessions.create(agent=agent, environment_id=environment.id)
with client.beta.sessions.events.stream(session_id=session.id) as stream:
for event in stream:
...
neosigma.shutdown()
AsyncAnthropic works the same way (async with / async for).
Configuration
Common settings read from a NEOSIGMA_* environment variable, or can be passed to
init(...):
| Variable | Default | Purpose |
|---|---|---|
NEOSIGMA_API_KEY |
(none) | Your ns_live_... key. Required to export, without it the SDK stays dark. |
NEOSIGMA_PROJECT |
default |
Logical project name, attached to every trace. |
NEOSIGMA_OTEL_ENDPOINT |
NeoSigma cloud | OTLP/HTTP endpoint agent traces ship to. Override to target another environment. |
NEOSIGMA_EVENTS_ENDPOINT |
NeoSigma cloud | HTTP endpoint product events (capture()) ship to. Override alongside NEOSIGMA_OTEL_ENDPOINT when targeting another environment, otherwise traces move but events keep going to the default cloud. |
NEOSIGMA_CONSOLE_EXPORT |
false |
Also print spans to stdout, for local debugging. |
See the NeoSigma documentation for the complete configuration reference and API docs.
License
Released under the MIT License.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file neosigma_sdk-0.3.0.tar.gz.
File metadata
- Download URL: neosigma_sdk-0.3.0.tar.gz
- Upload date:
- Size: 103.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17b374b7c7f72dc0c81c645236c97ed35de869b7ac30248c49f7740912a715db
|
|
| MD5 |
5da86b63c42d55c2ed94ae2b219f332d
|
|
| BLAKE2b-256 |
afc0ad98c382e0654077633c66a073b22479da3f39ff3dd05d58c5b00c31c86e
|
File details
Details for the file neosigma_sdk-0.3.0-py3-none-any.whl.
File metadata
- Download URL: neosigma_sdk-0.3.0-py3-none-any.whl
- Upload date:
- Size: 82.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
60db9e4fdce7e2d64f9042de4b555ba34fb08a566da0969272d01efa484e2c3d
|
|
| MD5 |
2fa580b0d919d1e613263cdfc6d2a3ee
|
|
| BLAKE2b-256 |
5925535e19a89d643f6bf540529e3809c9e6533f082c9d8eb21c28aa0c88e26e
|