Skip to content

Python SDK Reference

This section is generated from the installed zelos_sdk package.

Use the sidebar to browse modules, or jump directly:

Package Overview

zelos_sdk

ActionFailed

Bases: AgentError

An action ran and reported failure.

agent.actions.execute itself does not raise this — a failed run reports through ActionResult.ok / ActionResult.status instead. Raised only by callers that choose to escalate a failed result.

Example: >>> result = agent.actions.execute('battery_test', params={'target_soc': 80}) >>> if not result.ok: ... raise ActionFailed(result.status)

ActionInfo

Identifier carried by every entry from agent.actions.list().

Currently exposes a single name field; kept as its own type (rather than a plain string) so future fields (description, category, …) can ship without breaking for info in agent.actions.list(): info.name notebooks.

Example: >>> [info.name for info in agent.actions.list()] ['battery_test']

name instance-attribute

The action's registered name, the string execute() takes.

ActionResult

Result of agent.actions.execute(...) — parsed value plus a non-optional status.

A missing status from the agent reads as ActionStatus.Done. Fail/error statuses come back here rather than raising, so a caller branches on result.ok instead of try/except.

Example: >>> result = agent.actions.execute("battery_test", params={"target_soc": 80}) >>> result.ok True

ok instance-attribute

True on Pass / Done.

status instance-attribute

The action's terminal status.

value instance-attribute

Whatever the action returned, decoded from JSON to a dict, list, scalar, or None.

ActionSchema

Schema from agent.actions.schema(...) with pre-parsed JSON objects.

Schema blobs (action_schema, ui_schema) are pre-parsed dicts.

Example: >>> agent.actions.schema("battery_test").action_schema["properties"]

action_schema instance-attribute

JSON Schema for the action's parameters, as a dict.

default_timeout_ms instance-attribute

Timeout the action declares for itself, in milliseconds; None when it declares none.

ui_schema instance-attribute

UI hints for rendering the parameter form, as a dict.

ActionStatus

Typed action execution status: one of ActionStatus.Pass, .Fail, .Error, .Done.

Compares equal to its lowercase wire string ("pass" / "fail" / "error" / "done"), like a str enum.

Example: >>> result = agent.actions.execute("battery_test") >>> result.status == "pass" True

Done instance-attribute

Typed action execution status: one of ActionStatus.Pass, .Fail, .Error, .Done.

Compares equal to its lowercase wire string ("pass" / "fail" / "error" / "done"), like a str enum.

Example: >>> result = agent.actions.execute("battery_test") >>> result.status == "pass" True

Error instance-attribute

Typed action execution status: one of ActionStatus.Pass, .Fail, .Error, .Done.

Compares equal to its lowercase wire string ("pass" / "fail" / "error" / "done"), like a str enum.

Example: >>> result = agent.actions.execute("battery_test") >>> result.status == "pass" True

Fail instance-attribute

Typed action execution status: one of ActionStatus.Pass, .Fail, .Error, .Done.

Compares equal to its lowercase wire string ("pass" / "fail" / "error" / "done"), like a str enum.

Example: >>> result = agent.actions.execute("battery_test") >>> result.status == "pass" True

Pass instance-attribute

Typed action execution status: one of ActionStatus.Pass, .Fail, .Error, .Done.

Compares equal to its lowercase wire string ("pass" / "fail" / "error" / "done"), like a str enum.

Example: >>> result = agent.actions.execute("battery_test") >>> result.status == "pass" True

value instance-attribute

Wire-string view ("pass" / "fail" / "error" / "done").

Agent

A handle to a Zelos agent — the entry point for signals, queries and checks.

Construction is lazy: no network call happens until the first RPC, and the channel reconnects on its own across agent restarts. Use zelos_sdk.connect() when you want the connection validated up front.

Example: >>> import zelos_sdk >>> agent = zelos_sdk.Agent("http://localhost:2300") >>> agent.signals()[0].path 'bus0/BMS_message/status.pack_current'

actions instance-attribute

The action registry and executor for this agent.

check instance-attribute

Typed predicate Check API. Returns a Checks proxy bound to this agent: agent.check.that(signal, op, rhs, **kwargs) builds a spec and runs it through _run_check. Results auto-post to any active pytest Checker board via the ContextVar in zelos_sdk.pytest.checker.

extensions instance-attribute

Extension management (list, start, stop, logs) for this agent.

layouts instance-attribute

Console-backed layout CRUD (Agent.layouts).

Returns an AgentLayouts handle sharing this agent's transport. list() / show(id) / create(name, data) / update(id, ...) / delete(id) all round-trip to the agent's layout service, which requires console login.

target instance-attribute

The resolved agent endpoint this handle talks to.

url instance-attribute

Alias of [Self::target], matching the url= constructor keyword.

__del__()

Best-effort cleanup when the handle is GC'd without close() / with. close() is idempotent, so explicit cleanup still wins.

__new__(target=None, *, url=None)

Create and return a new object. See help(type) for accurate signature.

at(paths, time, *, min_time=None, producers=None, lookback=None)

Per-signal value at (or before) a specific timestamp.

Returns a Snapshot mapping each resolved signal's path to its most recent LatestValue whose timestamp is <= time. Every dict spelling works on it. time and min_time accept datetime, ISO / relative string ("-1m", "now"), or raw int epoch ns. Use min_time to floor the search. producers = None (default) covers every connected producer.

Args: paths: A str, Signal, or sequence thereof. time: Anchor timestamp (datetime, str, or epoch ns). min_time: Optional lower bound; values strictly older than this are skipped. Also floors the window wildcards resolve against. producers: Producer addresses; None covers every connected producer.

Returns: Snapshot: One entry per matched signal, keyed by path.

Raises: SignalNotFound: a wildcard expanded to zero matches. ValueError: min_time > time.

close()

Close the transport and drop the catalog cache.

Idempotent. Subsequent RPCs on this Agent (or any Trace/TraceSet derived from it) raise AgentCancelled. Prefer with Agent(...).

close_traces(paths=[])

Release one or more open trace files in a single RPC. Empty (default) closes all.

connect(timeout=5.0)

export(path, *, start, end=None, signals=None, paths=None, producers=None, lookback=None, overwrite=False)

Export signal data to a .trz file on disk.

signals selects the producers that own the matched signals, and each of those producers writes its whole slice of the time window into the same output file — the exported trace is wider than the filter. When signals is None (default) every connected producer writes. The agent does the actual writing; on a localhost target with localhost producers an atomic tmp-rename strategy guards against partial files when overwrite=True and the destination already exists.

Cross-host targets reject overwrite=False outright (the gate has no way to atomically replace a file on a remote host); pass overwrite=True to opt in.

Args: path: Filesystem destination for the .trz file. start: Start of the time window. end: End of the window; defaults to "now". signals: Optional str, Signal, or sequence naming the producers to export; None (default) exports every connected producer. (paths= is the older spelling.) producers: Producer addresses; None (default) covers every connected producer. overwrite: When True, replace an existing file. Required for cross-host writes regardless of whether the path exists.

Returns: ExportResult: On-disk path plus per-producer success bits. result.ok is only True if every producer succeeded.

Raises: ValueError: overwrite=False on a cross-host target. FileExistsError: overwrite=False and the destination already exists.

health()

Round-trip the agent's Health RPC and return its status string.

Example: >>> agent.health() 'OK'

health_check()

info()

Composite identity + settings + memory + dirs.

Health-check gates; the four sub-RPCs (settings/memory/logs/configs) fan out concurrently and partial failures land in info.failures rather than raising.

is_connected(*, timeout=1.0)

Round-trip reachability probe — runs HealthCheck with a short deadline and returns True iff the agent answered SERVING.

This costs a network round-trip (~1 ms typical, ~timeout on failure) — it is a question, not a property. Ask it when you need the answer; there is no cached "connected" flag to read, because the lazy channel reconnects on its own and any such flag would lie.

Every failure reads as False. Call health() when you want the typed error instead.

latest(paths, *, lookback=60.0, producers=None)

Latest value(s): one literal path → LatestValue; a wildcard pattern or a sequence → Snapshot, a read-only Mapping[str, LatestValue] (missing keys omitted).

A single path or pattern: no catalog match → SignalNotFound; a literal that matched but has no rows in lookback → NoData; two producers owning that literal → AmbiguousSignal. Sequence: misses omit keys. producers = None (default) covers every connected producer.

Args: paths: A str, Signal, or sequence thereof. lookback: How far back to look for a value (default 60 s). producers: Producer addresses; None covers every connected producer.

query(paths, *, start, end=None, producers=None, lookback=None, max_rows=0, sort=None, sort_order=None, downsample=None)

Time-bounded query → SignalFrame (Arrow IPC). Empty frame when the signals resolved but no rows fall in the window.

A path that names no signal raises SignalNotFound rather than returning a frame quietly missing that column — including a literal the catalog lacks that no producer answers for.

paths accepts a str, Signal, or sequence. start/end accept datetime, ISO 8601, or relative strings ("-1m", "now"). downsample = N runs the agent's M4 strategy with N buckets and is mutually exclusive with max_rows / sort_order. max_rows = 0 means no cap. producers = None (default) covers every connected producer. Wildcards expand against the signals active inside [start, end]. Convert via frame.to_pandas() or frame.series(name).

resolve_paths(paths, *, producers=None, lookback=60.0)

segments(producers=None)

Live data segments. producers = None (default) covers every connected producer; pass a str or a sequence of addresses to narrow it.

Per-producer warnings (an unreachable producer, a stale segment) are emitted as RuntimeWarning so partial results never silently hide failures. Catch with warnings.catch_warnings() to inspect them.

signal(path)

Path-only Signal reference. Shorthand for Signal.from_path(path) — no catalog roundtrip, no SignalNotFound. The path is verified server-side at the next check / latest / query invocation, so the resolver picks the freshest matching segment at evaluation time.

Use this in tests and extension consumers that need a typed signal handle before (or independent of) the catalog being populated.

signals(producers=None, lookback=60.0)

Signal catalog. producers = None (default) covers every connected producer; pass a str or a sequence of addresses to narrow it.

An explicit call always fetches — the short-lived cache behind wildcard resolution would otherwise hand back a catalog from before the producer you just started. Per-producer failures land in catalog.warnings and are also emitted as RuntimeWarning.

Args: producers: Producer addresses; None covers every connected producer. lookback: Seconds since a signal last produced a sample (default 60).

trace(path)

Open a trace file. Returns a Trace handle sharing this agent's transport.

Raises TraceNotFound if the agent doesn't have the file. The returned Trace is a context manager — prefer with agent.trace(path) as trace: over manual trace.close().

traces(paths)

Open multiple traces as one queryable set; routes per-signal to the owning file.

paths is a sequence of files — the runs are then named by file stem — or a {label: path} mapping when you want to name them yourself (agent.traces({"baseline": "a.trz", "candidate": "b.trz"})).

Raises ValueError on empty paths, TraceNotFound if any file is unknown. TraceSet is a context manager — prefer with agent.traces([...]) as traces: over manual traces.close().

watch(paths=None, *, interval=1.0, lookback=60.0, producers=None, until=None, on_error='raise', max_consecutive_errors=5)

Yield a snapshot of the latest values every interval until until. producers = None (default) covers every connected producer.

window(paths, *, start, duration, producers=None, lookback=None, display_fps=0)

Replay snapshot + change-stream over a fixed time window.

Returns a ReplayWindow with two parts: the snapshot (latest value per signal at start) and the changes (every subsequent update inside the window). start accepts datetime, str, or int ns; duration accepts int/float seconds, a datetime.timedelta, or a string like "5s". producers = None (default) covers every connected producer. display_fps = 0 means no thinning.

Args: paths: A str, Signal, or sequence thereof. start: Window start. duration: Window length. producers: Producer addresses; None covers every connected producer. display_fps: Optional display-rate cap on the change stream.

Returns: ReplayWindow: Snapshot + changes for the window.

Raises: SignalNotFound: a wildcard expanded to zero matches. ValueError: duration <= 0 or display_fps < 0.

AgentActions

Agent action API (Agent.actions): list, schema, execute over gRPC (no caching).

Callers pass Python dicts; they are JSON-encoded at the wire boundary.

Example: >>> [info.name for info in agent.actions.list()] ['battery_test'] >>> result = agent.actions.execute("battery_test", params={"target_soc": 80}) >>> result.ok True

execute(action, params=None, timeout=None)

Run action with optional params. Status lives on ActionResult, not exceptions.

Args: timeout: Seconds; None uses the agent default (typically 30 s).

Returns: ActionResult: Always — a failed run reports through result.ok / result.status.

Raises: Internal: the agent answered without a result payload.

Example: >>> result = agent.actions.execute("battery_test", params={"target_soc": 80}) >>> result.ok True

list()

Registered actions from actions_list (order as on the agent; not cached).

Returns: list[ActionInfo]: One entry per action, with at least a .name field.

Raises: AgentUnavailable: agent is unreachable.

schema(action, current_values=None)

JSON-Schema and ui-schema for action, pre-parsed as dicts.

Args: current_values: Partial form data for dynamic schemas; None omits from the wire.

Returns: ActionSchema: The action's form schema.

Raises: Internal: the agent answered without a schema payload.

Example: >>> agent.actions.schema("battery_test").action_schema["properties"]

AgentCancelled

Bases: AgentError

The RPC was cancelled or its deadline expired.

Example: >>> try: ... agent.query(['bus0/BMS_message/status.pack_current'], start='-5m', timeout=0.001) ... except AgentCancelled: ... pass

AgentError

Bases: Exception

Base class for every Agent SDK wire or RPC failure.

Every subclass carries suggestions, matches, and cause attributes (empty/None unless the specific subclass fills them in), so a broad handler can inspect any of them without an AttributeError.

Example: >>> try: ... agent.latest('bus0/typo.voltage') ... except AgentError as exc: ... exc.cause

matches instance-attribute

Catalog entries an ambiguous selector matched; empty unless the raise site listed them.

suggestions instance-attribute

Closest catalog paths to what was asked for; empty unless the raise site computed hints.

AgentExtensions

Sub-namespace exposing the agent's extension lifecycle API.

Reached via Agent.extensions. The handle holds a transport clone plus a shared handle to the parent agent's signals catalog cache — start(), stop(), and restart() invalidate that cache so the next agent.signals() call sees any new producers an extension brought online (or saw a producer go away).

list() enumerates installed extensions; info(id) and readme(id) return descriptive metadata; config_schema(id) and last_config(id) return the JSON-Schema and saved config (decoded to dict). start(id, config={...}) and restart(id, config={...}) launch (or relaunch) the extension and return a typed ExtensionStart carrying the resolved version + pid.

Example: >>> [e.id for e in agent.extensions.list()] ['zeloscloud.zelos-extension-can'] >>> agent.extensions.start( ... "zeloscloud.zelos-extension-can", ... config={"buses": [{"interface": "demo"}]}, ... ).pid 4821

config_schema(id, version=None)

Returns the extension's JSON-Schema config, decoded into a dict. None means "no schema declared"; an empty dict means "configured but with no fields" (proto schema_json = "{}").

info(id, version=None)

Fetch metadata for a specific extension.

Returns the extension's manifest data: id, version, description, any actions/signals it provides, and so on. Useful for building catalog UIs or verifying an extension is installed before start()-ing it.

Args: id: Extension identifier (e.g. "zeloscloud.zelos-extension-can"). version: Specific version to query; None resolves to the currently-installed version.

Returns: ExtensionInfo: The extension's manifest.

Raises: ExtensionError: nothing matching id is installed. Internal: the agent answered without a manifest.

Example: >>> agent.extensions.info("zeloscloud.zelos-extension-can").version

last_config(id, version=None)

Returns the last saved config, decoded into a dict. None means "never configured"; an empty dict means "configured but with no fields" (proto config_json = "{}").

list()

List every extension installed in the agent.

The returned ExtensionEntry objects carry id, version, current ExtensionState (Installed or Running), and pid (when running). The list is not cached, so it always reflects the agent's current view.

Returns: list[ExtensionEntry]: One entry per installed extension.

Raises: AgentUnavailable: agent is unreachable.

readme(id, version=None)

Fetch the extension's README markdown.

Useful for building marketplace UIs or showing inline help in a notebook. Returns the raw markdown verbatim, or an empty string when the extension shipped no README — render it directly with whatever tooling fits the surface.

Args: id: Extension identifier. version: Specific version; None resolves to the currently-installed version.

Returns: str: Markdown text, or "" when the extension shipped no README.

restart(id, config=None)

Stop then start an extension, applying config on the way back up.

See [Self::start] for config = None vs config = {} semantics.

Example: >>> agent.extensions.restart("zeloscloud.zelos-extension-can")

start(id, config=None)

config = None means "no config" (transport sends nothing on the wire). config = {} means "empty config" (transport sends "{}" on the wire). The returned ExtensionStart already carries the resolved version and pid, so no follow-up call is needed.

Example: >>> agent.extensions.start("zeloscloud.zelos-extension-can").version

stop(id)

Stop a running extension.

Asks the agent to terminate the extension process; the signals-catalog cache is invalidated so the next agent.signals() call reflects the producer leaving. Idempotent — stopping an already-stopped extension is a no-op.

Args: id: Extension identifier.

Raises: ExtensionError: agent reported an error tearing down the extension process.

AgentInfo

Composite agent identity and runtime state.

Returned by Agent.info(). Bundles the connection target, latest health-check result, and best-effort introspection: the agent's AgentSettings, current memory usage, and the on-disk paths the agent uses for logs and configs.

info() runs four optional sub-RPCs concurrently behind the scenes (settings + memory + logs-dir + configs-dir); each of them may fail independently of the others. Failed sub-RPC names appear in failures (a stable list of strings — "SettingsGet", "SystemGetMemory", "SystemGetDirLogs", "SystemGetDirConfigs" — useful for grep-friendly notebook checks). Only the gating health-check is fatal; everything else is fail-soft.

Example: >>> info = agent.info() >>> info.target, info.health ('http://127.0.0.1:2300', 'OK') >>> info.failures []

configs_dir instance-attribute

Directory the agent keeps its configs in; None when that sub-call failed.

failures instance-attribute

Names of the optional sub-calls that failed, e.g. ["SystemGetMemory"].

health instance-attribute

Result of the gating health check; "OK" when the agent answered.

logs_dir instance-attribute

Directory the agent writes its logs to; None when that sub-call failed.

memory_bytes instance-attribute

Memory the agent process is using, in bytes; None when that sub-call failed.

settings instance-attribute

The agent's settings; None when that sub-call failed.

target instance-attribute

The agent endpoint this info was read from.

AgentLayouts

Sub-namespace exposing the agent's layout CRUD API.

Reached via Agent.layouts. The handle holds a transport clone. Every method round-trips to the agent's console-backed layout service, which requires the agent to be logged in — unauthenticated calls surface as the usual typed AgentError subclasses.

list() enumerates every layout visible to the logged-in user; show(id) fetches one; create(name, data, *, is_personal=False) saves a new layout and returns it; update(id, *, name=..., data=..., is_personal=...) patches an existing layout (read-modify-write — omitted fields keep their current values); delete(id) removes one.

Every update is also recorded in a Google Docs-style version history: versions(id) lists a layout's versions (newest first, metadata only); show_version(id, version) fetches one version with its data snapshot; restore(id, version) restores the layout to a previous version (appended to the history as a new version — no history is lost); set_version_label(id, version, label) names a version (None clears — labeled versions are exempt from retention pruning).

Example: >>> created = agent.layouts.create("My Dashboard", {"panels": []}) >>> agent.layouts.update(created.id, name="Renamed").name 'Renamed' >>> agent.layouts.delete(created.id)

create(name, data, *, is_personal=False)

Create a new layout and return it.

Args: name: Human-readable layout name. data: Arbitrary JSON-serializable dict (panels, config, …). is_personal: When True, scope the layout to the current user rather than the team. Defaults to False.

Returns: Layout: The newly-created layout, with its server-assigned id.

Raises: ValueError: data is not JSON-serializable. AgentError: the agent is not logged in, or returned malformed data.

delete(id)

Delete a layout by id.

Args: id: The layout's UUID (as a string).

Raises: ValueError: id is not a valid UUID. AgentError: the layout does not exist, or the agent is not logged in.

list()

List every layout visible to the logged-in console user.

Returns: list[Layout]: One entry per saved layout (may be empty).

Raises: AgentUnavailable: agent is unreachable. AgentError: agent is not logged in, or returned malformed data.

restore(id, version)

Restore a layout to a previous version.

The restore is appended to the history as a new version (Google Docs-style — no history is lost), so it can itself be undone by restoring an earlier version.

Args: id: The layout's UUID (as a string). version: The version number to restore (versions start at 1).

Returns: Layout: The layout after the restore.

Raises: ValueError: id is not a valid UUID, or version is outside the valid range (1 to 4294967295). AgentError: the layout or version does not exist, or the agent is not logged in.

set_version_label(id, version, label)

Set or clear a layout version's label (name).

Labeled versions are kept forever — they are exempt from the version-retention pruning. The label is trimmed before sending; pass None (or a blank string) to clear it.

Args: id: The layout's UUID (as a string). version: The version number to label (versions start at 1). label: The label to set, or None to clear the current label.

Returns: LayoutVersion: The updated version metadata.

Raises: ValueError: id is not a valid UUID, or version is outside the valid range (1 to 4294967295) — both raised before any network call. Also raised when the server rejects the request: a label longer than 100 characters, or naming a version when the layout already has 40 named versions. AgentError: the layout or version does not exist, or the agent is not logged in.

show(id)

Fetch a single layout by id.

Args: id: The layout's UUID (as a string).

Returns: Layout: The requested layout.

Raises: ValueError: id is not a valid UUID. AgentError: the layout does not exist, or the agent is not logged in.

show_version(id, version)

Fetch a single layout version, including its data snapshot.

Args: id: The layout's UUID (as a string). version: The version number to fetch (versions start at 1).

Returns: LayoutVersion: The requested version, with its data snapshot.

Raises: ValueError: id is not a valid UUID, or version is outside the valid range (1 to 4294967295). OverflowError: version does not fit in a signed 64-bit integer. AgentError: the layout or version does not exist, or the agent is not logged in.

update(id, *, name=None, data=None, is_personal=None)

Update an existing layout and return the updated version.

Any kwarg left as None keeps its current value. The full (name, data, is_personal) triple is always sent on the wire, so any omitted field is filled from the current layout via a show() round-trip (skipped only when all three are provided). Sending the full triple keeps updates correct against older bundled agents, which predate partial updates and would read a missing is_personal as False (silently un-sharing a personal layout). Pass at least one kwarg — an all-None call raises ValueError without touching the network.

Args: id: The layout's UUID (as a string). name: New name, or None to keep the current name. data: New payload dict, or None to keep the current payload. is_personal: New personal/team scope, or None to keep the current scope.

Returns: Layout: The updated layout.

Raises: ValueError: id is not a valid UUID, data is not JSON-serializable, or all three kwargs are None. AgentError: the layout does not exist, or the agent is not logged in.

versions(id)

List a layout's version history, newest first.

Version entries are metadata-only — their data snapshots come back as None. Fetch a specific snapshot with show_version().

Args: id: The layout's UUID (as a string).

Returns: list[LayoutVersion]: One entry per saved version, newest first.

Raises: ValueError: id is not a valid UUID. AgentError: the layout does not exist, or the agent is not logged in.

AgentSettings

Raw settings snapshot from SettingsGet. Wire fields cross verbatim; the Python AgentSettings dataclass in agent/info.py re-exports the same data (memory/disk strings stay strings — they are human-readable like "25%" / "10GB" and are parsed against actual host capacity at runtime).

Plot UX defaults and subscription / version-migration fields are omitted (not part of the SDK's public surface).

memory_limit / disk_limit are strings (e.g. "25%", "10GB"), not u64 — the agent stores human-readable strings and parses them against actual host capacity at runtime.

Example: >>> settings = agent.info().settings >>> settings.store_type, settings.memory_limit ('memory', '25%')

data_retention instance-attribute

datetime.timedelta; None means time-based pruning is disabled (or the wire value would overflow chrono::Duration — purely defensive, realistic retention values stay far below i64::MAX seconds). Call .total_seconds() for raw seconds.

dev_mode instance-attribute

True when the agent runs with developer features enabled.

disk_limit instance-attribute

e.g. "10GB", "25%"; None means unlimited.

log_retention instance-attribute

See [Self::data_retention]; None means no limit.

memory_limit instance-attribute

e.g. "25%", "10GB"; None means unlimited.

store_path instance-attribute

None means platform-default data directory. Only meaningful when store_type == "disk".

store_type instance-attribute

Lowercase "memory" (ArrowStore, default), "disk" (ParquetStore), or "metadata" (catalog only, no data — headless agents) — matches the StoreType serde encoding so notebook code lines up with CLI / app JSON debugging.

AgentUnavailable

Bases: AgentError

No agent answered at the target.

Covers a dead connection, a closed port, and an agent that was killed or restarted mid-RPC.

Example: >>> try: ... agent.latest('bus0/BMS_message/status.pack_current') ... except AgentUnavailable: ... reconnect()

AmbiguousSignal

Bases: AgentError

A path or pattern matched more than one signal.

Carries the matching catalog entries as matches: list[Signal]; pass one of them directly to disambiguate.

Example: >>> try: ... agent.latest('*/BMS_message/status.pack_current') ... except AmbiguousSignal as exc: ... [s.path for s in exc.matches] ['bus0/BMS_message/status.pack_current', 'bus1/BMS_message/status.pack_current']

CheckEvidence

The single sample that decided a CheckResult's verdict: which signal, at what time, with what value. None on result.evidence when the check has no one deciding sample (e.g. a passing count check).

Example: >>> result = agent.check.that( ... "bus0/BMS_message/status.pack_current", "<", 100, temporal="always", last="5m" ... ) >>> result.evidence.signal 'bus0/BMS_message/status.pack_current'

arrow_value instance-attribute

Raw 1-row, 1-column Arrow IPC RecordBatch bytes (same convention as LatestSignalValue.arrow_value). None when the cell is NULL or the column type is unsupported.

rhs_value instance-attribute

Typed value of the right-hand signal at the evidence row, or None when the check compared against a literal.

signal instance-attribute

Clean path of the signal the evidence sample came from.

time_ns instance-attribute

When the evidence sample was recorded, epoch nanoseconds.

value instance-attribute

Typed sample value at the evidence row. Decoded once from the underlying 1-row, 1-column Arrow IPC payload — returns Python bool / int / float / str / bytes matching the column type, or None for NULL / unsupported types.

CheckResult

Outcome of running a CheckSpec against the agent.

bool(result) is True iff status == "pass". result.failed is True for both fail (predicate violated, evidence available) and error (precondition failed, no evaluation possible).

Example: >>> result = agent.check.that("bus0/BMS_message/status.pack_current", "<", 100) >>> result.passed True

count instance-attribute

Populated for temporal="count"; otherwise None.

evidence instance-attribute

The sample that decided the verdict — the extreme on a pass, the first violation on a fail. None when the check could not be evaluated.

failed instance-attribute

True for Status::Fail (predicate violated) or Status::Error (precondition failed). The reason and evidence distinguish them.

fired_at_ns instance-attribute

Wall-clock UTC nanoseconds the agent captured the moment this check finished evaluating. Same field the checkerboard's timestamp column renders for live, trace, and literal-only checks alike.

message instance-attribute

Free-form human message for error/type_mismatch/signal_not_found.

name instance-attribute

Name of the spec this result came from.

passed instance-attribute

True iff the predicate held over the entire range (or for count, always — count results carry the count, not a verdict).

query_ms instance-attribute

Time spent in the executor's query plan / SQL execution, in milliseconds.

reason instance-attribute

Why the check ended as it did ("ok", "signal_not_found", "type_mismatch", ...).

status instance-attribute

The verdict as a wire string: "pass", "fail", or "error".

wall_ms instance-attribute

Total wall-clock time the agent spent on the check, in milliseconds.

CheckResults

Bases: list

A run's results: a plain list that also knows its own tally.

bool(results) is "nothing failed and nothing errored" — not "non-empty" — so a suite reads as one verdict.

Example: >>> results = agent.check.suite("checks/battery.json") >>> len(results.failed) 0 >>> results.raise_if_failed()

errors property

The results that never got as far as a verdict.

failed property

The results whose predicate did not hold.

passed property

The results whose predicate held.

raise_if_failed()

Raise AssertionError listing every check that did not pass — each on its own line, with its predicate, window and evidence.

For a single check, assert agent.check.that(...): a result is truthy when it passed.

CheckSpec

A typed check specification — predicate + time range + temporal quantifier.

Constructed by the Python agent.check.that(...) / agent.check.count(...) helpers. Field access via typed getters (signal, op, temporal, start_ns, end_ns, rhs).

Example: >>> spec = CheckSpec( ... "pack_current_ok", ... signal="bus0/BMS_message/status.pack_current", ... op="<", ... rhs=100.0, ... ) >>> spec.op '<'

duration_ns instance-attribute

How long the predicate must hold for the check to pass, in nanoseconds; None means a single sample is enough.

end_ns instance-attribute

End of the window the check evaluates, epoch nanoseconds; None for a latest check.

lhs instance-attribute

("literal", <python_value>) or ("signal", <signal_path>). Symmetric with .rhs so Python wrappers render either side uniformly on the rich-table board.

lookback instance-attribute

Seconds of history a latest check looks back over when it resolves its signal.

name instance-attribute

The check's name, echoed on every CheckResult it produces.

op instance-attribute

The comparison, in its canonical spelling ("<", "is_close", ...).

producers instance-attribute

Producer addresses the signal resolves against; empty means every connected producer.

rhs instance-attribute

("literal", <python_value>) or ("signal", <signal_path>) for binary specs; None for unary specs (is_empty, is_positive, ...). The Python wrapper uses this to render the RHS on the rich-table board — the None case renders as an empty cell.

start_ns instance-attribute

Start of the window the check evaluates, epoch nanoseconds; None for a latest check.

strict_resolution instance-attribute

Whether the resolver should opt out of newest-segment-wins disambiguation for this spec. See agent.check.that(..., strict=...).

temporal instance-attribute

How samples are quantified: "latest", "always", "ever", "never", or "count".

tolerance instance-attribute

Tolerance for is_close / is_approximately; None for any other op. Returned as a 3-tuple (rel_tol, abs_tol, nan_ok).

__new__(name, signal=None, op=None, *, lhs_value=None, lhs_is_null=False, rhs=None, rhs_signal=None, rhs_is_null=False, temporal='latest', start_ns=None, end_ns=None, lookback=60.0, tolerance=None, duration_ns=None, producers=[], strict=False)

Create and return a new object. See help(type) for accurate signature.

from_json(_cls, s) classmethod

Parse and validate a CheckSpec from a JSON string.

The same loader the CLI uses for suite files, so a spec built as a dict round-trips through CheckSpec.from_json(json.dumps(spec)) and is held to exactly the same schema.

Checks(parent)

Proxy returned by Agent.check and Trace.check: .that() / .count() / .run() / .suite().

Example: >>> result = agent.check.that("bus0/BMS_message/status.pack_current", "<", 100) >>> result.passed True

count(lhs, op='is_true', rhs=None, *, producers=None, last=None, start=None, end=None, name=None, lookback=DEFAULT_LOOKBACK_S, rel_tol=None, abs_tol=None, nan_ok=False, strict=False)

Count the satisfying samples. Posts to any active Checker board.

Returns the :class:CheckResult, not a bare number: int(result) is the count, and it raises rather than reading 0 off a result that never got as far as counting. Requires at least one signal operand (a literal-only count is degenerate — there are no samples to enumerate). Shape-changes on producers the same way as :meth:that.

strict matches :meth:that — pass True to require a unique segment per signal path (opt out of newest-segment-wins disambiguation).

run(spec, *, producers=None, interval_s=DEFAULT_DURATION_INTERVAL_S, display_lhs=None, display_rhs=None, pure_literal=None)

Run a pre-built :class:CheckSpec and post the result(s).

Lower-level seam: bypasses the kwargs ergonomics. Useful for replaying specs loaded from JSON suites or constructed programmatically.

Same shape-change as :meth:that — scalar for a single producer, dict for fan-out. Per-producer transport failures synthesize :class:CheckResult (status="error", reason="internal_error") entries so the caller always sees one entry per requested producer.

temporal="for_duration" / "within_duration" specs dispatch into a client-side polling loop (cadence interval_s) — the wire-level spec records the original temporal so the artifact and any future streaming RPC stay identical; only the execution path differs.

Routing: signal-bearing specs go through the agent's gRPC executor (point-in-time or polling). Literal-only specs (pure literals or TraceSourceCacheLastField operands whose values were captured at spec-build time) are evaluated in-process — no gRPC round-trip, no agent dependency. Same CheckResult shape either way; same Checker board / artifact output.

Emission: while a Checker session is active, each posted result also publishes a zelos.check.result.v1 event through a shared TraceSource("checks"). Outside a session there is no sink, no emission, and the literal-only path stays pure in-process.

suite(path, *, producers=None, last=None, start=None, end=None, lookback=DEFAULT_LOOKBACK_S, strict=False)

Run a JSON check suite and post each result to the Checker board.

path may be a single .json file, a directory (every *.json inside, lex order), a glob pattern, or a list of any of the above.

Each JSON file holds an array of spec dicts. An operand is {"signal": "<path>"} for a catalog lookup, or a bare JSON scalar for a literal. range is required for every temporal but latest, and last= / start=+end= fill it in when the file omits it. One entry reads:

[
  {
    "name": "pack current under load",
    "lhs": {"signal": "bus0/BMS_message/status.pack_current"},
    "op": "<",
    "rhs": 120.0,
    "temporal": "always",
    "range": {"start_ns": 1000, "end_ns": 9000}
  }
]

Element shape follows :meth:run: each spec yields a scalar :class:CheckResult for single-producer (the default), or a {address: CheckResult} dict for fan-out.

last / start+end (mutually exclusive) inject a runtime window into specs whose JSON omits range. Same JSON suite ports between live (with e.g. last=30.0 or start="-30s", end="now") and trace (TraceChecks.suite fills the trace's full extent automatically). lookback sets the resolution-freshness default that flows onto every spec on the wire.

strict is the suite-level default for strict_resolution — applied only to entries whose JSON omits the field (entries that set it explicitly win). Mirrors the lookback injection semantics.

The signal-bearing specs are batched into one LiveCheck RPC against the agent (per-agent customization: each producer in producers gets the same spec list, sent in one call). Literal-only specs (pure literals or captured TraceSourceCacheLastField references) evaluate in-process, with no RPC. Each result posts to the active Checker board in input order regardless of which path it took.

that(lhs, op='is_true', rhs=None, *, producers=None, last=None, start=None, end=None, temporal=None, name=None, lookback=DEFAULT_LOOKBACK_S, rel_tol=None, abs_tol=None, nan_ok=False, duration_s=None, interval_s=DEFAULT_DURATION_INTERVAL_S, nonblocking=False, strict=False)

Assert a typed predicate lhs op rhs (or unary lhs op).

Both lhs and rhs accept the same shapes:

  • bool / int / float / str → literal.
  • Signal handle (from agent.signals().by_path(...) or trace.signals().by_path(...)) → signal reference; the agent fetches the underlying samples (full range for always / ever / never / count, latest for latest) and evaluates server-side.
  • TraceSourceCacheLastField → the current cached value is captured via .get() at spec build time and sent on the wire as a literal — the agent stores the check as an event with the source path preserved for naming / replay.
  • SignalSeries (an lhs column of a queried frame) → check the thing you just queried. A signal-backed series resolves to its signal and to its own time extent, so the window is the one you queried; passing last= / start= / end= alongside it is an error. A derived series (cell_a - cell_b) is evaluated in-process over its own samples.

op defaults to "is_true" so any Python predicate result becomes a checked assertion routed through the agent — check.that(isinstance(foo, int)), check.that(x is None), check.that(0 < y < 10) all work without needing an explicit op. Encourages the rich check artifact over plain assert.

rhs is omitted for unary ops (is_empty, is_positive / is_not_positive, is_negative / is_not_negative, is_true, is_false).

rel_tol / abs_tol / nan_ok set the tolerance for the tolerance ops (is_close / is_around, is_approximately / ~=). Each defaults to None / False — left unset, the op's stdlib default applies (is_closemath.isclose: rel_tol=1e-9, abs_tol=0; is_approximatelypytest.approx: rel_tol=1e-6, abs_tol=1e-12). Override any subset.

All combinations route through the agent CheckMulti RPC — the agent is the single eval/storage point.

Shape-changes by the producers argument:

  • producers=None (default, the local store) or a single address → one :class:CheckResult.
  • producers=() or a multi-address list → fan-out; returns {address: CheckResult}.

None is the local store, not every connected producer as it is on :meth:Agent.query.

With no last / start / end, defaults to temporal="latest" (most recent sample). When a range is given, defaults to temporal="always" (every sample must satisfy). last= reads the duration grammar — 120, "2m", timedelta(minutes=2).

Mistakes raise here rather than becoming an error row: comparing two incomparable literals is a TypeError, and a window on a check with no samples to quantify over is a ValueError.

temporal="for_duration" + duration_s=... asserts the predicate holds continuously from now until the duration elapses; temporal="within_duration" asserts the predicate becomes true at least once before the duration elapses. Both run as a client-side polling loop over temporal="latest" with cadence interval_s (default :data:DEFAULT_DURATION_INTERVAL_S). On :class:TraceChecks the same temporals desugar to Always / Ever over [trace.start_ns, trace.start_ns + duration_s*1e9] — the wire spec is identical, the executor differs.

ColumnMetadata

Per-column metadata attached to a query result: which signal a column holds, its producer, segment, and the time range it covers.

signal keeps its wire name (not name) because name collides with __name__-style lookups when used as a pandas/pyarrow column header.

Example: >>> frame = agent.query(["bus0/BMS_message/status.pack_current"], start="-5m") >>> frame.columns[0].signal 'pack_current'

data_segment_id instance-attribute

The data segment this column belongs to, None when segmentation is not in play.

end_time_s instance-attribute

End of the covered time range, in seconds, None when unknown.

message instance-attribute

The column's message ({source}/{message}.{signal}).

path instance-attribute

Clean signal path: "{source}/{message}.{signal}". Producer / segment disambiguation lives on the matching column name and on the producer / data_segment_id fields; this getter is just the human-readable label for display and notebook lookups.

producer instance-attribute

The producer this column came from, None when the query carries no producer metadata.

signal instance-attribute

The column's wire signal name (see the class docstring for why this isn't called name).

source instance-attribute

The column's source ({source}/{message}.{signal}).

start_time_s instance-attribute

Start of the covered time range, in seconds, None when unknown.

trace_path instance-attribute

The trace file this column was read from, None for a live query.

ConnectionTargetError

Bases: AgentError, ValueError

The agent target string could not be parsed into a URL.

Also a ValueError, so except ValueError: catches it too.

Example: >>> try: ... zelos_sdk.connect('not a url') ... except ConnectionTargetError as exc: ... str(exc) "could not parse target 'not a url'"

DataSegment

Python wrapper for a data segment.

Represents a segment of trace data with metadata about its time range and producer.

DataType

__eq__(other)

Equal to another member and to the wire string, like enum.StrEnum.

ExitInfo

Exit information from a terminated extension process.

Both fields are optional because some platforms only report one (POSIX signal vs. exit code).

Example: >>> entry = agent.extensions.list()[0] >>> entry.last_exit.code if entry.last_exit else None 0

code instance-attribute

Process exit code; None when the platform reported a signal instead.

signal instance-attribute

Number of the signal that killed the process; None when it exited on its own.

ExportProducerResult

One producer's outcome from Agent.export(...) / Trace.export(...), keyed by producer address in the parent ExportResult.results dict.

Example: >>> result = agent.export("bms.trz", start="-1m", paths=["can/Battery.*"]) >>> for producer, r in result.results.items(): ... if not r.ok: ... print(producer, r.error)

error instance-attribute

Server-reported error message; None on success.

ok instance-attribute

True when this producer's shard was written.

producer instance-attribute

Address of the producer this shard came from.

ExportResult

Result of Agent.export(...) / Trace.export(...).

Bundles the on-disk path the agent (or local tmp-rename strategy) wrote to with a results dict keyed by producer address. Each entry is an ExportProducerResult carrying that producer's per-shard success bit and any error message.

ok is True only when every producer shard succeeded — partial failures surface as ok=False with per-producer detail in results rather than raising. This lets callers branch on partial-success outcomes without a try/except.

Example: >>> result = agent.export("bms.trz", start="-1m", paths=["can/Battery.*"]) >>> result.ok True

format instance-attribute

The trace format the agent wrote ("trz2" / "legacy"), None when it did not report one.

ok instance-attribute

True only when every producer's shard succeeded.

path instance-attribute

Path of the trace file the export wrote.

results instance-attribute

Per-producer results keyed by producer address. Builds the dict directly from &self.results — cloning the inner HashMap first would double the allocation work for no benefit.

open()

Open the file this export wrote as a Trace, through the same agent.

Example: with agent.export("run.trz", start="-5m").open() as trace: frame = trace.query("bus0/BMS.voltage")

ExtensionEntry

Catalog row returned by agent.extensions.list().

host_type and app_contribution_kind are exposed as their canonical lowercase wire strings (e.g. "agent", "web_app").

Example: >>> entry = agent.extensions.list()[0] >>> entry.id, entry.version, entry.state.value ('zeloscloud.zelos-extension-can', '0.1.10', 'running')

app_contribution_kind instance-attribute

e.g. "web_app"; None for non-app extensions.

author instance-attribute

Manifest author; None when the manifest omits it.

categories instance-attribute

Manifest categories, used for grouping in the app.

description instance-attribute

One-line summary from the manifest.

dev_mode instance-attribute

True when the extension is loaded from a local development checkout.

entry instance-attribute

Entry point the host runs; None for an extension with nothing to run.

homepage instance-attribute

Project homepage URL; None when the manifest omits it.

host_type instance-attribute

"agent" or "app".

icon_path instance-attribute

Absolute path to the extension's icon file; None when it ships none.

id instance-attribute

Fully qualified extension id, e.g. "zeloscloud.zelos-extension-can".

keywords instance-attribute

Manifest keywords, used for search.

last_exit instance-attribute

How the extension process last terminated; None when it has never run.

name instance-attribute

Display name from the extension's manifest.

pid instance-attribute

Process id while the extension runs; None when it is not running.

repository instance-attribute

Source repository URL; None when the manifest omits it.

state instance-attribute

Whether the extension is installed or currently running.

version instance-attribute

Installed version.

zelos_version instance-attribute

Zelos version range the extension declares it works with.

ExtensionError

Bases: AgentError

An extension lifecycle call failed.

Example: >>> try: ... agent.extensions.start('missing-extension') ... except ExtensionError: ... pass

ExtensionInfo

Rich info returned by agent.extensions.info(id).

Compared to ExtensionEntry, this carries install_path, readme_path, etc. but not author or last_exit (those are list-time concerns).

Example: >>> agent.extensions.info("zeloscloud.zelos-extension-can").install_path '/opt/zelos/extensions/zeloscloud.zelos-extension-can/0.1.10'

app_contribution_kind instance-attribute

What an app extension contributes, e.g. "web_app"; None for an agent extension.

categories instance-attribute

Manifest categories, used for grouping in the app.

description instance-attribute

One-line summary from the manifest.

dev_mode instance-attribute

True when the extension is loaded from a local development checkout.

entry instance-attribute

Entry point the host runs; None for an extension with nothing to run.

homepage instance-attribute

Project homepage URL; None when the manifest omits it.

host_type instance-attribute

Which host runs the extension: "agent" or "app".

icon_path instance-attribute

Absolute path to the extension's icon file; None when it ships none.

id instance-attribute

Fully qualified extension id, e.g. "zeloscloud.zelos-extension-can".

install_path instance-attribute

Directory the extension is installed in.

keywords instance-attribute

Manifest keywords, used for search.

name instance-attribute

Display name from the extension's manifest.

pid instance-attribute

Process id while the extension runs; None when it is not running.

readme_path instance-attribute

Absolute path to the extension's README; None when it ships none.

repository instance-attribute

Source repository URL; None when the manifest omits it.

state instance-attribute

Whether the extension is installed or currently running.

version instance-attribute

Installed version.

zelos_version instance-attribute

Zelos version range the extension declares it works with.

ExtensionStart

Result of agent.extensions.start(id, ...) / restart(id, ...).

Carries the caller-known id alongside the resolved pid and version the agent actually launched — no follow-up agent.extensions.list() call is needed to learn either.

Example: >>> started = agent.extensions.start("zeloscloud.zelos-extension-can") >>> started.pid, started.version (4821, '0.1.10')

id instance-attribute

Publisher-qualified extension id (e.g. "zeloscloud.zelos-extension-can").

pid instance-attribute

0 means "remote — unknown by design".

version instance-attribute

Resolved installed version that was spawned (e.g. "0.1.10").

ExtensionState

Runtime state of an extension.

Two-member enum-like class: ExtensionState.Installed and ExtensionState.Running. Wire form is the lowercase string ("installed" / "running"); access via state.value, or compare directly to the member or its wire string.

Example: >>> entry = agent.extensions.list()[0] >>> entry.state == ExtensionState.Running True >>> entry.state.value 'running'

Installed instance-attribute

Runtime state of an extension.

Two-member enum-like class: ExtensionState.Installed and ExtensionState.Running. Wire form is the lowercase string ("installed" / "running"); access via state.value, or compare directly to the member or its wire string.

Example: >>> entry = agent.extensions.list()[0] >>> entry.state == ExtensionState.Running True >>> entry.state.value 'running'

Running instance-attribute

Runtime state of an extension.

Two-member enum-like class: ExtensionState.Installed and ExtensionState.Running. Wire form is the lowercase string ("installed" / "running"); access via state.value, or compare directly to the member or its wire string.

Example: >>> entry = agent.extensions.list()[0] >>> entry.state == ExtensionState.Running True >>> entry.state.value 'running'

value instance-attribute

The state as its wire string: "installed" or "running".

Internal

Bases: RuntimeError

An SDK bug marker.

Deliberately outside AgentError so a broad except AgentError handler cannot swallow it — the agent answered, but the SDK could not make sense of the reply. If you hit this, it is a bug in the SDK, not a caller mistake.

Example: >>> try: ... agent.actions.execute('battery_test') ... except Internal: ... pass

LatestValue

One row from latest() / at().

Optional Arrow bytes plus a typed scalar; decoding uses catalog metadata when signal is known. Clone only bumps refcounts / copies small buffers (no GIL).

Example: >>> value = agent.latest("bus0/BMS_message/status.pack_current") >>> value.value 12.4

arrow_array instance-attribute

Lazily decode arrow_value into a 1-element pyarrow.Array.

arrow_value instance-attribute

Raw 1-row Arrow IPC bytes when the agent provided a typed payload, else None.

data_segment_id instance-attribute

Id of the data segment the sample came from; None when unknown.

display_value instance-attribute

Agent-provided display string. Always populated; the typed value getter is preferred for arithmetic / formatting.

label instance-attribute

signal.value_table[int(value)] lookup. Returns None when no value_table is present or the value isn't int-coercible.

message instance-attribute

Message the signal belongs to.

name instance-attribute

Checkable contract — alias for [Self::path].

path instance-attribute

Fully-qualified path: "{source}/{message}.{signal}".

producer instance-attribute

Address of the producer that reported the sample; None for trace data.

raw instance-attribute

Alias for display_value (matches pre-rewrite SDK).

signal instance-attribute

Resolved catalog Signal, when known. Populated by PyLatestRow::with_signal.

signal_name instance-attribute

Signal name within the message — the part after the final ..

source instance-attribute

Source name — the first segment of the signal's path.

time instance-attribute

time_ns as a UTC datetime, or None when the agent did not timestamp this row. chrono::DateTime::from_timestamp_nanos is total over i64 ns (every ns since the Unix epoch is in range).

time_ns instance-attribute

When the sample was recorded, epoch nanoseconds; None when the agent reported no timestamp.

trace_path instance-attribute

Trace file the sample came from; None for live data.

value instance-attribute

Typed Python value (bool / int / float / str / bytes / None) decoded once at construction. Falls back to the agent-provided display_value string when no typed payload was decodable.

__repr__()

Return repr(self).

formatted(digits=2)

Display string: enum label, numeric+unit, bool, hex bytes, em-dash for null, etc.

get()

Checkable contract — typed value, mirrors the value property.

Layout

A single saved layout, as returned by agent.layouts.list() / show() / create() / update().

Mirrors the console API LayoutData envelope. Fields are parsed once from the wire JSON; data is the arbitrary panel/config payload decoded to a Python dict on access. team_slug, user_id, email, and updated_by_email are optional and come back as None when the wire omits them.

Example: >>> layout = agent.layouts.list()[0] >>> layout.name, layout.is_personal ('My Dashboard', False)

created_at instance-attribute

When the layout was created, as an RFC 3339 string.

data instance-attribute

The layout's stored panel/config payload, decoded into a dict.

The wire data is validated to be a JSON object when the layout is parsed, so this getter never fails on a well-formed layout.

email instance-attribute

Email of the layout's owner; None when the wire omits it.

id instance-attribute

Layout id — the handle every other agent.layouts call takes.

is_personal instance-attribute

True for a private layout, False for one shared with the team.

name instance-attribute

Display name.

team_id instance-attribute

Id of the team the layout belongs to.

team_slug instance-attribute

URL slug of that team; None when the wire omits it.

updated_at instance-attribute

When the layout was last saved, as an RFC 3339 string.

updated_by_email instance-attribute

Email of whoever saved the layout last; None when the wire omits it.

user_id instance-attribute

Id of the owning user; None for a team layout.

LayoutVersion

A single entry in a layout's version history, as returned by agent.layouts.versions() / show_version().

Mirrors the console API LayoutVersionData envelope. Fields are parsed once from the wire JSON; data is the layout payload snapshot at this version — present on show_version(), None on versions() list entries (the list is metadata-only). label is the user-given version name set via set_version_label() (None when unlabeled), restored_from_version is set when the version was created by a restore(), and created_by_email comes back as None when the wire omits it.

Example: >>> history = agent.layouts.versions(layout.id) >>> history[0].version, history[0].label (3, None)

created_at instance-attribute

When the version was saved, as an RFC 3339 string.

created_by_email instance-attribute

Email of whoever saved the version; None when the wire omits it.

data instance-attribute

The layout payload snapshot at this version, decoded into a dict.

None for entries from versions() (the list is metadata-only); always populated on show_version().

id instance-attribute

Version id.

label instance-attribute

User-given version name; labeled versions are never pruned.

layout_id instance-attribute

Id of the layout this version belongs to.

name instance-attribute

The layout's display name when this version was saved.

restored_from_version instance-attribute

Version this one was restored from; None for an ordinary save.

version instance-attribute

Version number, counting up from 1.

NamedScalar

Bases: float

float carrying the aggregation's name and unit (Checkable contract).

str and repr round to four significant digits and keep the unit; float(scalar) is the exact value.

Attributes: name: How the value was produced, e.g. max(bus0/BMS.voltage). unit: The unit it inherits from the series, None when dimensionless.

Example: >>> series = frame["bus0/BMS_message/status.pack_current"] >>> peak = series.max() >>> peak.name, peak.unit ('max(bus0/BMS_message/status.pack_current)', 'A')

get()

The bare float, dropping the name and unit.

NoData

Bases: AgentError

The signal exists but produced no value inside the window.

Example: >>> try: ... agent.latest('bus0/BMS_message/status.pack_current', lookback=0.001) ... except NoData: ... pass

QueryRangeTooLarge

Bases: AgentError

The requested range exceeds what the agent will serve at once.

No agent build caps a query range today, so nothing raises this; it is reserved for the gRPC ResourceExhausted status, which a storage quota or a rate limit on a console-backed call can also carry.

Example: >>> try: ... agent.query(['bus0/BMS_message/status.pack_current'], start='-30d', end='now') ... except QueryRangeTooLarge: ... pass

QueryResult

Python wrapper for query results.

Contains the results of a trace data query, including column labels, the raw Arrow data as Python bytes, and the SQL query that was executed.

fields[0] is always time_s; every other column is labeled with its field path source/event.field, so table.column("source/event.field") indexes the decoded Arrow table directly.

sql carries the executed query on legacy .trz files and is EMPTY on TRZ2 traces — those are served by a DataFusion plan, which has no SQL text.

to_arrow()

Convert the Arrow data to a Python object that can be read by PyArrow.

Returns: bytes: Arrow IPC stream data

Examples: >>> import pyarrow as pa >>> result = reader.query(...) >>> arrow_bytes = result.to_arrow() >>> reader = pa.ipc.open_stream(arrow_bytes) >>> table = reader.read_all()

QueryType

Raw / M4 — selects the agent-side query strategy. Wire encoding mirrors the proto's QueryType enum (Raw=0, M4=1); the proto's MinMax=2 is internal-only and not exposed through the SDK surface.

Example: >>> QueryType.M4.value 'm4'

M4 instance-attribute

Raw / M4 — selects the agent-side query strategy. Wire encoding mirrors the proto's QueryType enum (Raw=0, M4=1); the proto's MinMax=2 is internal-only and not exposed through the SDK surface.

Example: >>> QueryType.M4.value 'm4'

Raw instance-attribute

Raw / M4 — selects the agent-side query strategy. Wire encoding mirrors the proto's QueryType enum (Raw=0, M4=1); the proto's MinMax=2 is internal-only and not exposed through the SDK surface.

Example: >>> QueryType.M4.value 'm4'

value instance-attribute

Wire-string view (matches the proto enum's lower-case form).

ReplayWindow

Replay-window response: I-frame snapshot + P-frame changes + window bounds. Rows use the same typed LatestValue shape as latest() and at().

Example: >>> window = agent.window(["bus0/BMS_message/status.pack_current"], start="-10s", duration=5) >>> [row.value for row in window.changes]

changes instance-attribute

duration instance-attribute

How long the window spans, as a timedelta.

end instance-attribute

Window end as a datetime.

end_ns instance-attribute

Alias for [Self::window_end_ns] (matches pre-rewrite SDK).

snapshot property

Opening values at start, one entry per signal — the same Snapshot at() returns.

start instance-attribute

Window start as a datetime.

start_ns instance-attribute

Alias for [Self::window_start_ns] (matches pre-rewrite SDK).

window_end_ns instance-attribute

Authoritative window end, epoch nanoseconds (clamped like the start).

window_start_ns instance-attribute

Authoritative window start (may differ from request if clamped).

Segment

Metadata for a single data segment.

Both bounds are optional because a segment that is still recording does not have a final end time. connection is set for live segments, trace_path for trace-file segments — they're mutually exclusive in practice.

Example: >>> segments = agent.segments() >>> segments[0].producer, segments[0].sources ('bus0', ['bus0/BMS_message', 'bus0/inverter_status'])

connection instance-attribute

Live-connection address (None for trace segments).

duration instance-attribute

None if the segment is still recording (no end bound).

end instance-attribute

Last sample time in the segment; None while it is still recording.

id instance-attribute

Data segment id.

producer instance-attribute

Producer that owns the segment.

sources instance-attribute

Source names that appear in the segment.

start instance-attribute

First sample time in the segment; None when the agent reported none.

trace_path instance-attribute

Trace file path (None for live segments).

SeriesByRun(runs, frame=None)

Bases: Mapping

One :class:SignalSeries per run of a :class:~zelos_sdk.TraceSet, keyed by run label.

A read-only mapping, so dict(by_run), by_run["baseline"] and by_run.items() all work.

Example: >>> by_run = runs.series(frame, "bus0/BMS_message/status.pack_current") >>> list(by_run) ['baseline', 'retest']

Signal

Selector + metadata for a single signal: source, message, name, data type, unit, and (for enum-like signals) the value table.

The wire field signal is exposed as the Pythonic name (signal.signal is awkward in Python); data_segment_id is exposed as a string.

Example: >>> sig = agent.signals()["bus0/BMS_message/status.pack_current"] >>> sig.unit 'A' >>> sig.path 'bus0/BMS_message/status.pack_current'

data_segment_id instance-attribute

The data segment this signal belongs to, "" when segmentation is not in play.

data_type instance-attribute

None on a path-only handle — no catalog entry has described it yet.

message instance-attribute

The signal's message ({source}/{message}.{name}).

name instance-attribute

The signal's own name — the trailing component of its path ({source}/{message}.{name}).

path instance-attribute

Fully-qualified path: "{source}/{message}.{name}".

producer instance-attribute

The producer this signal came from, None when the frame carries no producer metadata.

source instance-attribute

The signal's source ({source}/{message}.{name}).

trace_path instance-attribute

The trace file this signal was read from, None for a live signal.

unit instance-attribute

The signal's engineering unit (e.g. "V", "A"), None when unknown.

value_table instance-attribute

{raw value: label} for an enum-like signal, None otherwise.

__new__(source, message, name, data_type, data_segment_id=None, producer=None, trace_path=None, unit=None, value_table=None)

Create and return a new object. See help(type) for accurate signature.

from_path(path, data_type=None) classmethod

Construct a path-only Signal handle. No catalog roundtrip — the path is verified server-side at the next check / latest / query invocation.

Use this when the producer hasn't emitted yet (cold-start tests, fixture order races, extension consumers) or when you want a long-lived handle that re-resolves to the freshest segment on every call rather than binding to a specific one.

data_type is informational only — only the path crosses the wire (SignalOperand(path)); the agent uses its own resolved schema for evaluation. Left unset, data_type and unit read back as None: nothing has verified them yet.

Accepts bare paths ("src/foo.bar"), wildcard-segment paths ("*/src/foo.bar"), and fully-qualified segment paths ("{uuid}/src/foo.bar") — the last pins resolution to a specific segment.

SignalCatalog

Immutable signal catalog with fast search and exact-path lookup, returned by agent.signals().

Example: >>> catalog = agent.signals() >>> catalog["bus0/BMS_message/status.pack_current"].unit 'A'

paths instance-attribute

Every canonical path in catalog order.

warnings instance-attribute

Per-producer / per-trace warnings collected while building the catalog.

__contains__(key)

Return bool(key in self).

__repr__()

Return repr(self).

by_path(path)

Deprecated alias for catalog[path].

get(path, default=None)

Exact-path lookup that returns default instead of raising when the catalog has no such signal. Ambiguity still raises AmbiguousSignal.

match(pattern)

Filter the catalog by a glob over the full canonical path.

* and ? span / and ., so "bus0/*", "*.cell_*" and "bus0/BMS_message/*.cell_0" all work. The same grammar every paths= argument uses. No match returns an empty catalog.

search(query)

Filter the catalog by case-insensitive substring match.

Each signal is checked across five fields: full path, source, message, signal name, and unit. Matching is case-insensitive; the empty query returns the catalog unchanged.

Args: query: Substring to search for.

Returns: SignalCatalog: A new catalog containing only the matched signals; warnings are inherited from the parent.

to_list()

Deprecated alias for list(catalog).

to_pandas()

Render the catalog as a pandas DataFrame (one row per signal).

Columns: path, source, message, name, data_type, unit, producer, trace_path, data_segment_id. Raises ImportError when pandas is not installed.

SignalFrame

Result of a tabular query.

Arrow IPC bytes cross verbatim and are decoded by to_arrow(). arrow_ipc_data is held so Clone is cheap on the data path; normalized_table_cache resets to empty on clone (it's a perf aid, not identity, and sliced frames need a fresh table anyway).

Example: >>> frame = agent.query(["bus0/BMS_message/status.pack_current"], start="-5m") >>> frame.to_pandas()["bus0/BMS_message/status.pack_current"].mean()

arrow_ipc_data instance-attribute

Decode with pyarrow.ipc.open_stream. A fresh bytes is allocated per access; users typically call to_arrow() once per response so the cost is negligible.

column_names instance-attribute

Every column label except the time column — the same labels as keys() and the signal columns of to_pandas().

columns instance-attribute

Wire metadata for the data columns, in Arrow order. The time column is not a signal and has no metadata worth reporting, so it is not here.

downsampled instance-attribute

True when the agent ran an M4 / downsampled query rather than raw.

meta instance-attribute

Alias for columns.

query_duration_s instance-attribute

How long the agent took to answer the query, in seconds.

requested_range instance-attribute

The range the caller ASKED for. time_range reports the data's own extent; this is what the request said.

signals instance-attribute

Resolved catalog signals in column order (excludes the time column).

time_origin instance-attribute

Where the frame's time axis starts: "epoch" for wall-clock time, "run_start" for a TraceSet frame aligned at each run's own start, None for a frame built locally with no wire metadata.

time_range instance-attribute

The extent of the DATA: (first, last) timestamp in the frame, None when it has no rows. The window the caller asked for is requested_range.

truncated instance-attribute

True when max_rows > 0 and the result hit the cap.

warnings instance-attribute

Per-producer / per-trace warnings collected during the query.

__contains__(name)

Return bool(key in self).

__getitem__(key)

Return self[key].

__iter__()

Implement iter(self).

__repr__()

Return repr(self).

between(start=None, end=None)

Rows inside [start, end], both anchored on the frame's own extent: "+10s" counts from the first sample, "-10s" back from the last, and "start" / "end" are the bounds themselves. An omitted bound is the matching end of the frame.

Example: frame.between("+30s", "+40s") # the second half-minute of the run

describe()

pandas.DataFrame.describe() over the data columns, with each column's unit as the first row.

Example: frame.describe() # unit / count / mean / std / min / quartiles / max

dropna(how='any')

Drop rows with nulls: how="any" (default) keeps only rows where every column has a sample, how="all" drops only the empty rows.

Example: frame.dropna() # rows both messages landed on

ffill()

Sample-and-hold every column: each null takes the last value before it.

One row per timestamp means a two-message frame is mostly nulls; this is how you get a dense frame to do arithmetic on.

Example: power = frame.ffill()["bus0/BMS.voltage"] * frame.ffill()["bus0/BMS.current"]

from_arrow_ipc(data, columns=[], signals=[]) staticmethod

staticmethod(function) -> method

Convert a function to be a static method.

A static method does not receive an implicit first argument. To declare a static method, use this idiom:

 class C:
     @staticmethod
     def f(arg1, arg2, argN):
         ...

It can be called either on the class (e.g. C.f()) or on an instance (e.g. C().f()). Both the class and the instance are ignored, and neither is passed implicitly as the first argument to the method.

Static methods in Python are similar to those found in Java or C++. For a more advanced concept, see the classmethod builtin.

head(n=5)

First n rows as a new SignalFrame (default 5).

items()

(label, series) tuples for every non-time column.

keys()

Column labels excluding the leading time column. Pandas-style iteration, and the same labels to_pandas() puts on its columns.

plot(**kwargs)

Render with zelos_sdk.agent.plot.plot_frame. Keyword arguments (title=, height=) forward verbatim.

rename(mapping)

Relabel columns from a {old: new} mapping or a callable over the current labels. Unknown keys keep their label; two columns landing on one label raise ValueError.

Example: frame.rename({"bus0/BMS_message/status.pack_current": "current"})

resample(every, how='mean')

Aggregate onto a fixed time grid: one row per every, combined with how ("mean", "min", "max", "first", "last").

This is the analysis verb pandas users reach for; downsample= on query() is a chart thinning by bucket count, not a period.

Example: frame.resample("1s").to_pandas()

series(key)

Project one column as a Python SignalSeries (pandas/pyarrow ergonomics).

key accepts a str (a column label, a clean catalog path, or the raw wire column name) or a Signal from the frame's catalog. Ties across producers / segments raise [errors::AmbiguousSignal] with a hint to pass a Signal, except on a TraceSet frame, where one path really does name one column per run: that returns a SeriesByRun mapping.

short_names()

Label each column by its signal name alone, falling back to message.name where a bare name repeats in this frame. A run frame shortens within each run and keeps the run: baseline · cell_0.

Example: frame.short_names().to_pandas() # columns "cell_0", "pack_current"

tail(n=5)

Last n rows as a new SignalFrame (default 5).

to_arrow()

The frame's rows as a pyarrow.Table, decoded and normalized.

The returned object is a pyarrow.Table with a canonical time: timestamp[ns, tz=UTC] column and signal columns labeled with the catalog path source/message.signal. The name mirrors SignalSeries.to_arrow() (a column accessor) so notebook users have one symbol to reach for at both the frame and column level.

Cached after the first call; every caller gets a handle to the same immutable Table.

to_pandas(*, index=True)

Notebook one-liner: frame.to_pandas(), indexed by a UTC DatetimeIndex named time. A run-aligned TraceSet frame (time_origin == "run_start") is indexed by a TimedeltaIndex of offsets instead. Pass index=False for the older shape, where time is an ordinary column.

The index is what df.resample("1s"), df.loc["...":] and df.plot() all read, and it matches SignalSeries.to_pandas(). Columns are labeled the way the catalog labels signals — source/message.signal — taking a producer:: prefix only when two columns in one result share a path.

Example: frame.to_pandas().resample("1s").mean()

Raises ImportError with an actionable hint if pandas is not installed; we deliberately do not list it in the SDK's hard requirements so notebook users opt-in via zelos-sdk[notebook].

values()

One SignalSeries per non-time column (parallel to keys()).

SignalNotFound

Bases: AgentError, KeyError

No signal matched the requested path or pattern.

Carries a computed suggestions: list[str] of the closest catalog paths, so a caller can surface a "did you mean" hint.

Example: >>> try: ... agent.latest('bus0/BMS_message/status.pack_currnt') ... except SignalNotFound as exc: ... exc.suggestions ['bus0/BMS_message/status.pack_current']

SignalSeries(name, values, *, signal=None, unit=None, time=None)

One Arrow column with pyarrow/pandas helpers and a unit-aware math surface.

Unit composes through * and /; + and - require matching units. Scalars, Arrow arrays and plain sequences are unit-blind operands; pandas and numpy objects are refused. Values are always a pa.ChunkedArray; time is the frame's shared time column, so two series from one frame align by identity.

Attributes: name: The signal's path, a caller's rename(), or None. values: The samples as a pa.ChunkedArray; a null is a missing sample. signal: The catalog Signal a frame column came from; None once the series is derived. time: The shared time axis, None on a series built without one.

Example: >>> frame = agent.query(["bus0/BMS_message/status.pack_current"], start="-5m") >>> series = frame["bus0/BMS_message/status.pack_current"] >>> series.mean().unit 'A'

legend property

Display label as "name (unit)", falling back to whichever exists.

null_count property

Number of null samples in the series.

unit property

The series' unit as a display string, None when unknown.

abs()

Magnitude of every sample, unit kept.

clip(lower=None, upper=None)

Clamp values to [lower, upper]. At least one bound is required.

count()

Number of non-null values, as an int — not a NamedScalar.

cumsum()

Running total over the samples, unit kept.

derivative()

Numerical derivative over time (centered, np.gradient).

derive(values=None, *, label=None, unit=None)

Deprecated alias of :meth:from_arrow on this series' time axis.

diff()

Delta to the previous sample; the first value is null.

dropna()

Drop null samples, keeping time aligned with the values that remain.

ffill()

from_arrow(values, *, name=None, unit=None, time=None) classmethod

Series from Arrow-compatible values, checked against time.

from_pandas(series, *, unit=None, name=None) classmethod

Series from pandas; a DatetimeIndex becomes the UTC time axis.

get()

Deprecated alias of :meth:to_pandas.

head(n=5)

The first n samples, time kept alongside.

integrate()

Cumulative trapezoidal integration over time; first sample is 0.

isna()

Boolean series, True wherever the sample is missing.

max()

Largest non-null sample; nan when there is none.

mean()

Arithmetic mean over the non-null samples.

median()

Exact median of the non-null samples.

min()

Smallest non-null sample; nan when there is none.

plot(**kwargs)

Render with :func:zelos_sdk.agent.plot.plot_series. Keyword arguments (title=, height=) forward verbatim.

rename(name)

Same samples under a new name — what a derived series wants.

std(ddof=1)

Sample standard deviation (ddof=1, as in pandas).

sum()

Total of the non-null samples.

tail(n=5)

The last n samples, time kept alongside.

time_arrow()

Deprecated alias of the :attr:time attribute.

to_arrow()

The samples as a pa.ChunkedArray, without copying.

to_numpy()

float64 numpy array; nulls become NaN. The fast path for bulk math.

to_pandas()

pandas.Series (UTC DatetimeIndex when timed, unit in attrs).

where(mask)

Keep values where mask is True; the rest become nulls.

with_unit(unit)

Assert a unit (no conversion).

Snapshot

The values of several signals as of one moment: what latest(paths), at(paths, time) and each watch() tick hand back.

A read-only Mapping[str, LatestValue] keyed by clean path, so every spelling that worked against the plain dict still works — snapshot[path], in, len, .get, .keys(), .values(), .items(), iteration, and dict(snapshot). time is the newest sample time in the set.

Example: >>> snapshot = agent.latest(["bus0/BMS_message/cells.cell_0", "bus0/BMS_message/status.pack_current"]) >>> snapshot["bus0/BMS_message/status.pack_current"].value 12.4

paths instance-attribute

The signal paths in the snapshot — the same list as keys().

time instance-attribute

The newest sample time in the snapshot, None when it is empty or no row carries a timestamp.

__repr__()

Return repr(self).

SortOrder

Asc / Desc — drives row ordering for Agent.query() / Trace.query(). Wire encoding mirrors the proto's SortOrder enum (Asc=0, Desc=1).

Example: >>> agent.query(["bus0/BMS_message/status.pack_current"], start="-5m", sort_order=SortOrder.Desc) >>> SortOrder.Asc.value 'asc'

Asc instance-attribute

Asc / Desc — drives row ordering for Agent.query() / Trace.query(). Wire encoding mirrors the proto's SortOrder enum (Asc=0, Desc=1).

Example: >>> agent.query(["bus0/BMS_message/status.pack_current"], start="-5m", sort_order=SortOrder.Desc) >>> SortOrder.Asc.value 'asc'

Desc instance-attribute

Asc / Desc — drives row ordering for Agent.query() / Trace.query(). Wire encoding mirrors the proto's SortOrder enum (Asc=0, Desc=1).

Example: >>> agent.query(["bus0/BMS_message/status.pack_current"], start="-5m", sort_order=SortOrder.Desc) >>> SortOrder.Asc.value 'asc'

value instance-attribute

Wire-string view (matches the proto enum's lower-case form).

TimeMode

Relative / Absolute — selects how trace-side query bounds are interpreted. Wire encoding mirrors MultiTraceTimeMode (Relative=0, Absolute=1).

Example: >>> TimeMode.Relative.value 'relative'

Absolute instance-attribute

Relative / Absolute — selects how trace-side query bounds are interpreted. Wire encoding mirrors MultiTraceTimeMode (Relative=0, Absolute=1).

Example: >>> TimeMode.Relative.value 'relative'

Relative instance-attribute

Relative / Absolute — selects how trace-side query bounds are interpreted. Wire encoding mirrors MultiTraceTimeMode (Relative=0, Absolute=1).

Example: >>> TimeMode.Relative.value 'relative'

value instance-attribute

Wire-string view (matches the proto enum's lower-case form).

TimeRange

Inclusive datetime interval [start, end].

Example: >>> trace = agent.trace("run.trz") >>> trace.time_range.start, trace.time_range.end (datetime.datetime(2026, 8, 1, 12, 0, tzinfo=datetime.timezone.utc), datetime.datetime(2026, 8, 1, 12, 5, tzinfo=datetime.timezone.utc))

duration instance-attribute

end - start, as a timedelta.

end instance-attribute

Last instant in the interval.

end_ns instance-attribute

end as epoch nanoseconds, the precision a datetime cannot hold.

start instance-attribute

First instant in the interval.

start_ns instance-attribute

Epoch nanoseconds. Prefer this over .start / .end when precision matters: a Python datetime only holds microseconds.

TimeRangeMulti

Multi-trace time range response (overlay / absolute mode): the union extent across every run, plus each run's own TraceTiming.

Example: >>> timing = TraceTiming("a.trz", start=trace.time_range.start, duration=trace.time_range.duration) >>> multi = TimeRangeMulti(trace.time_range.start, trace.time_range.end, max_duration=trace.time_range.duration, traces=[timing]) >>> multi.max_duration, len(multi.traces) (datetime.timedelta(seconds=300), 1)

end instance-attribute

Latest end across every run.

max_duration instance-attribute

The longest run's duration, as a timedelta.

max_duration_s instance-attribute

The longest run's duration, in seconds.

start instance-attribute

Earliest start across every run.

traces instance-attribute

One entry per run, in the order the set was opened.

Trace

Handle to a trace file opened through Agent.trace.

Owns an Arc<AgentTransport> cloned from its parent Agent, so the trace shares the agent's connection lifecycle: calling agent.close() invalidates this handle too, and any subsequent method on it raises AgentCancelled.

Time arguments anchor on this file, not on wall-clock now: start="-30s" is the last 30 seconds OF THE FILE. The frame's time axis is always epoch.

Example: >>> trace = agent.trace("run.trz") >>> df = trace.query(["bus0/BMS_message/status.pack_current"], start="-30s").to_pandas() >>> trace.close()

check instance-attribute

Typed predicate Check API bound to this trace file.

Returns a TraceChecks proxy whose default is temporal="always" over the trace's full [start_ns, end_ns] — matches the natural reading of trace.check.that('voltage', '<', 42) ("is voltage < 42 across this recording?"). Override with last= (anchored at the trace's end_ns, not wall-clock), start_ns=+end_ns=, or temporal="latest".

path instance-attribute

The trace file's path on disk.

time_range instance-attribute

The trace file's own time extent.

__enter__()

Enable with agent.trace(path) as trace: — returns self.

__exit__(_exc_type=None, _exc_value=None, _traceback=None)

Close the trace handle on context exit. Mirrors the try/finally trace.close() pattern; idempotent on multiple closes.

at(paths, time=None, *, min_time=None)

Per-signal value at (or before) a moment inside the trace.

time and min_time use the same source-anchored grammar as query and default to the file's end. One literal path returns a LatestValue; a wildcard or a sequence returns a Snapshot keyed by signal path. Raises SignalNotFound on catalog miss; ValueError if min_time > time.

close()

Release the agent's hold on this trace file.

A hint, not a shutdown: it tells the agent it may drop the catalog and indices it loaded, and the agent reopens the file on demand the next time this handle is used. Idempotent; the handle stays usable, and only a file that has since left the disk raises TraceNotFound. The per-handle catalog cache is kept — a trace file cannot change.

export(path, *, start=None, end=None, overwrite=False, format=None, relative_start=None, relative_end=None)

Export this trace (or a slice of it) to a new .trz file.

start / end use the same source-anchored grammar as query; omitting both exports the full trace. format defaults to the agent's release default (legacy DuckDB), except over a TRZ2 input, which re-exports as TRZ2.

Args: path: Destination path for the new .trz file. start: Slice start; defaults to the file's start. end: Slice end; defaults to the file's end. overwrite: Replace path if it exists. format: "trz2", "legacy", or None for the agent's default.

Returns: ExportResult: On-disk path plus per-producer success bits.

info()

Aggregate trace inspection — one round-trip for file size, time range, segments, per-table row counts, and total data rows. Same shape as zelos trace info.

latest(paths)

The file's last value for each path — at(paths, "end").

One literal path returns a LatestValue, anything else a Snapshot.

query(paths, *, start=None, end=None, downsample=None, max_rows=0, sort=None, sort_order=None, relative_start=None, relative_end=None)

Time-bounded query against this trace file.

Resolves paths against the trace's catalog (trace mode is authoritative — literal-path misses raise SignalNotFound).

start / end anchor on THIS FILE: "-30s" is 30 s before its end, "+30s" is 30 s after its start, "start" / "end" / "now" are its bounds, and a datetime, ISO 8601 string or epoch-ns int is taken literally. An omitted bound is the matching end of the file. A window entirely outside the file raises ValueError naming the file's extent; one that only overhangs is clamped.

Args: paths: A str, Signal, or sequence thereof. start: Window start; defaults to the file's start. end: Window end; defaults to the file's end. downsample: Optional M4 bucket count — same semantics as Agent.query(downsample=N). The agent returns at most four points per bucket. Mutually exclusive with max_rows / sort. max_rows: Cap on returned rows; 0 (default) means no cap. sort: "asc" (default) or "desc".

Returns: SignalFrame: Arrow-backed frame on an epoch time axis.

Raises: SignalNotFound: catalog miss for a literal path. ValueError: an unparsable or out-of-range window.

segments()

List the trace file's data segments.

A segment is a contiguous chunk inside the trace bounded by a TraceSegmentStart / TraceSegmentEnd pair. Per-trace read warnings are emitted as RuntimeWarning, matching Agent.segments; use segments_with_warnings() to receive them as a list instead.

segments_with_warnings()

Same as segments() but returns (segments, warnings).

signals()

Fetch the trace's signal catalog.

Cached per-handle: the first call hits the wire, every subsequent call returns the same SignalCatalog (trace files are immutable — the catalog cannot drift). Supports search(), by_path(), and slicing — same shape as the live agent catalog.

Returns: SignalCatalog: All signals present in this trace.

window(paths, *, start, duration, display_fps=0)

Replay snapshot + change-stream over a window inside this trace.

Same semantics as Agent.window() scoped to this trace. start uses the source-anchored grammar; duration accepts int/float seconds, a datetime.timedelta, or a string like "30s". display_fps=0 means "no thinning". Raises ValueError on duration <= 0 or display_fps < 0.

TraceEventFieldMetadata

Metadata describing a field in a trace event schema.

This class defines the structure of a field within an event schema, including its name, data type, and optional unit of measurement.

Args: name (str): The field name. data_type (DataType): The data type for the field. unit (Optional[str]): Optional unit of measurement.

Examples: >>> # Define a field for HTTP status code >>> status_field = TraceEventFieldMetadata("status_code", DataType.Int32) >>> >>> # Define a field with a unit of measurement >>> duration_field = TraceEventFieldMetadata( ... "duration_ms", DataType.Float64, "milliseconds")

TraceMetadata

Python wrapper for trace metadata.

Contains information about a complete trace including its time range, producer, and associated data segments.

TraceNamespace

A namespace that manages and organizes TraceSources.

TraceNamespace provides a centralized registry for TraceSources with an isolated router. Each namespace has its own router, allowing complete isolation between different namespaces for testing or multi-tenant scenarios.

Examples: >>> # Create an isolated namespace >>> ns = TraceNamespace("my_app") >>> source = TraceSource("service", namespace=ns) >>> with TraceWriter("data.trz", namespace=ns) as writer: ... source.log("event", value=42)

__del__()

Python destructor — only shuts down non-global namespaces. The global namespace is cleaned up via atexit.

__repr__()

String representation of the namespace.

drain()

Flush all sources, signal the router to drain, and block until every queued event has been delivered to subscribed sinks.

Call this before tearing down a writer (or at the end of a transcode) when you need at-least-once delivery — __del__ / atexit drain are best-effort and don't block on the caller's thread. Releases the GIL while waiting.

Idempotent and safe to call multiple times. Must NOT be called from inside the tokio runtime (e.g. from an async-Python task) because it blocks on the router's drain ack.

source_count()

Get the number of registered sources.

Returns: int: Number of registered sources.

TraceNotFound

Bases: AgentError

The agent has no trace file at that path.

Example: >>> try: ... agent.trace('missing.trz') ... except TraceNotFound: ... pass

TracePublishClient

Client for publishing trace events to a Zelos Cloud service.

This client manages the connection to a remote trace service and provides the communication channel needed by TraceSource objects to transmit events. It handles batching, retries, and connection management.

Examples: >>> # Create a client with default settings >>> client = TracePublishClient() >>> >>> # Create a client with custom configuration >>> config = TracePublishClientConfig(url="grpc://localhost:2300") >>> client = TracePublishClient(config)

__del__()

Python destructor - automatically shutdown background tasks

__repr__()

String representation of the client.

shutdown()

Shutdown the client and all background tasks.

This will cancel the background tasks and wait for them to complete. After calling this method, the client should not be used further.

TracePublishClientConfig

Configuration for the PyTracePublishClient.

This class allows customizing the behavior of trace publishing, including: - Batch size: Number of events to batch before sending - Batch timeout: Maximum time to wait before sending a partial batch

Examples: >>> config = TracePublishClientConfig( ... batch_size=500, ... batch_timeout_ms=2000, ... ) >>> client = TracePublishClient(config)

__repr__()

String representation of the configuration.

set_batch_size(size)

Set the configured batch size.

set_batch_timeout_ms(ms)

Set the configured batch timeout in milliseconds.

TraceReadEvent

Python wrapper for a trace read event.

Represents an event containing multiple fields.

TraceReadEventField

Python wrapper for a trace read event field.

Represents a single field within an event.

TraceReadSource

Python wrapper for a trace read source.

Represents a source (e.g., "can") containing multiple events.

TraceReader

Python wrapper for the TraceReader.

This reader provides read-only access to trace files, allowing you to query metadata and retrieve trace data programmatically. It supports listing data segments, querying time ranges, and retrieving raw or downsampled data.

The reader uses context management and should be used with a with statement to ensure proper resource cleanup.

Complete End-to-End Workflow

This example demonstrates opening a trace file, discovering available fields, and querying specific data:

import zelos_sdk
import pyarrow as pa

# Open trace file for reading
with zelos_sdk.TraceReader("recording.trz") as reader:
    # Discover available segments
    segments = reader.list_data_segments()
    assert len(segments) > 0

    # Discover available fields hierarchically
    sources = reader.list_fields()
    assert len(sources) > 0

    # Navigate hierarchy: source → event → field
    can_source = next(s for s in sources if s.name == "can")
    msg_event = next(e for e in can_source.events if e.name == "VehicleSpeed")
    speed_field = next(f for f in msg_event.fields if f.name == "speed")

    # Query discovered field
    time_range = reader.time_range()
    result = reader.query(
        data_segment_ids=[s.id for s in segments],
        fields=[speed_field.path],  # "*/can/VehicleSpeed.speed"
        start=time_range.start,
        end=time_range.end,
    )

    # Verify data received. Columns are labeled with the field path,
    # so the queried selector minus its `*/` prefix indexes the table.
    arrow_reader = pa.ipc.open_stream(result.to_arrow())
    table = arrow_reader.read_all()
    assert table.num_rows > 0
    assert table.column("can/VehicleSpeed.speed")

__repr__()

String representation of the reader.

close()

Close the trace reader.

This method closes the trace file and releases resources. It's automatically called when exiting the context manager.

Returns: None

get_value_table(data_segment_id, field_path)

Get the value table (enum mapping) for a specific field.

Args: data_segment_id (str): Data segment ID to query. field_path (str): Field path in format "source/event.field" (without the "*/" prefix).

Returns: dict: Mapping of integer keys to string values, or None if no value table exists.

Raises: RuntimeError: If the reader is not open or query fails.

Examples: >>> with TraceReader("my_trace.trz") as reader: ... segments = reader.list_data_segments() ... # Get enum mapping for a status field ... status_map = reader.get_value_table( ... segments[0].id, ... "controller/state.status" ... ) ... if status_map: ... print(status_map) # {0: "IDLE", 1: "RUNNING", 2: "ERROR"}

list_data_segments()

List all data segments in the trace.

Returns: List[DataSegment]: List of data segment metadata.

Raises: RuntimeError: If the reader is not open or query fails.

Examples: >>> with TraceReader("my_trace.trz") as reader: ... segments = reader.list_data_segments() ... for seg in segments: ... print(f"Segment {seg.id}: {seg.producer}")

list_data_segments_in_time_range(start, end)

List data segments within a specific time range.

Args: start (str): Start of time range (inclusive), ISO 8601 / RFC 3339. end (str): End of time range (inclusive), ISO 8601 / RFC 3339.

Returns: List[DataSegment]: List of data segments overlapping the time range.

Raises: RuntimeError: If the reader is not open, a timestamp does not parse, or the query fails.

Examples: >>> with TraceReader("my_trace.trz") as reader: ... time_range = reader.time_range() ... segments = reader.list_data_segments_in_time_range( ... time_range.start, time_range.end ... )

list_fields(data_segment_id=None)

List all fields in the trace organized by source and event.

This method discovers all available fields in the trace by querying the database schema and organizing them hierarchically by source and event.

Args: data_segment_id (str, optional): Specific data segment ID to query. If None, queries all segments.

Returns: List[TraceReadSource]: List of sources, each containing events and fields.

Raises: RuntimeError: If the reader is not open or query fails.

Example: Discover and Query Fields
with TraceReader("recording.trz") as reader:
    # Discover all available fields
    sources = reader.list_fields()
    assert len(sources) > 0

    # Navigate the hierarchy
    for source in sources:
        for event in source.events:
            for field in event.fields:
                # field.path is the field path for queries (e.g., "*/can/VehicleSpeed.speed")
                assert field.path.startswith("*/") and "." in field.path

list_traces()

List all traces in the trace file.

Returns: List[TraceMetadata]: List of trace metadata.

Raises: RuntimeError: If the reader is not open or query fails. ValueError: If the trace has no such registry. Use time_range() and list_data_segments() instead.

Examples: >>> with TraceReader("my_trace.trz") as reader: ... traces = reader.list_traces() ... for trace in traces: ... print(f"Trace {trace.name}: {trace.start_date} to {trace.end_date}")

open()

Open the trace file for reading.

This method initializes the reader and opens the trace file in read-only mode. It's automatically called when entering the context manager (with statement).

Returns: None

Raises: ValueError: If the path is not a trace this build can read — unrecognized content, a container written by a newer Zelos, or a directory that is not a sealed trace. RuntimeError: If the trace file cannot be opened (including a missing file and any other I/O failure).

query(data_segment_ids, fields, start, end)

Query data for specified fields within a time range.

This returns raw, unsampled data for the requested fields.

Columns are labeled with the field path source/event.field, so the selector you queried indexes the result directly. Two producers emitting the same path into one file take a producer:: prefix.

Args: data_segment_ids (List[str]): List of data segment IDs to query. An empty list selects nothing. fields (List[str]): List of field selectors (e.g., "*/bus0/msg1.sig1", or "/bus0/msg1.sig1" for one specific segment). start (str): Start of time range (inclusive), ISO 8601 / RFC 3339. end (str): End of time range (inclusive), ISO 8601 / RFC 3339.

Returns: QueryResult: Query results with Arrow data. fields reflects the REQUEST: a requested field is listed even when it has no row in the window. A request that resolves to nothing is the empty result (fields == ["time_s"], no data), not an error. sql is empty on TRZ2 traces — a DataFusion plan has no SQL text — and carries the executed query on legacy .trz files.

Raises: RuntimeError: If the reader is not open, an argument does not parse, or the query fails.

Examples: >>> with TraceReader("my_trace.trz") as reader: ... segments = reader.list_data_segments() ... time_range = reader.time_range() ... result = reader.query( ... data_segment_ids=[s.id for s in segments], ... fields=["/bus0/msg1.sig1", "/bus0/msg2.sig3"], ... start=time_range.start, ... end=time_range.end, ... ) ... # Convert to PyArrow table ... import pyarrow as pa ... arrow_reader = pa.ipc.open_stream(result.to_arrow()) ... table = arrow_reader.read_all() ... sig1 = table.column("bus0/msg1.sig1") ... df = table.to_pandas()

time_range()

Get the time range covered by the trace.

Returns: TimeRange: Object containing start and end timestamps.

Raises: RuntimeError: If the reader is not open or query fails.

Examples: >>> with TraceReader("my_trace.trz") as reader: ... time_range = reader.time_range() ... print(f"Start: {time_range.start}") ... print(f"End: {time_range.end}")

TraceSender

Communication channel for sending trace events.

This class is typically obtained from a TracePublishClient and passed to a TraceSource during creation. It handles the underlying message transport.

Note: Users generally don't need to interact with this class directly; it's used internally to connect TraceSource to TracePublishClient.

TraceSet

Handle to a set of trace files opened together for multi-trace queries.

Returned by Agent.traces([...]). Like Trace, holds an Arc<AgentTransport> cloned from its parent Agent and shares the agent's connection lifecycle.

Each member file is a RUN, named by runs (the file stems, deduplicated). len(traces), traces[label], and iteration over (label, Trace) address them individually.

Bounds given as source-anchored offsets (or omitted) align the runs at t = 0 and the frame's axis is seconds from each run's start; bounds given as absolute instants apply once and the axis is epoch.

Example: >>> ts = agent.traces(["a.trz", "b.trz"]) >>> df = ts.query(["bus1/inverter_status.rpm"], end="+30s").to_pandas() >>> ts.close()

check instance-attribute

Typed predicate Check API bound to this set. Each assertion returns a dict[run label, CheckResult].

max_duration instance-attribute

The longest run's duration — what time_range.max_duration returned when time_range was a TimeRangeMulti.

paths instance-attribute

The trace files' paths on disk, in the order the set was opened.

runs instance-attribute

The run labels, in the order the set was opened.

time_range instance-attribute

The union extent across every run.

traces instance-attribute

Per-run timing entries, one per run — what time_range.traces returned when time_range was a TimeRangeMulti.

__enter__()

Enable with agent.traces(paths) as traces: — returns self.

__exit__(_exc_type=None, _exc_value=None, _traceback=None)

Close every trace in the set on context exit. Mirrors the try/finally traces.close() pattern; idempotent on multiple closes.

at(paths, time=None, *, min_time=None)

Per-signal value at (or before) a moment, across all member runs.

time and min_time resolve against the set's union extent and default to its end. One literal path returns a LatestValue; a wildcard or a sequence returns a Snapshot keyed by signal path.

close()

Release the agent's hold on every trace in the set.

Idempotent. Same semantics as Trace.close() but issued for every member trace in one RPC.

export(path, *, start=None, end=None, overwrite=False, format=None, time_mode=None, relative_start=None, relative_end=None)

Export the union of all member runs to a single new .trz file.

Same bounds contract as TraceSet.query(): offset bounds slice each run from its own start, absolute bounds slice once on wall-clock.

latest(paths)

The last value for each path across the set — at(paths, "end").

One literal path returns a LatestValue, anything else a Snapshot.

query(paths, *, start=None, end=None, downsample=None, max_rows=0, sort=None, sort_order=None, time_mode=None, relative_start=None, relative_end=None)

Time-bounded query joining results across every member run.

start / end take the same grammar as Trace.query. Offset bounds ("+30s", "-30s", "start", "end") — or no bounds at all — align the runs at t = 0 and the frame's axis is seconds from each run's own start; a datetime or ISO instant selects the epoch axis instead.

Args: paths: A str, Signal, or sequence thereof. Wildcards expand against the union catalog. start: Window start; defaults to each run's start. end: Window end; defaults to the longest run's end. downsample: Optional M4 bucket count; mutually exclusive with max_rows / sort. max_rows: Cap on returned rows; 0 (default) means no cap. sort: "asc" (default) or "desc".

Returns: SignalFrame: Arrow-backed frame; one column per (run, signal).

segments()

List per-trace segments across every member trace.

Segment entries carry their owning trace_path so callers can group them. Read warnings surface as RuntimeWarning; use segments_with_warnings() to receive them as a list instead.

segments_with_warnings()

Same as segments() but returns (segments, warnings).

series(frame, path)

One SignalSeries per run for path, keyed by run label.

A set frame lays out one column per (run, signal); this is the run dimension a caller can name, resolved through each column's trace_path. frame["path"] cannot do it on its own — the frame does not know the set's labels.

signals()

Fetch the union signal catalog across every member trace.

Cached per handle. Each signal carries its source file in signal.trace_path, so a later query routes to the right one.

Returns: SignalCatalog: Union of all member-trace catalogs.

window(paths, *, start, duration, display_fps=0)

Replay snapshot + change-stream over a window across all member runs.

start resolves against the set's union extent; duration accepts int/float seconds, a datetime.timedelta, or a string like "30s".

TraceSource

Central source for trace events in an application.

A TraceSource represents a single data source within your application (like a service or component) and manages the event schemas and transmission of events to the trace collection system.

Examples: >>> client = TracePublishClient() >>> source = TraceSource("motor_controller") >>> >>> # Define an event schema >>> motor_event = source.add_event("motor_stats", [ ... TraceEventFieldMetadata("rpm", DataType.Float64), ... TraceEventFieldMetadata("torque", DataType.Float64, "Nm"), ... TraceEventFieldMetadata("temperature", DataType.Float64, "celsius"), ... TraceEventFieldMetadata("voltage", DataType.Float64, "V"), ... ]) >>> >>> # Log an event >>> motor_event.log(**{ ... "rpm": 3500.0, ... "torque": 42.8, ... "temperature": 75.5, ... "voltage": 48.2 ... })

__repr__()

String representation of the source.

add_event(name, schema, event_type=...)

Register an event schema.

Args: name (str): The event name (e.g. "log"). schema: Either a list[TraceEventFieldMetadata] (ad-hoc fields) or a class with FIELDS and (optionally) EVENT_TYPE classvars — e.g. zelos_sdk.schemas.Log. When a class is passed, FIELDS is used and EVENT_TYPE populates the event_type argument if not explicitly set. event_type (Optional[str]): Stable identifier for this event's schema (e.g. "zelos.log.v1"). Defaults to schema.EVENT_TYPE when schema is a class.

Returns: TraceSourceEvent: A handle to the newly registered event.

Raises: ValueError: If registering the schema fails internally.

add_value_table(name, field_name, data)

Add a value table to the trace source.

Register the event and field with add_event first: a key is typed by the field it labels, so there is nothing to type it against otherwise.

Args: name (str): The name of the value table. data (dict): A dictionary of values to add to the value table.

Returns: None

Examples: >>> source.add_value_table("motor_status", "state", {0: "stopped", 1: "running"}) >>> source.add_value_table("sensor_data", "sensor_id", {1: "temp_sensor", 2: "pressure_sensor"})

flush()

Flush all buffered events as Arrow RecordBatches.

Events logged via log() are buffered internally and flushed automatically when the batch size or timeout threshold is reached. Call this method to force-flush any remaining buffered events.

Returns: None

get_event(name)

Get a handle to a previously registered event schema.

Args: name (str): The name of the event schema.

Returns: TraceSourceEvent: A handle to the event.

Raises: KeyError: If no event with the given name is registered.

Examples: >>> # After defining an event schema >>> event = source.get_event("motor_stats")

log(name, data)

Log an event with a name and a dictionary of fields.

Args: name (str): The name to log. data (dict): A dictionary of fields to log.

Returns: None

Examples: >>> source.log("sensor_data", {"temperature": 25.0, "pressure": 101325})

log_at(time_ns, name, data)

Log an event with a name and a dictionary of fields.

Args: time_ns (int): The time to log the event at. name (str): The name to log. data (dict): A dictionary of fields to log.

Returns: None

Examples: >>> source.log_at(time.time_ns(), "sensor_data", {"temperature": 25.0})

log_batch(event_name, data)

Log an Arrow RecordBatch directly (zero-copy from PyArrow).

The RecordBatch must have a time_ns column (TimestampNanosecond) as its first column. Schema is auto-registered on the first batch per event name.

Args: event_name (str): The event name for this batch. data: A pyarrow.RecordBatch or pyarrow.Table.

Examples: >>> import pyarrow as pa >>> batch = pa.record_batch({"time_ns": pa.array([1, 2], type=pa.timestamp("ns", tz="UTC")), "value": [1.0, 2.0]}) >>> source.log_batch("sensor", batch)

log_dict(name, data)

Log an event with a name and a dictionary of fields.

Args: name (str): The name to log. data (dict): A dictionary of fields to log. prefix (str): A prefix to add to the event name.

Returns: None

Examples: >>> source.log_dict("sensor_data", {"temperature": 25.0, "pressure": 101325})

log_many(events)

Log multiple events in a single call, minimizing Python↔Rust overhead.

Each entry is a (time_ns, event_name, signals_dict) tuple. Events are grouped by event name and emitted in bulk with the GIL released.

This is the recommended path for high-throughput sources like CAN codecs that decode many frames per cycle.

Args: events: List of (time_ns: int, name: str, data: dict) tuples.

Examples: >>> source.log_many([ ... (time.time_ns(), "sensor", {"temp": 25.0}), ... (time.time_ns(), "sensor", {"temp": 25.1}), ... (time.time_ns(), "status", {"mode": "active"}), ... ])

TraceSourceCache(name, namespace=None)

A TraceSource wrapper that caches the last value of each field.

Uses a Rust core for cache storage, condition evaluation, and emit decisions. Python layer provides Pythonic attribute navigation.

Example: source = TraceSourceCache("motor_controller") source.add_event("motor_stats", [ TraceEventFieldMetadata("rpm", DataType.Float64), TraceEventFieldMetadata("torque", DataType.Float64, "Nm") ]) source.log("motor_stats", {"rpm": 3500.0, "torque": 42.8}) assert source.motor_stats.rpm.get() == 3500.0

add_event(name, schema, conditions=None)

Register an event schema.

add_value_table(name, field_name, data)

Register a value table (enum mapping).

Register the event and field with add_event first: a key is typed by the field it labels, so there is nothing to type it against otherwise.

get_source()

Not supported — use the cache API directly.

log(name, data)

Log data and update cache. Auto-registers event if not yet registered.

log_at(time_ns, name, data)

Log data at a specific timestamp.

set_default_log_condition(condition=None)

Set default log condition. Pass a Python LogCondition or None.

TraceSourceCacheLastEvent(name, cache, source, conditions=None)

A cached event with attribute access to fields and submessages.

Example: event = source.motor_stats event.rpm.get() # field access event.thermal.temp.get() # submessage access event.log(rpm=3500) # log via event

time_ns property

Wall-clock timestamp of the last log call for this event, in nanoseconds since the Unix epoch. None if the event hasn't been logged yet. Symmetric with 🇵🇾meth:TraceSourceCacheLastField.get.

TraceSourceCacheLastField(name, full_path, data_type, cache, event_name, condition=None, uses_default=False)

A cached field that stores the last logged value.

Example: field = event.rpm field.get() # Get cached value field.name # Full path: "motor_stats.rpm"

get()

Get the cached value.

set(value)

No-op — cache is updated atomically during log() calls in the Rust core. Retained for backward compatibility.

TraceSourceEvent

__repr__()

String representation of the event.

log(**kwargs)

Log an event with a dictionary of fields.

Args: kwargs (dict): Keyword arguments to log.

Returns: None

Examples: >>> event = source.add_event("motor_stats") >>> event.log(rpm=3500, torque=42.8)

log_at(time_ns, **kwargs)

Log an event with data provided as a dictionary at a specific time. Performs type checking based on the schema.

Args: time_ns (int): Timestamp in nanoseconds since Unix epoch. fields (dict[str, Any]): Dictionary of field names to values.

Raises: ValueError: If a field is not in the schema. TypeError: If a value's type doesn't match the schema. RuntimeError: If sending the event fails internally.

Examples: >>> event = source.get_event("motor_stats") >>> # Log with custom timestamp >>> event.log_at(1625097600000000000, { ... "rpm": 3500.0, ... "torque": 42.8, ... "temperature": 75.5 ... })

TraceStdout

Python wrapper for the stdout trace sink.

This sink outputs trace events to stdout with configurable log levels. It subscribes to all trace events from the router and formats them as structured log messages.

The sink uses context management and should be used with a with statement to ensure proper resource cleanup and automatic start/stop of trace capture.

Examples: >>> # Basic usage with default settings (info level) >>> with TraceStdout() as sink: ... # Trace events will be logged to stdout ... pass >>> >>> # Custom log level and batch configuration >>> with TraceStdout(log_level="debug", batch_size=500, batch_timeout_ms=2000) as sink: ... # Trace events will be logged with custom settings ... pass

__repr__()

String representation of the sink.

close()

Stop the stdout sink and finalize trace capture.

This method gracefully shuts down the sink and cancels background tasks. It's automatically called when exiting the context manager.

Returns: None

open()

Start the stdout sink and begin capturing events.

This method subscribes to the trace router and starts a background task to process and output trace events to stdout. It's automatically called when entering the context manager (with statement).

Returns: None

Raises: RuntimeError: If the sink cannot be initialized.

TraceTiming

One run's timing entry inside a TimeRangeMulti (per-trace start and duration).

Example: >>> timing = TraceTiming("a.trz", start=trace.time_range.start, duration=trace.time_range.duration) >>> timing.path, timing.start, timing.duration ('a.trz', datetime.datetime(2026, 8, 1, 12, 0, tzinfo=datetime.timezone.utc), datetime.timedelta(seconds=300))

duration instance-attribute

How long the run spans, as a timedelta.

duration_s instance-attribute

How long the run spans, in seconds.

path instance-attribute

Path of the trace file this timing describes.

start instance-attribute

When the run starts, as a datetime.

start_s instance-attribute

When the run starts, as epoch seconds.

TraceWriter

Python wrapper for the TraceWriter.

This writer manages writing trace events to a local file, with support for batching and buffering. It can be used with a TraceSource to capture events for later analysis.

The writer uses context management and should be used with a with statement to ensure proper resource cleanup and automatic start/stop of trace capture.

Examples: >>> # Basic usage with default settings >>> with TraceWriter("my_trace.trz") as writer: ... # Trace events will be captured automatically ... pass >>> >>> # Custom batch configuration >>> with TraceWriter("my_trace.trz", batch_size=500, batch_timeout_ms=2000) as writer: ... # Trace events will be captured with custom batch settings ... pass

__repr__()

String representation of the writer.

close()

Stop the trace writer and finalize trace capture.

This method gracefully shuts down the writer, cancels background tasks, and ensures all buffered events are written to the trace file. It's automatically called when exiting the context manager.

Returns: None

Note: This method is called automatically by exit when using the context manager pattern.

open()

Start the trace writer and begin capturing events.

This method initializes the writer and starts background tasks for batching and writing trace events. It's automatically called when entering the context manager (with statement).

Returns: None

Raises: RuntimeError: If the writer cannot be initialized.

Note: This method is called automatically by enter when using the context manager pattern.

UnsupportedAgentService

Bases: AgentError

The agent is reachable but does not implement the RPC.

Usually means the SDK is newer than the running agent build.

Example: >>> try: ... agent.actions.list() ... except UnsupportedAgentService: ... pass

all_well_known_event_schemas()

(EVENT_TYPE, FIELDS) for every well-known event type, in canonical order — the single enumeration point the Python schema wrappers and the drift test consume. Sourced from the canonical Rust zelos-event-types.

connect(target=None, *, timeout=5.0)

Connect to an agent, checking it answers before returning.

The canonical entry point: from zelos_sdk import connect. Prefer the lazy Agent(target) for long-running scripts and pytest fixtures — its channel reconnects on its own and RPC errors surface at the call site.

Args: target: Agent endpoint; None resolves ZELOS_AGENT_URL, then http://localhost:2300. timeout: Seconds to wait for the health check.

Returns: Agent: A handle whose agent answered.

Raises: AgentUnavailable: nothing answered before timeout. UnsupportedAgentService: something answered but is not a Zelos agent.

Example: >>> agent = connect("http://localhost:2300") >>> agent.signals()[0].path 'bus0/BMS_message/status.pack_current'

enable_logging(log_level=None)

Enable logging for the Zelos SDK native module.

This function initializes the tracing system with the specified log level. If no log level is provided, it defaults to "info".

Args: log_level (Optional[str]): The log level to use. Valid values: "trace", "debug", "info", "warn", "error". Defaults to "info" if not specified.

Returns: None

Examples: >>> enable_logging("debug") # Set log level to debug >>> enable_logging("info") # Set log level to info >>> enable_logging() # Set log level to info

get_global_router_sender()

Get the global default trace router sender (from global namespace)

Returns: TraceSender: The global namespace's router sender

Examples: >>> sender = get_global_router_sender()

init(name=None, *, url=None, client_config=None, log_level=None, trace=True, actions=False, block=False)

Initialize the Zelos SDK tracing and actions systems.

Args: name: Application identifier; defaults to "python". url: Agent endpoint (e.g. "http://host:port"). Forwarded to the trace publish client and the actions client. Falls back to ZELOS_AGENT_URL and finally http://localhost:2300. client_config: Configuration for the TracePublishClient (batch_size, batch_timeout_ms). log_level: Logging level to enable, None leaves logging untouched. trace: Initialize the trace system. Defaults to True. actions: Initialize the actions system. Defaults to False. block: Block the current thread until interrupted (useful for actions-only programs).

Notes: When ZELOS_STANDALONE is set — the standalone action harness sets it, as does the agent when it spawns a one-shot run — no agent connection is opened. The extension is stopped in that context, and connecting anyway would briefly register it as live. The global trace source is still created, so a module that logs through it keeps working; the events go nowhere.

Examples: >>> init() >>> init("my_app", url="grpc://localhost:2300", log_level="debug") >>> init(log_level="debug", trace=False) # logging only >>> init(actions=True, block=True)

init_global_actions_client(name, url=..., actions_registry=...)

Initialize the global actions client with a background task

This creates a global actions client that runs in the background without blocking or interfering with Python's signal handling. The client will automatically reconnect if the connection is lost and can be cleanly shut down via atexit handlers.

Args: name (str): Name for this service url (str): Server URL (e.g., "grpc://localhost:2300") actions_registry (ActionsRegistry): The actions registry to serve

init_global_actions_registry()

Initialize the global actions registry

init_global_client(url=None, config=None)

Initialize the global client with custom settings

Args: url (str): The URL of the trace publish service. Defaults to ZELOS_AGENT_URL (legacy: ZELOS_TRACE_FORWARD_URL), then http://localhost:2300. config (TracePublishClientConfig): Configuration for the client. If None, default settings will be used.

Returns: TracePublishClient: The global client instance

Examples: >>> # Initialize with default settings >>> client = init_global_client() >>> >>> # Initialize with custom settings >>> config = TracePublishClientConfig(batch_size=500) >>> client = init_global_client(url="grpc://localhost:2300", config=config)

init_global_source(name=None)

Get the global TraceSource, creating it on the first call.

Idempotent: later calls return the source that already exists and ignore name. zelos_sdk.init() calls this for you.

Args: name (Optional[str]): Source name for the first call. Defaults to "python".

Returns: TraceSource: The global source.

Examples: >>> source = init_global_source("my_app")

parse_connect_target(target=None)

Normalize a connect-target string into the URL connect() would use, without actually connecting — for pre-flight config validation in CI.

None falls back to ZELOS_AGENT_URL, then http://127.0.0.1:2300. host:port and bare host get an http:// scheme and, for a bare host, the default port; grpc:// normalizes to http://.

Raises: ConnectionTargetError: the string has whitespace, more than one ://, or an otherwise malformed port.

Example: >>> parse_connect_target('localhost:2300') 'http://localhost:2300'

parse_duration(value, *, allow_zero=True)

Non-negative seconds.

Accepts int/float seconds, timedelta, or the unsigned duration-string grammar ("30s", "2m", "1.5h", "500ms"). Rejects bool, negative values, NaN/inf, and (optionally) zero. A signed string ("-30s") is a time offset, not a duration — rejected with a hint to use :func:parse_time instead.

Example: >>> parse_duration("1.5h") 5400.0

parse_time(value)

Normalize to a UTC-aware datetime.

Accepts a datetime (naive → assumed UTC), "now", ISO 8601, or a relative offset ("-30s", "-2m", "-1.5h", "-1d", "+30s"). Relative forms resolve against the wall clock.

Example: >>> parse_time("-30s") datetime.datetime(2026, 9, 2, 11, 59, 30, tzinfo=datetime.timezone.utc)

parse_time_ns(value)

Coerce a flexible time argument to epoch nanoseconds.

Same surface as :func:parse_time plus integer / float / None pass-through:

  • NoneNone (caller decides what unset means).
  • int → epoch ns, but only if unambiguous (>= 1e15, i.e. after 1970-01-12); a smaller int is almost certainly seconds by mistake and raises with a hint.
  • float → epoch seconds, unless it is already too large to be seconds (>= 1e15, year 31 million), in which case it is read as epoch ns.
  • datetime / str → :func:parse_time then converted.

Used at any agent SDK boundary that takes start_ns / end_ns on the wire — the Check API, custom replay helpers, suite runners.

Example: >>> parse_time_ns("now") 1788609600000000000 >>> parse_time_ns(None)

parse_until(value)

Absolute deadline from datetime/ISO-string, or now + N seconds.

A float means "this many real seconds from now". A string in the unsigned duration grammar ("30s", "1.5h") means the same thing. Other string forms go through :func:parse_time and resolve the same way, against the wall clock.

Example: >>> parse_until("30s") datetime.datetime(2026, 9, 2, 12, 0, 30, tzinfo=datetime.timezone.utc)

sanitize_name(name, *, kind='source')

Rewrite an externally-sourced name into one that registers cleanly.

Trace names are an allow-list — letters, digits, spaces, _ and -, plus / for event names — enforced when a name is registered: TraceSource(...), add_event(...) and the first log_batch(...) raise ValueError on a name that violates it. Names lifted out of an external artifact (DBC signals, scope channel labels, packet decoder schemas) routinely do. This is the one blessed way to fix them up; do not hand-roll a sanitizer.

Every disallowed character becomes _ (a run of them collapses to a single _), edge spaces and underscores are trimmed, the result is capped at 128 bytes on a character boundary, and a name with nothing left becomes "unnamed".

Lossy. Distinct inputs can collapse to the same name (a.b, a..b and a:b all become a_b). De-duplicate before registering if the upstream artifact can produce collisions — the trace layer treats two identical names as one signal.

Idempotent. sanitize_name(sanitize_name(x)) == sanitize_name(x).

Args: name (str): The raw, externally-sourced name. kind (str): Which name this is: "source", "event", "field" or "value_table". Only event and value-table names may contain /; for the others it is substituted. Defaults to "source".

Returns: str: A name that always passes registration for that kind.

Raises: ValueError: If kind is not one of the four listed above.

Examples: >>> sanitize_name("Engine.RPM[0]", kind="field") 'Engine_RPM_0' >>> sanitize_name("bus:can0") 'bus_can0' >>> sanitize_name("battery/status", kind="event") 'battery/status' >>> sanitize_name("...") 'unnamed'

well_known_event_schema(event_type)

(EVENT_TYPE, FIELDS) for one well-known event type id, read straight from the canonical Rust zelos-event-types::field_schemas(). The Python schemas.* wrappers populate their FIELDS/EVENT_TYPE from this, so the two languages cannot drift.