Skip to content

Agent

zelos_sdk.agent connects to a running Zelos agent. It queries live signals, opens trace files, runs checks and actions, and manages extensions and layouts. Most names here also import from the top level, for example zelos_sdk.connect(). Import validate_suite and SuiteValidationError from zelos_sdk.agent.checks.

Public Agent SDK exports and connect helper.

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.

Examples:

>>> 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.

Examples:

>>> [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.

Examples:

>>> 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.

Examples:

>>> 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.

read_only instance-attribute

Whether the action declares it changes nothing.

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.

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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, info, start, stop, restart) 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.

__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.

Parameters:

Name Type Description Default
paths PathsArg

A str, Signal, or sequence thereof.

required
time datetime | str

Anchor timestamp (datetime, str, or epoch ns).

required
min_time datetime | str | None

Optional lower bound; values strictly older than this are skipped. Also floors the window wildcards resolve against.

None
producers ProducersArg | None

Producer addresses; None covers every connected producer.

None

Returns:

Name Type Description
Snapshot Snapshot

One entry per matched signal, keyed by path.

Raises:

Type Description
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)

download(url, path=None, *, overwrite=False)

Download a cloud trace to a local TRZ2 .trz file. Synchronous, on a connection with no RPC deadline; Ctrl+C interrupts it. The AGENT writes the file, so against a remote agent it lands on that host.

Parameters:

Name Type Description Default
url str

The trace's console URL, https://<console>/<org>/traces/<uuid> (Upload.url). The desktop app's zelos://open?… deep link is accepted too.

required
path str | PathLike | Path | None

Destination .trz, resolved against this process's cwd. None names it after the trace in the current directory.

None
overwrite bool

Replace an existing destination.

False

Raises:

Type Description
ValueError

url is not a cloud trace.

FileExistsError

destination exists and overwrite=False.

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

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.

Parameters:

Name Type Description Default
path str | PathLike | Path

Filesystem destination for the .trz file.

required
start datetime | str

Start of the time window.

required
end datetime | str | None

End of the window; defaults to "now".

None
signals PathsArg | None

Optional str, Signal, or sequence naming the producers to export; None (default) exports every connected producer. (paths= is the older spelling.)

None
producers ProducersArg | None

Producer addresses; None (default) covers every connected producer.

None
overwrite bool

When True, replace an existing file. Required for cross-host writes regardless of whether the path exists.

False
format str | None

"trz2" (sealed catalog + Parquet; the only format Agent.upload accepts) or "legacy" (DuckDB). None rides the agent's release default; ExportResult.format reports what was written.

None

Returns:

Name Type Description
ExportResult ExportResult

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

Raises:

Type Description
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.

Examples:

>>> 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.

Parameters:

Name Type Description Default
paths PathsArg

A str, Signal, or sequence thereof.

required
lookback float

How far back to look for a value (default 60 s).

60.0
producers ProducersArg | None

Producer addresses; None covers every connected producer.

None

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.

Parameters:

Name Type Description Default
producers ProducersArg | None

Producer addresses; None covers every connected producer.

None
lookback float

Seconds since a signal last produced a sample (default 60).

60.0

trace(path)

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

path is a local .trz file, or a cloud trace addressed by its console URL — the link you copy from the trace page or its Copy-link button:

with agent.trace("https://console2.zeloscloud.io/acme/traces/<uuid>") as trace:
    ...

The zelos://open?org=<slug>&trace=<uuid> deep link is accepted too. It is what the desktop app's protocol handler passes around; the URL is the form a person actually has.

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 traces — 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"})). Each entry is a local .trz file or a cloud trace's console URL, and the two can be mixed, so a local run compares against a cloud one without downloading it first. Label a cloud trace yourself: a URL has no useful file stem.

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().

upload(path=None, *, live=False, producers=None, organization=None, name=None, tags=[], wait=True, timeout=None, poll_interval=0.5)

Upload a trace to Zelos Cloud through the agent, which holds the login token, seals the artifact and moves the bytes as a detached task.

Exactly one source: live=True uploads the agent's live session (needs its disk store), or path names a sealed TRZ2 artifact. A legacy DuckDB .trz is refused — make one with agent.export(..., format="trz2").

Parameters:

Name Type Description Default
path str | PathLike | Path | None

Sealed TRZ2 artifact, resolved against this process's cwd.

None
live bool

Upload the live session instead.

False
producers ProducersArg | None

With live=True, which producers to seal; None (default) is the agent plus every remote it is connected to.

None
organization str | None

Org slug; None is your active organization.

None
name str | None

Human-facing label; defaults to trace_<UTC timestamp>.

None
tags Sequence[str]

Existing org tag NAMES (case-insensitive), resolved before anything is allocated. Never creates a tag.

[]
wait bool

Block until the upload finishes (default); False returns the handle for status() / wait() / cancel().

True
timeout float | None

Seconds to wait when wait=True; None waits forever. On expiry the upload is cancelled (there is no handle to do it with) and TimeoutError raised.

None
poll_interval float

Seconds between status polls while waiting.

0.5

Raises:

Type Description
ValueError

no source, both sources, a path that is missing or a cloud URL, an unknown tag name.

NoData

legacy artifact, or a live upload on a memory-store agent.

QueryRangeTooLarge

the organization's storage quota is exhausted.

UploadFailed

the agent reported the upload failed (when waiting).

TimeoutError

timeout elapsed; the upload was cancelled.

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.

Parameters:

Name Type Description Default
paths PathsArg

A str, Signal, or sequence thereof.

required
start datetime | str

Window start.

required
duration float | int | str | timedelta

Window length.

required
producers ProducersArg | None

Producer addresses; None covers every connected producer.

None
display_fps int

Optional display-rate cap on the change stream.

0

Returns:

Name Type Description
ReplayWindow ReplayWindow

Snapshot + changes for the window.

Raises:

Type Description
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.

Examples:

>>> [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.

Parameters:

Name Type Description Default
timeout float | None

Seconds. None applies the action's own default timeout. The call stops waiting after five minutes either way.

None

Returns:

Name Type Description
ActionResult ActionResult

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

Raises:

Type Description
Internal

the agent answered without a result payload.

Examples:

>>> 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:

Type Description
list[ActionInfo]

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

Raises:

Type Description
AgentUnavailable

agent is unreachable.

schema(action, current_values=None)

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

Parameters:

Name Type Description Default
current_values dict | None

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

None

Returns:

Name Type Description
ActionSchema ActionSchema

The action's form schema.

Raises:

Type Description
Internal

the agent answered without a schema payload.

Examples:

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

AgentCancelled

Bases: AgentError

The RPC was cancelled or its deadline expired.

Examples:

>>> 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.

Examples:

>>> try:
...     agent.latest('bus0/typo.voltage')
... except AgentError as exc:
...     exc.cause
{'code': 5, 'code_name': 'NotFound', 'message': "signal 'bus0/typo.voltage' not found"}

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.

Examples:

>>> [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.

Parameters:

Name Type Description Default
id str

Extension identifier (e.g. "zeloscloud.zelos-extension-can").

required
version str | None

Specific version to query; None resolves to the currently-installed version.

None

Returns:

Name Type Description
ExtensionInfo ExtensionInfo

The extension's manifest.

Raises:

Type Description
ExtensionError

nothing matching id is installed.

Internal

the agent answered without a manifest.

Examples:

>>> 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:

Type Description
list[ExtensionEntry]

list[ExtensionEntry]: One entry per installed extension.

Raises:

Type Description
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.

Parameters:

Name Type Description Default
id str

Extension identifier.

required
version str | None

Specific version; None resolves to the currently-installed version.

None

Returns:

Name Type Description
str 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.

Examples:

>>> 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.

Examples:

>>> 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.

Parameters:

Name Type Description Default
id str

Extension identifier.

required

Raises:

Type Description
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.

Examples:

>>> 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).

Examples:

>>> 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.

Parameters:

Name Type Description Default
name str

Human-readable layout name.

required
data dict

Arbitrary JSON-serializable dict (panels, config, …).

required
is_personal bool

When True, scope the layout to the current user rather than the team. Defaults to False.

False

Returns:

Name Type Description
Layout Layout

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

Raises:

Type Description
ValueError

data is not JSON-serializable.

AgentError

the agent is not logged in, or returned malformed data.

delete(id)

Delete a layout by id.

Parameters:

Name Type Description Default
id str

The layout's UUID (as a string).

required

Raises:

Type Description
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:

Type Description
list[Layout]

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

Raises:

Type Description
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.

Parameters:

Name Type Description Default
id str

The layout's UUID (as a string).

required
version int

The version number to restore (versions start at 1).

required

Returns:

Name Type Description
Layout Layout

The layout after the restore.

Raises:

Type Description
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.

Parameters:

Name Type Description Default
id str

The layout's UUID (as a string).

required
version int

The version number to label (versions start at 1).

required
label str | None

The label to set, or None to clear the current label.

required

Returns:

Name Type Description
LayoutVersion LayoutVersion

The updated version metadata.

Raises:

Type Description
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.

Parameters:

Name Type Description Default
id str

The layout's UUID (as a string).

required

Returns:

Name Type Description
Layout Layout

The requested layout.

Raises:

Type Description
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.

Parameters:

Name Type Description Default
id str

The layout's UUID (as a string).

required
version int

The version number to fetch (versions start at 1).

required

Returns:

Name Type Description
LayoutVersion LayoutVersion

The requested version, with its data snapshot.

Raises:

Type Description
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.

Parameters:

Name Type Description Default
id str

The layout's UUID (as a string).

required
name str | None

New name, or None to keep the current name.

None
data dict | None

New payload dict, or None to keep the current payload.

None
is_personal bool | None

New personal/team scope, or None to keep the current scope.

None

Returns:

Name Type Description
Layout Layout

The updated layout.

Raises:

Type Description
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().

Parameters:

Name Type Description Default
id str

The layout's UUID (as a string).

required

Returns:

Type Description
list[LayoutVersion]

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

Raises:

Type Description
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.

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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).

Examples:

>>> 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).

Examples:

>>> 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.

Examples:

>>> 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).

Examples:

>>> 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(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().

Examples:

>>> 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 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 that.

strict matches 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 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 that — scalar for a single producer, dict for fan-out. Per-producer transport failures synthesize 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 run: each spec yields a scalar 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()[path] or trace.signals()[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_close → math.isclose: rel_tol=1e-9, abs_tol=0; is_approximately → pytest.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 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 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 DEFAULT_DURATION_INTERVAL_S). On TraceChecks the same temporals desugar to Always / Ever over [trace.end_ns - duration_s*1e9, trace.end_ns], the last duration_s of the recording — 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.

Examples:

>>> frame = agent.query(["bus0/BMS_message/status.pack_current"], start="-5m")
>>> frame.meta[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.

Examples:

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

DownloadResult

The file Agent.download(...) wrote.

Usable wherever a path is: open(result), Path(result), agent.trace(result.path).

Examples:

>>> result = agent.download("https://console2.zeloscloud.io/acme/traces/0198f0e1-…")
>>> result.path, result.size_bytes
('/home/me/dyno_run_14.trz', 61440)

path instance-attribute

The .trz file written, as the agent reported it.

size_bytes instance-attribute

Its size on disk.

ExitInfo

Exit information from a terminated extension process.

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

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

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").

Examples:

>>> 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.

Examples:

>>> 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).

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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).

Examples:

>>> 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.

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.

Examples:

>>> 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.

Examples:

>>> 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 Type Description
name

How the value was produced, e.g. max(bus0/BMS.voltage).

unit

The unit it inherits from the series, None when dimensionless.

Examples:

>>> 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.

to(unit)

The same value in unit, as SignalSeries.to converts: energy.max().to("kWh").

NoData

Bases: AgentError

The signal exists but produced no value inside the window.

Examples:

>>> 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.

Examples:

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

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.

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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().

Examples:

>>> 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.

Examples:

>>> 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 SignalSeries per run of a zelos_sdk.TraceSet, keyed by run label.

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

Examples:

>>> 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.

Examples:

>>> 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}". A column a frame computed under a plain label, such as "power", is that label.

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().

Examples:

>>> 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).

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.

items()

(path, signal) pairs in catalog order.

keys()

Every path in catalog order, the same list as paths. SignalFrame and Snapshot answer keys(), values() and items() too.

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.

Parameters:

Name Type Description Default
query str

Substring to search for.

required

Returns:

Name Type Description
SignalCatalog 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.

values()

Every signal in catalog order: list(catalog), by its mapping name.

SignalFrame

A query's rows: one per timestamp, one column per signal, null where a signal had no sample.

It reads like a DataFrame: frame[path] is a SignalSeries, frame[[a, b]] a sub-frame, frame[mask] the rows a boolean series keeps, and frame["+10s":"+20s"] a time window.

Examples:

>>> frame = agent.query(["bus0/BMS_message/status.pack_current"], start="-5m")
>>> frame["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

Deprecated alias of columns.

columns instance-attribute

Every column label except the time column, as pandas' df.columns reads: the same labels as keys() and the columns of to_pandas().

downsampled instance-attribute

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

dtypes instance-attribute

Each data column's pandas dtype, indexed by label: the dtypes of to_pandas(), read without converting the rows.

empty instance-attribute

Whether the frame has no rows or no data columns, as in pandas.

iloc instance-attribute

Rows by position, as pandas' iloc: frame.iloc[10:20], and frame.iloc[10:20, [0, 2]] for some columns, by position or label. One row, frame.iloc[-1], is a pandas Series; frame.iloc[0, 2] is one value.

index instance-attribute

The time axis as pandas reads it: the UTC DatetimeIndex named time that to_pandas() indexes by, or a TimedeltaIndex of offsets on a run-aligned frame.

loc instance-attribute

Rows by time or by a mask, as pandas' loc: frame.loc["+10s":"+20s"], frame.loc[mask], and frame.loc[mask, ["bus0/BMS.voltage"]] for some columns. One timestamp is one row, a pandas Series, as pandas reads it.

meta 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.

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.

shape instance-attribute

(rows, columns), the time column not counted, as pandas reports it.

signals instance-attribute

Resolved catalog signals in column order (excludes the time column), as the SignalCatalog agent.signals() returns: frame.signals["bus0/BMS.voltage"].unit reads the same way.

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.

__array__(dtype=None, copy=None)

numpy's array protocol: np.asarray(frame) is frame.to_numpy(). pandas.DataFrame(frame) would read that array without the time axis or the labels, so it raises and names to_pandas().

__arrow_c_stream__(requested_schema=None)

The Arrow PyCapsule stream of to_arrow(): time first, then one column per label. pandas.DataFrame.from_arrow(frame), pyarrow.table(frame) and polars.DataFrame(frame) read it.

__contains__(name)

Return bool(key in self).

__iter__()

Iterating a SignalFrame yields its column labels, as a pandas DataFrame and a dict do: for label in frame: frame[label]. The catalog Signals are frame.signals.

__setitem__(key, value)

frame[label] = value sets a column in place, as pandas does: value replaces the column label names, or is appended last under a new label. frame.assign(label=value) does the same on a copy, and takes the same values.

Examples:

frame["bus0/BMS.power"] = frame["bus0/BMS.voltage"] * frame["bus0/BMS.current"]

assign(**columns)

A copy of the frame with each keyword set as a column, as pandas' assign does: a label already in the frame is replaced, a new one is appended last. A value is a SignalSeries on this frame's time axis, which brings its unit; a scalar, which fills every row; or one value per row as a list, tuple, numpy array or pyarrow array. A callable is called with the frame built so far and its result assigned.

Any assigned column makes the frame derived: its columns stand for the samples held here, and a check evaluates those rather than re-querying.

Examples:

frame.assign(power=lambda f: f["bus0/BMS.voltage"] * f["bus0/BMS.current"])

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.

Examples:

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

corr(**kwargs)

to_pandas().corr(**kwargs): the pandas DataFrame of pairwise correlations between the data columns, as describe() returns a pandas one. method= takes "pearson", "kendall" or "spearman".

Examples:

frame.ffill().corr()["bus0/BMS.current"]

describe()

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

Examples:

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

dropna(how='any', subset=None)

Drop rows with nulls: how="any" (default) keeps only rows where every column has a sample, how="all" drops only the empty rows. subset names the columns to look at, as a label or a list of them, as in pandas; the others keep whatever nulls they have.

Examples:

frame.dropna() # rows both messages landed on frame.dropna(subset=["bus0/BMS.voltage"]) # rows with a voltage sample

ffill(limit=None)

Sample-and-hold every column: each null takes the last value before it. limit fills at most that many nulls in a row, as in pandas.

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

Examples:

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

fillna(value)

Every null takes value, per column, as pandas' fillna; a column whose type cannot hold value is left as it is.

Examples:

frame.fillna(0.0)

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

Build a frame from an Arrow IPC stream — the same bytes the wire carries in arrow_ipc_data — with columns positionally aligned to its schema (entry 0 describes the time column). Lets local Arrow data and tests use the frame API without an agent.

signals is the catalog view of the data columns, parallel to columns[1:]. Pass it and the frame carries units and value tables the way a queried one does: frame["path"].unit, frame.signals, plots.

from_pandas(df, *, units=None) staticmethod

Build a frame from a pandas DataFrame: its DatetimeIndex (naive reads as UTC), or else its time column, is the time axis, and every other column is a data column labeled by its name. A TimedeltaIndex is a run-aligned axis of offsets, as to_pandas() writes for one.

Each column's unit comes from units. pandas carries df.attrs through arithmetic unchanged, so a unit left there is not trusted.

Examples:

df = frame.to_pandas() df["bus0/BMS.power"] = df["bus0/BMS.voltage"] * df["bus0/BMS.current"] frame = SignalFrame.from_pandas(df, units={"bus0/BMS.power": "W"})

groupby(by, **kwargs)

to_pandas().groupby(by, **kwargs), the pandas GroupBy it returns. by takes column labels, and a SignalSeries on this frame's time axis groups the rows it lines up with.

Examples:

frame.groupby(frame["bus1/inverter.state"])["bus0/BMS.current"].mean()

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(*columns, signals=None, **kwargs)

Render with zelos_sdk.agent.plot.plot_frame: every column, or only the ones named — frame.plot("bus0/BMS.voltage") — by the keys frame[[...]] takes, one per argument or in one list, or as signals=[...]. Keyword arguments (title=, height=, zero=) forward verbatim.

The legend names each line by its column label, so frame.short_names().plot() and frame.rename({...}).plot() choose it.

rename(mapping=None, *, columns=None)

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

Examples:

frame.rename({"bus0/BMS_message/status.pack_current": "current"}) frame.rename(columns=str.upper)

resample(every, how='mean')

Aggregate onto a fixed time grid: one row per every, combined with how ("mean", "min", "max", "median", "sum", "std", "count", "first", "last"), as pandas' resample(every).<how>().

A bucket with no samples is missing, for "sum" too; "count" counts it as 0. A state (a signal with a value table, a boolean, or text) takes its last value in each bucket under "mean", "median", "sum" and "std", because averaging codes invents codes. Units follow the numbers: "count" is unitless, and the "std" of a temperature reading is a difference (ΔdegC), as SignalSeries.std() reports it.

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

Examples:

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.

Examples:

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_numpy(dtype=None)

The data columns as one 2-D numpy array, a row per timestamp and a column per signal, as pandas' DataFrame.to_numpy(). Nulls read as NaN; dtype casts, and an integer or boolean dtype refuses a frame with nulls rather than inventing values. np.asarray(frame) reads the same array.

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.

Examples:

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.

Examples:

>>> 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, numpy arrays and plain sequences are unit-blind operands paired by position; a pandas object is refused unless it sits on the series' own time axis. Values are always a pa.ChunkedArray; time is the frame's shared time column, so two series from one frame align by identity.

What the series does natively answers to its pandas name (gt(), fillna(), shift(), rolling(), index, &); a pandas name it does not have raises an AttributeError naming the .to_pandas() call that does.

Attributes:

Name Type Description
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.

Examples:

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

dtype property

The numpy dtype pandas would give these samples, as Series.dtype.

empty property

Whether the series has no samples at all.

iloc property

Position-based access by its pandas name: series.iloc[0], series.iloc[-1] and series.iloc[2:5] are series[0], series[-1] and series[2:5].

index property

The time axis as pandas reads it: the UTC DatetimeIndex named time that to_pandas() carries, or a RangeIndex without one.

legend property

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

loc property

Time-based access by its pandas name: series.loc["+10s":"+20s"] keeps a window, both ends included, in SignalFrame.between's grammar; series.loc[mask] keeps the samples a boolean series is true on; series.loc[timestamp] is the sample at that instant.

null_count property

Number of null samples in the series.

shape property

(len(series),), as pandas reports it.

size property

Number of samples, nulls included: len(series).

unit property

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

abs()

Magnitude of every sample, unit kept; the magnitude of a temperature reading, a distance from an arbitrary zero, has none.

add(other, fill_value=None)

series + other, by its pandas name.

all()

Whether every present sample is true (nonzero); True when none is present.

any()

Whether any present sample is true (nonzero); False when none is present.

asof(where)

The last present sample at or before where, as pandas' asof; nan before the first sample. where reads the between() grammar: an absolute time, "+10s", "-10s".

astype(dtype)

Cast every sample to dtype (a numpy dtype or its name, a Python type, or a pa.DataType), keeping the unit. A float truncates toward zero on its way to an integer, as in pandas; nulls stay nulls.

between(left, right, inclusive='both')

Boolean series, True where the sample lies between left and right, as pandas' Series.between: inclusive is "both", "neither", "left" or "right". For rows inside a time window, slice by time: series["+10s":"+20s"].

clip(lower=None, upper=None, **kwargs)

Clamp values to [lower, upper]. At least one bound is required. A bound is a number, or a series aligned by time as pandas aligns it and converted to this series' unit; where it is missing, that side is unbounded. numpy's own keywords are accepted at their defaults, so np.clip(series, 0, 30) stays a series.

corr(other, method='pearson')

Correlation with other, aligned by time as pandas aligns it: "pearson", "spearman" or "kendall".

count()

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

cumsum(axis=None, skipna=True, **kwargs)

Running total over the samples, unit kept, skipping a missing one as pandas does; np.cumsum(series) too. A boolean series counts its true samples, so s.ne(s.shift(fill_value=False)).cumsum() numbers the runs of s.

derivative()

Numerical derivative over time: centered differences (np.gradient), which average each sample's two neighbors. For the rate between consecutive samples, divide diff() by the step.

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

Deprecated alias of from_arrow on this series' time axis.

describe()

pandas' summary (count, mean, std, min, quartiles, max) with the unit as its first row, as frame.describe() gives each column.

diff()

Delta to the previous sample; the first value is null. Two temperature readings differ by a ΔdegC, not a degC.

div(other, fill_value=None)

series / other, by its pandas name.

dropna()

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

eq(other)

series == other, by its pandas name.

ffill(limit=None)

the first limit nulls of each gap, as in pandas.

fillna(value)

Each null takes value, unit kept: (speed == 0).fillna(False). A series fills each gap with its own sample at that time, as pandas aligns it, converted to this series' unit: speed.fillna(gps_speed).

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. The unit is unit: pandas carries attrs through arithmetic unchanged, so a unit found there may no longer be true.

ge(other)

series >= other, by its pandas name.

get()

Deprecated alias of to_pandas.

groupby(by, **kwargs)

pandas' groupby on this series, returning the pandas groupby: power.groupby(state).mean(). by is a series on the same time axis (a state, run numbers from cumsum()), or anything pandas takes.

gt(other)

series > other, by its pandas name.

head(n=5)

The first n samples, time kept alongside.

idxmax()

Where the largest sample is: its timestamp from index, or its position when the series has no time axis.

idxmin()

Where the smallest sample is: its timestamp from index, or its position when the series has no time axis.

integrate()

Cumulative trapezoidal integration over time; first sample is 0.

Neighboring samples are joined by a straight line, so a series cut down by a mask integrates across the gaps the mask left. To integrate only while a condition holds, zero the rest instead: current.where(current > 100).fillna(0.0).integrate().

isin(values)

Boolean series, True where the sample is one of values: fault.isin([3, 7]). A missing sample matches nothing.

isna()

Boolean series, True wherever the sample is missing.

isnull()

pandas' other name for isna.

le(other)

series <= other, by its pandas name.

lt(other)

series < other, by its pandas name.

max(axis=None, skipna=True, **kwargs)

Largest present sample; nan when there is none.

mean(axis=None, skipna=True, **kwargs)

Arithmetic mean over the present samples.

median(axis=None, skipna=True, **kwargs)

Exact median of the present samples.

min(axis=None, skipna=True, **kwargs)

Smallest present sample; nan when there is none.

mul(other, fill_value=None)

series * other, by its pandas name.

ne(other)

series != other, by its pandas name.

notna()

Boolean series, True wherever a sample is present.

notnull()

pandas' other name for notna.

nunique(dropna=True)

Number of distinct values; dropna=False counts a missing sample as one more, as in pandas.

plot(**kwargs)

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

pow(other, fill_value=None)

series ** other, by its pandas name.

quantile(q=0.5)

The q quantile of the present samples, interpolated linearly as in pandas and named by percentile, as in p95(...). A list of quantiles returns a pandas.Series indexed by q, as pandas does.

radd(other, fill_value=None)

other + series, by its pandas name.

rate()

Change per second from each sample to the next: diff() over the time between the two, in unit/s; the first value is null. Unlike derivative(), nothing is averaged, so a step shows at full height.

rdiv(other, fill_value=None)

other / series, by its pandas name.

reindex(index, method=None, tolerance=None)

This series on another time axis, as pandas' reindex: a timestamp it lacks is missing, or with method="ffill" takes the sample before it. Put one signal on another's axis to combine them: speed * gps.reindex(speed.index, method="ffill"). index is a pandas index or a series, whose timestamps are used.

rename(name)

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

replace(to_replace=None, value=_KEEP, regex=False)

Values swapped as pandas' replace swaps them, time and unit kept: series.replace([np.inf, -np.inf], np.nan). A NaN is a missing sample, as everywhere in a series.

resample(every, how=None)

This series on a fixed time grid of every, one bucket per step, as in pandas: speed.resample("1s").mean(), or as a frame takes it, speed.resample("1s", how="mean"). An empty bucket is missing, and count() counts it as 0. A boolean's mean is the fraction of samples it was true; a text series takes each bucket's last value under mean, median, sum and std.

rmul(other, fill_value=None)

other * series, by its pandas name.

rolling(window, min_periods=None, center=False)

A moving window to aggregate: window samples, or a duration ("1s", a timedelta) over time, as in speed.rolling("1s").mean(). As in pandas, a sample window needs window present samples unless min_periods says fewer, a duration window needs one, and center=True labels each window by its middle sample rather than its last.

round(decimals=0)

Each sample rounded to decimals places, unit kept; round(series) too.

rsub(other, fill_value=None)

other - series, by its pandas name.

runs(min_samples=None, min_duration=None)

Each stretch of true samples in a boolean series, one row per run: its start, end, duration and number of samples. min_samples and min_duration keep only the runs that long: (speed == 0).runs(min_duration="2s").

A run lasts until the sample that ends it, so one true sample at 4 Hz lasts 250 ms. A run cut off by either end of the capture, whose true start or end is unseen, has complete False: its duration is only what was recorded. A missing sample ends a run, since a comparison reads it as False, so on a sparse signal compare the sample-and-held series: (temp.ffill() > 20).runs().

shift(periods=1, freq=None, fill_value=None)

Each sample moved periods rows later on the same time axis (earlier when negative); the rows it leaves hold fill_value, a null by default. With freq, as in pandas, the samples stay and their times move periods times freq: speed.shift(freq="1s").

std(axis=None, skipna=True, ddof=1, **kwargs)

Sample standard deviation (ddof=1, as in pandas; np.std passes 0). A spread of temperature readings is a difference: ΔdegC.

sub(other, fill_value=None)

series - other, by its pandas name.

sum(axis=None, skipna=True, **kwargs)

Total of the present samples. A boolean series counts its true samples and returns an int, as count() does.

tail(n=5)

The last n samples, time kept alongside.

time_arrow()

Deprecated alias of the time attribute.

to(unit)

The same quantity in unit: power.to("kW"), energy.to("Wh"), speed.to("rad/s"). SI prefixes and the common engineering units convert; a degC reading converts to degF with the offset, and a ΔdegC difference by scale alone.

to_arrow()

The samples as a pa.ChunkedArray, without copying.

to_list()

The samples as a Python list, a missing one as None.

to_numpy(dtype=None)

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

dtype casts the samples first, as pandas does; a dtype that cannot hold NaN refuses a series with nulls rather than inventing values.

to_pandas()

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

tolist()

pandas' other name for to_list.

truediv(other, fill_value=None)

pandas' other name for div.

unique()

The distinct values in order of first appearance, as a numpy array; a missing sample is nan, as in pandas.

value_counts()

How often each present value occurs, most frequent first, as a pandas.Series indexed by value: a state or fault code's histogram.

var(axis=None, skipna=True, ddof=1, **kwargs)

Sample variance (ddof=1, as in pandas), in the square of the unit a spread has: V², ΔdegC².

where(mask, other=None)

Keep values where mask is True; the rest become other, missing by default, as pandas' where. other is a number, or a series aligned by time as pandas aligns it and converted to this series' unit.

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.

Examples:

>>> 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.

SortOrder

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

Examples:

>>> 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).

Examples:

>>> 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).

Examples:

>>> 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).

SuiteValidationError(source, index, message)

One problem validate_suite found in a check suite file.

Attributes:

Name Type Description
source

Path of the suite file.

index

0-based index of the failing entry, or -1 when the whole file failed (unreadable, malformed JSON, or a root that is not an array).

message

What is wrong, naming the entry's name when it has one.

Examples:

>>> for error in validate_suite("checks/live.json"):
...     print(error.source, error.index, error.message)

TimeMode

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

Examples:

>>> 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).

Examples:

>>> 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).

Examples:

>>> TimeMode.Relative.value
'relative'

value instance-attribute

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

TimeRange

Inclusive datetime interval [start, end].

Examples:

>>> 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.

Examples:

>>> 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.

Examples:

>>> 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

How this trace was addressed: its path on disk, or a cloud trace's zelos://open?org=…&trace=… deep link, which a console URL becomes.

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.

Parameters:

Name Type Description Default
path str

Destination path for the new .trz file.

required
start datetime | str | None

Slice start; defaults to the file's start.

None
end datetime | str | None

Slice end; defaults to the file's end.

None
overwrite bool

Replace path if it exists.

False
format str | None

"trz2", "legacy", or None for the agent's default.

None

Returns:

Name Type Description
ExportResult 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.

Parameters:

Name Type Description Default
paths PathsArg

A str, Signal, or sequence thereof.

required
start datetime | str | None

Window start; defaults to the file's start.

None
end datetime | str | None

Window end; defaults to the file's end.

None
downsample int | None

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.

None
max_rows int

Cap on returned rows; 0 (default) means no cap.

0
sort SortArg | None

"asc" (default) or "desc".

None

Returns:

Name Type Description
SignalFrame SignalFrame

Arrow-backed frame on an epoch time axis.

Raises:

Type Description
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(), catalog[path], and slicing — same shape as the live agent catalog.

Returns:

Name Type Description
SignalCatalog 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.

TraceNotFound

Bases: AgentError

The agent has no trace file at that path.

Examples:

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

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.

Examples:

>>> 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

How each trace was addressed (path on disk, or a cloud trace's zelos:// deep link), 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.

Parameters:

Name Type Description Default
paths PathsArg

A str, Signal, or sequence thereof. Wildcards expand against the union catalog.

required
start datetime | str | None

Window start; defaults to each run's start.

None
end datetime | str | None

Window end; defaults to the longest run's end.

None
downsample int | None

Optional M4 bucket count; mutually exclusive with max_rows / sort.

None
max_rows int

Cap on returned rows; 0 (default) means no cap.

0
sort SortArg | None

"asc" (default) or "desc".

None

Returns:

Name Type Description
SignalFrame 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:

Name Type Description
SignalCatalog 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".

TraceTiming

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

Examples:

>>> 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.

UnsupportedAgentService

Bases: AgentError

The agent is reachable but does not implement the RPC.

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

Examples:

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

Upload

A cloud upload in progress, returned by Agent.upload(...).

The upload runs on the agent; this handle polls it. It stays valid for the life of the agent connection.

Examples:

>>> upload = agent.upload(live=True, wait=False)
>>> while not (status := upload.status()).done:
...     print(status.phase, status.bytes_done, status.bytes_total)
>>> agent.download(upload.url)

organization instance-attribute

The organization the trace landed in (the slug given, or the active organization the agent resolved).

trace_id instance-attribute

The cloud trace's id.

url instance-attribute

The trace's page in the console, https://<console>/<org>/traces/<id>: the link to share, and what Agent.download and Agent.trace take. None from an agent too old to report it.

cancel()

Request cancellation; the agent's detached task owns the teardown.

status()

Poll the upload once.

Raises:

Type Description
AgentError

the agent no longer knows this upload (finished uploads are evicted from its registry under pressure).

wait(timeout=None, poll_interval=0.5)

Block until the upload reaches a terminal phase.

Parameters:

Name Type Description Default
timeout float | None

Seconds before raising TimeoutError; None waits forever. A timeout does not cancel the upload — you hold the handle, so call cancel() if that is what you want.

None
poll_interval float

Seconds between status polls.

0.5

Raises:

Type Description
UploadFailed

the agent reported the upload failed.

TimeoutError

timeout elapsed; the upload is still running.

KeyboardInterrupt

Ctrl+C; a cancel is requested first so the agent tears the upload down instead of leaving it running.

UploadFailed

Bases: AgentError

A cloud upload the agent reported as failed.

The upload runs detached on the agent, so the failure arrives as the terminal failed phase of Upload.wait() rather than as an error on any one call; str(exc) carries the agent's (credential-redacted) reason.

Examples:

>>> try:
...     agent.upload(live=True)
... except UploadFailed as exc:
...     print(exc)

UploadStatus

One progress sample of a cloud upload.

phase is one of exporting, sealing, uploading, finalizing, ready, failed; bytes_total is 0 until the agent knows the size.

Examples:

>>> status = upload.status()
>>> status.phase, status.bytes_done, status.bytes_total
('uploading', 1048576, 8388608)

bytes_done instance-attribute

Bytes moved so far.

bytes_total instance-attribute

Bytes the upload will move; 0 until the agent knows.

done instance-attribute

True once the upload reached ready or failed.

error instance-attribute

The agent's failure reason once phase == "failed", else None.

phase instance-attribute

The upload's phase, as a lower-case name.

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.

Parameters:

Name Type Description Default
target str | None

Agent endpoint; None resolves ZELOS_AGENT_URL, then http://localhost:2300.

None
timeout float

Seconds to wait for the health check.

5.0

Returns:

Name Type Description
Agent Agent

A handle whose agent answered.

Raises:

Type Description
AgentUnavailable

nothing answered before timeout.

UnsupportedAgentService

something answered but is not a Zelos agent.

Examples:

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

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:

Type Description
ConnectionTargetError

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

Examples:

>>> 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" or "2min", "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 parse_time instead.

Examples:

>>> 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.

Examples:

>>> 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 parse_time plus integer / float / None pass-through:

  • None → None (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 → 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.

Examples:

>>> 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 parse_time and resolve the same way, against the wall clock.

Examples:

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

validate_suite(path)

Validate a JSON check suite without running any checks.

Checks every entry against the suite rules Checks.suite enforces, without contacting an agent.

Parameters:

Name Type Description Default
path str | PathLike[str] | Iterable[Any]

A suite file, a directory (every *.json in it), a glob pattern, or a list of any of those.

required

Returns:

Type Description
list[SuiteValidationError]

One SuiteValidationError per bad entry or unreadable file. An

list[SuiteValidationError]

empty list means the suite is valid.

Raises:

Type Description
FileNotFoundError

path matches no file.

Examples:

>>> errors = validate_suite("checks/live.json")
>>> for error in errors:
...     print(error)
checks/live.json[1]: malformed JSON: unknown check operator: bigger at line 1 column 61 (entry name='pack voltage')