SDK Guide¶
The Zentinelle SDKs wrap the agent-facing REST API at /api/zentinelle/v1. All maintained clients now follow the same bootstrap-to-runtime auth flow and the same core endpoint set.
SDK source: github.com/calliopeai/zentinelle-sdk
Supported SDKs¶
| Language | Source Directory | Notes |
|---|---|---|
| Python | python/zentinelle |
Synchronous client with background flush and heartbeat threads |
| TypeScript / Node | typescript/src |
Async client for Node and compatible runtimes |
| Go | go/zentinelle |
context.Context-based API |
| Java | java/src/main/java/ai/zentinelle |
Builder-style client |
| C# | csharp/src/Zentinelle |
Async-first client |
Shared Flow¶
- Create a client with a bootstrap token (
bt_...),agent_type, and the Zentinelle service endpoint. - Call
register()once on startup. - The server returns
agent_id, a runtimeapi_key, initialconfig, andpolicies. - The SDK swaps from
X-Zentinelle-BootstraptoX-Zentinelle-Keyautomatically after registration. - Call
evaluate()before guarded actions,emit()after them, and usegetConfig()/getSecrets()for cached runtime state. - Let background heartbeat and flush loops run, or call the explicit flush/shutdown methods before exit.
Shared Runtime Contract¶
All maintained SDKs target these endpoints:
| Method | Path | Used For |
|---|---|---|
POST |
/register |
Bootstrap registration |
GET |
/config/{agent_id} |
Runtime config and effective policies |
GET |
/secrets/{agent_id} |
Scoped secrets |
POST |
/evaluate |
Guardrail and policy checks |
POST |
/events |
Buffered telemetry, audit, and alert events |
POST |
/heartbeat |
Health and liveness reporting |
Notes:
- Registration uses
X-Zentinelle-Bootstrap; runtime requests useX-Zentinelle-Key. - In this standalone repo,
/secretsand/secrets/{agent_id}currently return an empty bundle unless secret provisioning is implemented externally. - Heartbeat currently returns
202 Acceptedwith{"acknowledged": true}. SDKs are tolerant of future drift/sync fields.
Python¶
from zentinelle import ZentinelleClient
client = ZentinelleClient(
api_key="bt_<tenant_id>_<signature>",
agent_type="codex",
endpoint="http://localhost:8080",
)
registration = client.register(
capabilities=["chat", "tool:search"],
metadata={"version": "1.0.0"},
name="codex-dev-agent",
)
decision = client.evaluate(
"tool_call",
user_id="user_123",
context={"tool": "web_search"},
)
if not decision.allowed:
raise PermissionError(decision.reason or "blocked by policy")
client.emit(
"tool_call",
{"tool": "web_search", "duration_ms": 1420},
category="audit",
user_id="user_123",
)
client.flush_events()
client.shutdown()
TypeScript / Node¶
import { ZentinelleClient } from 'zentinelle';
const client = new ZentinelleClient({
apiKey: 'bt_<tenant_id>_<signature>',
agentType: 'codex',
endpoint: 'http://localhost:8080',
});
const registration = await client.register({
capabilities: ['chat', 'tool:search'],
metadata: { version: '1.0.0' },
name: 'codex-dev-agent',
});
const decision = await client.evaluate('tool_call', {
userId: 'user_123',
context: { tool: 'web_search' },
});
if (!decision.allowed) {
throw new Error(decision.reason ?? 'blocked by policy');
}
client.emit(
'tool_call',
{ tool: 'web_search', duration_ms: 1420 },
{ category: 'audit', userId: 'user_123' }
);
await client.flushEvents();
await client.shutdown();
Go¶
client, err := zentinelle.NewClient(zentinelle.Config{
APIKey: "bt_<tenant_id>_<signature>",
AgentType: "codex",
Endpoint: "http://localhost:8080",
})
if err != nil {
log.Fatal(err)
}
defer client.Shutdown()
registration, err := client.Register(ctx, zentinelle.RegisterOptions{
Capabilities: []string{"chat", "tool:search"},
Metadata: map[string]interface{}{"version": "1.0.0"},
Name: "codex-dev-agent",
})
if err != nil {
log.Fatal(err)
}
decision, err := client.Evaluate(ctx, "tool_call", zentinelle.EvaluateOptions{
UserID: "user_123",
Context: map[string]interface{}{"tool": "web_search"},
})
if err != nil {
log.Fatal(err)
}
if !decision.Allowed {
log.Fatalf("blocked: %s", decision.Reason)
}
client.Emit("tool_call", map[string]interface{}{
"tool": "web_search",
"duration_ms": 1420,
}, zentinelle.EmitOptions{
Category: "audit",
UserID: "user_123",
})
if err := client.FlushEvents(ctx); err != nil {
log.Fatal(err)
}
_ = registration
Java¶
ZentinelleClient client = ZentinelleClient.builder()
.apiKey("bt_<tenant_id>_<signature>")
.agentType("codex")
.endpoint("http://localhost:8080")
.build();
RegisterResult registration = client.register(RegisterOptions.builder()
.capabilities(List.of("chat", "tool:search"))
.metadata(Map.of("version", "1.0.0"))
.name("codex-dev-agent")
.build());
EvaluateResult decision = client.evaluate("tool_call", EvaluateOptions.builder()
.userId("user_123")
.context(Map.of("tool", "web_search"))
.build());
if (!decision.isAllowed()) {
throw new IllegalStateException(decision.getReason());
}
client.emit(
"tool_call",
Map.of("tool", "web_search", "duration_ms", 1420),
EmitOptions.builder().category(EventCategory.AUDIT).userId("user_123").build()
);
client.flushEvents();
client.shutdown();
C¶
var client = new ZentinelleClient(new ZentinelleOptions
{
ApiKey = "bt_<tenant_id>_<signature>",
AgentType = "codex",
BaseUrl = "http://localhost:8080",
});
var registration = await client.RegisterAsync(new RegisterOptions
{
Capabilities = new List<string> { "chat", "tool:search" },
Metadata = new Dictionary<string, object> { ["version"] = "1.0.0" },
Name = "codex-dev-agent",
});
var decision = await client.EvaluateAsync("tool_call", new EvaluateOptions
{
UserId = "user_123",
Context = new Dictionary<string, object> { ["tool"] = "web_search" },
});
if (!decision.Allowed)
{
throw new InvalidOperationException(decision.Reason ?? "blocked by policy");
}
client.EmitToolCall("web_search", "user_123", 1420);
await client.FlushAsync();
await client.DisposeAsync();
Plugins¶
The SDK repo also carries framework adapters and runtime integrations under plugins/, including:
plugins/agentplugins/langchainplugins/crewaiplugins/llamaindexplugins/ms-agent-frameworkplugins/n8nplugins/vercel-ai
Use the plugin-specific READMEs in the SDK repo when you want framework-native instrumentation rather than calling the core client directly.
Built-In Resilience¶
Across languages, the SDKs provide the same operational baseline:
- Retry with backoff for transient HTTP failures
- Circuit-breaker support plus configurable fail-open behavior
- Buffered event emission with periodic flush
- Cached config and secrets reads
- Periodic heartbeats once registration succeeds
The exact option names vary by language, but the behavior is intentionally aligned around the same server contract.