Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/glassflow/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""GlassFlow SDK OpenTelemetry-native tracing for AI agents and LLM apps."""
"""GlassFlow SDK: OpenTelemetry-native tracing for AI agents and LLM apps."""

__version__ = "0.6.0" # x-release-please-version

Expand Down
2 changes: 1 addition & 1 deletion src/glassflow/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def init(
``<endpoint>/v1/heartbeat`` from init until process exit so the
platform can tell a live-but-idle agent from a vanished one.
heartbeat_interval: Seconds between pings (default 15, clamped to
``[5, 300]`` the backend derives staleness from this).
``[5, 300]``; the backend derives staleness from this).
agent_name: Identity heartbeats group under; defaults to
``service_name``.
heartbeat_transport: Override the heartbeat HTTP transport
Expand Down
8 changes: 4 additions & 4 deletions src/glassflow/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ def traces_endpoint(self) -> str:

@property
def heartbeat_endpoint(self) -> str:
"""Heartbeat URL (``<endpoint>/v1/heartbeat``) same host as traces."""
"""Heartbeat URL (``<endpoint>/v1/heartbeat``), same host as traces."""
return self.endpoint.rstrip("/") + "/v1/heartbeat"


def _clamp_sample_rate(value: float) -> float:
"""Clamp to [0.0, 1.0] an out-of-range value must degrade, not crash init()."""
"""Clamp to [0.0, 1.0]; an out-of-range value must degrade, not crash init()."""
if 0.0 <= value <= 1.0:
return value
clamped = min(max(value, 0.0), 1.0)
Expand All @@ -91,7 +91,7 @@ def _clamp_sample_rate(value: float) -> float:


def _clamp_heartbeat_interval(value: float) -> float:
"""Clamp to the contract bounds out-of-range degrades, never crashes init()."""
"""Clamp to the contract bounds; out-of-range degrades, never crashes init()."""
if HEARTBEAT_INTERVAL_MIN <= value <= HEARTBEAT_INTERVAL_MAX:
return value
clamped = min(max(value, HEARTBEAT_INTERVAL_MIN), HEARTBEAT_INTERVAL_MAX)
Expand Down Expand Up @@ -142,7 +142,7 @@ def resolve_config(
heartbeat: Enable the agent-lifetime heartbeat thread
(``GLASSFLOW_HEARTBEAT``). Off by default this release.
heartbeat_interval: Seconds between pings
(``GLASSFLOW_HEARTBEAT_INTERVAL``), clamped to ``[5, 300]``
(``GLASSFLOW_HEARTBEAT_INTERVAL``), clamped to ``[5, 300]``;
the backend derives staleness from this, so the bounds are part
of the wire contract.
agent_name: Identity heartbeats group under (``GLASSFLOW_AGENT_NAME``);
Expand Down
2 changes: 1 addition & 1 deletion src/glassflow/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def record_first_token(self) -> None:

Call from a streaming loop when the first content chunk arrives; the
backend derives time-to-first-token as the event time minus the span
start. Idempotent only the first call records; safe to call
start. Idempotent: only the first call records; safe to call
unconditionally per chunk. A no-op after ``end()``.
"""
if self._first_token_recorded or not self._span.is_recording():
Expand Down
12 changes: 6 additions & 6 deletions src/glassflow/heartbeat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
- First ping immediately at start (the agent appears without waiting an
interval), then every ``interval`` seconds.
- Graceful shutdown (``client.shutdown()`` / ``atexit``) sends a final
``stopped: true`` ping. No signal handlers are installed — a library
must not own process signals; an unhandled SIGTERM/SIGKILL means no
stopped ping, and the backend's stale→gone path covers exactly that.
``stopped: true`` ping. No signal handlers are installed, because a
library must not own process signals; an unhandled SIGTERM/SIGKILL means
no stopped ping, and the backend's stale→gone path covers exactly that.
- Never raises into user code. Pings have a short timeout, are never
retried or queued (liveness is only true fresh a late heartbeat is
retried or queued (liveness is only true fresh; a late heartbeat is
misinformation), and delivery problems warn once per process.
- ``fork()``: the child re-arms with a NEW ``instance_id`` one identity
- ``fork()``: the child re-arms with a NEW ``instance_id``, so one identity
never speaks for two processes.
"""

Expand Down Expand Up @@ -129,7 +129,7 @@ def _http_transport(url: str, headers: dict[str, str]) -> Callable[[dict[str, An
"""Default transport: a plain POST with a per-call timeout, no retries.

TLS certificate verification is urllib's default and is deliberately not
configurable here a liveness signal must not become a reason to accept
configurable here: a liveness signal must not become a reason to accept
unverified endpoints.
"""

Expand Down
2 changes: 1 addition & 1 deletion src/glassflow/instrumentation_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
over MCP becomes a TOOL-kind span: tool name, arguments, result, latency, and
error status. Generic instrumentation SDKs cover MCP unevenly (the OpenInference
MCP package only propagates context; it creates no spans), so we instrument it
ourselves. Registered in :mod:`glassflow.instrumentation` under ``"mcp"`` the
ourselves. Registered in :mod:`glassflow.instrumentation` under ``"mcp"``; the
top-level import of ``mcp`` below makes an environment without the package look
"not installed" to the registry, exactly like a missing third-party instrumentor.

Expand Down
8 changes: 4 additions & 4 deletions src/glassflow/masking.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

A ``SpanExporter`` wrapper that, before spans leave the process, either strips
content attributes (``capture_content=False``) or applies a caller-supplied
``mask``. It runs on every span it seesincluding third-party instrumentation —
so it's a single client-side choke point for sensitive data.
``mask``. It runs on every span it sees, including third-party
instrumentation, so it's a single client-side choke point for sensitive data.

Sanitization works on **copies**: a ``ReadableSpan`` shares its attribute dict
by reference with every processor on the provider, so mutating it in place
would rewrite what other exporters see (and race with their iteration).

Fail-closed guarantees: a mask that raises, returns ``None``, or returns a
value OTel can't encode never leaks the original the attribute is dropped
value OTel can't encode never leaks the original; the attribute is dropped
(or the return value serialized), and the rest of the batch is delivered.
"""

Expand Down Expand Up @@ -123,7 +123,7 @@ def _safe_value(value: Any) -> Any:
"""Coerce a mask's return into something OTel can encode, or None to drop.

BoundedAttributes-style cleaning silently refuses invalid values, which
would leave the ORIGINAL in place so we validate ourselves.
would leave the ORIGINAL in place, so we validate ourselves.
"""
if value is None or isinstance(value, _PRIMITIVES):
return value
Expand Down
6 changes: 3 additions & 3 deletions src/glassflow/spans.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Two surfaces, following the OpenTelemetry / Langfuse / Laminar convention:

- ``start_as_current_span`` context manager: activates the span in the OTel
context (so children nest under it) and auto-ends it.
- ``start_span`` manual: returns an ``Observation`` you must ``.end()``. The span
- ``start_as_current_span`` is the context manager: it activates the span in
the OTel context (so children nest under it) and auto-ends it.
- ``start_span`` is manual: it returns an ``Observation`` you must ``.end()``. The span
is parented to the current span at creation but is NOT set as current and does
NOT auto-record exceptions. For lifetimes a ``with`` block can't express
(streaming, callbacks, passing a span across boundaries).
Expand Down