Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,24 @@ events the SDK emits through its normal event-construction flow. The default
SDK sink remains the OSS path to the Agent Control server. To use registered
or named custom sinks, set `observability_sink_name` explicitly.

The SDK also includes a built-in OpenTelemetry sink. Install the OTEL extra,
select the `otel` sink, and configure the OTLP exporter through Agent Control
settings or environment variables:

```bash
uv pip install "agent-control-sdk[otel]"
export AGENT_CONTROL_OBSERVABILITY_SINK_NAME=otel
export AGENT_CONTROL_OTEL_ENABLED=true
export AGENT_CONTROL_OTEL_ENDPOINT=http://localhost:4318/v1/traces
export AGENT_CONTROL_OTEL_HEADERS='{"authorization":"Bearer demo-token"}'
export AGENT_CONTROL_OTEL_SERVICE_NAME=awesome-bot
```

If the `otel` sink is selected without an OTLP endpoint/exporter configured,
the OTEL path stays inert and the default OSS SDK-to-server behavior still
remains unchanged unless `observability_sink_name` is explicitly switched away
from `default`.

Next, create a control in Step 4, then run the setup and agent scripts in
order to see blocking in action.

Expand Down
5 changes: 5 additions & 0 deletions sdks/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ Repository = "https://github.com/yourusername/agent-control"
strands-agents = ["strands-agents>=1.26.0"]
google-adk = ["google-adk>=1.0.0"]
galileo = ["agent-control-evaluator-galileo>=3.0.0"]
otel = [
"opentelemetry-api>=1.24.0",
"opentelemetry-sdk>=1.24.0",
"opentelemetry-exporter-otlp-proto-http>=1.24.0",
]

[dependency-groups]
dev = [
Expand Down
2 changes: 2 additions & 0 deletions sdks/python/src/agent_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ async def handle_input(user_message: str) -> str:
unregister_control_event_sink_factory,
write_events,
)
from .otel_sink import control_event_to_otel_span
from .tracing import (
get_current_span_id,
get_current_trace_id,
Expand Down Expand Up @@ -1380,6 +1381,7 @@ async def main():
"write_events",
"shutdown_observability",
"is_observability_enabled",
"control_event_to_otel_span",
"get_event_batcher",
"get_event_sink",
"get_registered_control_event_sink_factory_names",
Expand Down
19 changes: 19 additions & 0 deletions sdks/python/src/agent_control/observability.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
Configuration (Environment Variables):
# Observability (event batching)
AGENT_CONTROL_OBSERVABILITY_ENABLED: Enable observability (default: true)
AGENT_CONTROL_OBSERVABILITY_SINK_NAME: Selected control-event sink (default: default)
AGENT_CONTROL_OBSERVABILITY_SINK_CONFIG: JSON config for the selected sink
AGENT_CONTROL_OTEL_ENABLED: Enable the built-in OTEL sink (default: false)
AGENT_CONTROL_OTEL_ENDPOINT: OTLP HTTP endpoint for exported control-event spans
AGENT_CONTROL_OTEL_HEADERS: JSON object of OTLP exporter headers
AGENT_CONTROL_OTEL_SERVICE_NAME: OTEL service.name for emitted spans
AGENT_CONTROL_BATCH_SIZE: Max events per batch (default: 100)
AGENT_CONTROL_FLUSH_INTERVAL: Seconds between flushes (default: 5.0)
AGENT_CONTROL_SHUTDOWN_JOIN_TIMEOUT: Seconds to wait for worker shutdown (default: 5.0)
Expand Down Expand Up @@ -75,6 +81,8 @@
if TYPE_CHECKING:
from agent_control_models import ControlExecutionEvent

from .otel_sink import OTEL_CONTROL_EVENT_SINK_NAME, create_otel_control_event_sink

# =============================================================================
# Logger Setup - Standard Library Pattern
# =============================================================================
Expand Down Expand Up @@ -820,6 +828,17 @@ def get_stats(self) -> dict:
_used_custom_event_sinks_lock = threading.Lock()


def _register_builtin_control_event_sink_factories() -> None:
"""Ensure built-in named sink factories are available."""
_named_event_sink_factories.register(
OTEL_CONTROL_EVENT_SINK_NAME,
create_otel_control_event_sink,
)


_register_builtin_control_event_sink_factories()


class _BatcherControlEventSink(BaseControlEventSink):
"""Default SDK sink backed by the existing queue-based EventBatcher."""

Expand Down
Loading
Loading