Drop-in reliability observability for multi-agent AI workflows
Project description
sentinelai-sdk
A control plane for multi-agent AI workflows — tracing, contract enforcement, safe state, and failure replay.
Dashboard: www.agentsentinelai.com/dashboard
Install
pip install sentinelai-sdk
Get an API key: open the dashboard → ⚙️ Settings → Generate Key. Free, no credit card required.
What it does
| I want to… | Feature |
|---|---|
| See every agent step — inputs, outputs, latency, token counts | Tracing |
| Block bad data from reaching the next agent | Contracts |
| Replay a failed run from any checkpoint once the bug is fixed | Replay |
| Let concurrent agents write shared state without overwriting each other | Shared State |
These stack — use one, two, or all four.
Quickstart
import sentinel
sentinel.init(api_key="sk_live_...")
with sentinel.workflow("my-pipeline") as run:
with run.step("planner", step_type="llm_call") as step:
step.set_input({"query": "Plan a trip to Tokyo"})
result = planner_agent(query)
step.set_output({"plan": result})
with run.step("researcher", step_type="tool_call") as step:
step.set_input(result)
data = researcher_agent(result)
step.set_output({"findings": data})
Tracing
Option 1 — Workflow context manager (recommended)
import sentinel
sentinel.init(api_key="sk_live_...")
with sentinel.workflow("travel-planner") as run:
with run.step("plan", step_type="llm_call") as step:
step.set_input({"query": query})
output = plan_agent(query)
step.set_output(output)
Option 2 — Patch OpenAI clients (sync)
Every client.chat.completions.create() call becomes a traced step automatically.
import openai, sentinel
sentinel.init(api_key="sk_live_...")
client = openai.OpenAI(api_key="...")
sentinel.patch_openai(client, workflow_name="my-pipeline")
sentinel.set_active_run("run_001", "my-pipeline")
response = client.chat.completions.create(model="gpt-4o", messages=[...])
Option 3 — Patch AsyncOpenAI clients (async)
import openai, sentinel
sentinel.init(api_key="sk_live_...")
client = openai.AsyncOpenAI(api_key="...")
sentinel.patch_openai_async(client, workflow_name="my-pipeline")
async def main():
sentinel.set_active_run("run_001", "my-pipeline")
response = await client.chat.completions.create(model="gpt-4o", messages=[...])
Deep instrumentation — for libraries like gpt-researcher that create their own AsyncOpenAI instances internally, patch at the class level:
import openai.resources.chat.completions as _oai_completions
sentinel.patch_openai_async(_oai_completions.AsyncCompletions)
# Now every AsyncOpenAI client anywhere in the process is traced
Option 4 — LangChain callback
from sentinel import LangChainCallback
from langchain_openai import ChatOpenAI
sentinel.init(api_key="sk_live_...")
cb = LangChainCallback(workflow_name="my-pipeline")
llm = ChatOpenAI(model="gpt-4o", callbacks=[cb])
Option 5 — Decorator
@sentinel.trace_step(name="planner", step_type="llm_call", workflow_name="my-pipeline")
def planner(query):
return llm.invoke(query)
Contracts (handoff validation)
Define what one agent must pass to the next. If the payload fails validation, Sentinel raises ContractViolationError, marks the run as blocked in the dashboard, and saves a checkpoint for replay.
import sentinel
from sentinel import ContractViolationError
sentinel.init(api_key="sk_live_...")
sentinel.register_contract(
agent="researcher",
accepts={
"destination": {"type": "string", "required": True, "min_length": 1},
"budget": {"type": "number", "required": True, "min": 100},
"days": {"type": "number", "required": True, "min": 1, "max": 30},
},
)
with sentinel.workflow("travel-planner") as run:
with run.step("planner", step_type="llm_call") as step:
plan = planner_agent(query)
step.set_output(plan)
# Validate handoff before the next agent runs
sentinel.handoff(
from_agent="planner",
to_agent="researcher",
payload=plan,
run_id=run.run_id,
)
with run.step("researcher", step_type="tool_call") as step:
research = researcher_agent(plan)
step.set_output(research)
Field spec options
{"type": "string", "required": True, "min_length": 1}
{"type": "number", "required": True, "min": 0, "max": 100}
{"type": "boolean", "required": True}
{"type": "array", "required": False}
Shared State
Safe concurrent writes — no silent overwrites when agents run in parallel.
# Read
value, version = sentinel.get_state(run_id, "research_results")
# Write with conflict protection (raises ConflictError if stale)
sentinel.propose_state(run_id, "research_results", new_value, base_version=version)
# Auto-retry on conflict (merge function receives current value)
sentinel.propose_state_with_retry(
run_id, "research_results",
lambda cur: {**(cur or {}), "hotels": hotel_list}
)
gpt-researcher example
Full deep instrumentation — every internal LLM call becomes a visible step:
import asyncio, os
import sentinel
from sentinel import ContractViolationError
import openai.resources.chat.completions as _oai_completions
sentinel.init(api_key="sk_live_...")
# Patch at the class level to catch gpt-researcher's internal AsyncOpenAI clients
sentinel.patch_openai_async(_oai_completions.AsyncCompletions)
sentinel.register_contract(
agent="write_report",
accepts={
"source_count": {"type": "number", "required": True, "min": 1},
"has_context": {"type": "boolean", "required": True},
"query": {"type": "string", "required": True, "min_length": 1},
},
)
async def run_research(query: str) -> str:
from gpt_researcher import GPTResearcher
researcher = GPTResearcher(query=query, report_type="research_report", verbose=False)
with sentinel.workflow("gpt-researcher") as run:
sentinel.set_active_run(run.run_id, "gpt-researcher")
with run.step("conduct_research", step_type="tool_call") as step:
step.set_input({"query": query})
await researcher.conduct_research()
sources = researcher.get_source_urls()
context = researcher.get_research_context()
step.set_output({"source_count": len(sources), "sources": sources[:5]})
sentinel.handoff(
from_agent="conduct_research",
to_agent="write_report",
payload={"source_count": len(sources), "has_context": bool(context), "query": query},
run_id=run.run_id,
)
with run.step("write_report", step_type="llm_call") as step:
step.set_input({"source_count": len(sources), "query": query})
report = await researcher.write_report()
step.set_output({"word_count": len(report.split())})
return report
asyncio.run(run_research("What is the impact of AI agents on software engineering in 2025?"))
Run with DuckDuckGo (no API key needed):
pip install gpt-researcher sentinelai-sdk ddgs
RETRIEVER=duckduckgo OPENAI_API_KEY=sk-... python your_script.py
Each run produces ~5 nested steps in the dashboard:
openai/gpt-4.1— agent selectionconduct_research— phase wrapperopenai/o4-mini— report generationwrite_report— phase wrapperopenai/gpt-4.1— TOC / introduction / conclusion
Links
Project details
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 sentinelai_sdk-0.1.6.tar.gz.
File metadata
- Download URL: sentinelai_sdk-0.1.6.tar.gz
- Upload date:
- Size: 23.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd9d2f4ef0d9828d867a990f13518cfc3c7ec3ea36eeb6469e7d04d226ee3a57
|
|
| MD5 |
9647ca798727c5a3843b981c4dcc2d3a
|
|
| BLAKE2b-256 |
2967aeabe7dcf4e6c308eca9b515738ed1bb275416181854b00d92af49371be0
|
File details
Details for the file sentinelai_sdk-0.1.6-py3-none-any.whl.
File metadata
- Download URL: sentinelai_sdk-0.1.6-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7bf047ce4d71310052d93f28ca454d3861c2434b3a645888ff264664916c5c49
|
|
| MD5 |
1d7eeb42d1b2a6c4c611db5a226a1613
|
|
| BLAKE2b-256 |
e1258ed4f8534f4100c0a0d15c9303a64279651dfd1d5917e118283e5b4212c8
|