Release Notes¶
This page provides release notes for the Zelos App and the Zelos SDK.
26.0.9
Summary¶
A much more capable Zelos AI that moves a whole layout to another data source in one step, and faster plots, especially with hundreds of signals.
Added¶
- Action Button Labels: An action can rename its Execute button, for example Flash for a firmware action. Learn more →
Changed¶
- More Capable Zelos AI: Ask to move a layout from one data source to another and every tab changes in one step, as one undo. Zelos AI reads any tab in full, leaves panel settings you didn't mention alone, and states times the way your screen shows them. It sees longer tasks through, and if it has to stop early, it tells you what it did. It knows what every marketplace extension does, so it can find the right one for a job such as converting a CAN log, install it, and open it for you. Learn what Zelos AI can do →
- Faster Plots: Plots with hundreds of signals load up to 29% faster, and long time windows need far less memory. Live plots do less work on every update, especially when several share a cursor.
- Scrolling Tall Layouts: In a layout taller than the window, the scroll wheel scrolls the layout even when the pointer is over a panel. Hold
Option/Altto scroll the panel instead; a hint shows the key the first time. Layouts that fit the window work as before. Learn more → - Logs Follow the Newest Line: A Log panel follows its newest line in every mode, not only live, and opens there. With a cursor set, it opens on the cursor's line. Scroll back and the lines hold still until you choose Jump to latest, which replaces Resume. Learn more →
- Panel Resizing: A resized panel's edges snap to neighboring panels and to the bottom of the view. Resizing toward the timeline scrolls the layout only once the pointer passes the edge.
- Auto-Configure: An auto-configure action sees the form as you typed it, before you save, so a scan can use the connection you just entered. Its message, or its error, shows beside the button in the action's own words, instead of in a toast.
- Getting Started: If your account has no organization yet, Zelos asks you to create one or join your team before you start. A new account then takes the guided tour, which is now eight steps, from connecting an agent to Zelos AI's answer. Learn more →
Fixed¶
- Signals of different types can share a panel. A plot of a CAN signal and a log signal, or a Log, Raw, Packet or CAN panel listing both, used to fail.
- Dragging to zoom a live plot zooms the range you dragged. New data arriving mid-drag used to move the selection to the plot's edge.
- Log, Raw and Packet panels, and the layout itself, keep their scroll position when you switch tabs.
- Long log lines scroll sideways together, instead of one row at a time.
- With two or more traces open, legend readouts and other values at the cursor read every trace, not only the first.
- Plot and State panels show an error when data can't load, instead of staying blurred on old data.
- Log, Raw, Packet and CAN panels open on traces exported in the TRZ2 format.
- With two TRZ2 traces open, the second trace's text values show their labels.
- State panels keep same-named signals from different messages apart, such as two DBC
Countersignals. - A signal missing from part of a recording shows as empty instead of failing the whole panel.
- Saving live data to a trace works for any signal name, including names with dashes, spaces or a leading digit. A message that can't be saved no longer stops the rest.
- The CAN panel names a multiplexed row under its message, for example
04e0_BMU00_InfoMessage.SERIALinstead ofSERIAL, and sorting by Message places it right after its message. - Packet filters for
tcp,udp,icmpand ports keep matching traffic labeled V2GTP, SDP or HomePlug AV. - Plot tooltips no longer show some timestamps one microsecond early.
AI debugging your extension(s), dedicated CAN and Packet panels.26.0.8
Summary¶
Added¶
Fixed¶
Smoother Action panels, clearer legend chips, cleaner saved layouts, a signed Windows installer, and a fix for a launch error some accounts saw after updating.26.0.7
Summary¶
Changed¶
Fixed¶
Trace sharing is here. Save a recording to Zelos Cloud, send your team a link, and explore it together in Zelos. No more passing large trace files around. New Notebooks turn investigations into repeatable analyses you can publish through Zelos Cloud. A new Cloud library, a new Home, and a much more capable Zelos AI bring everything together.26.0.6
Summary¶
Added¶
.trz files into Zelos to open them together.Changed¶
Cmd+\ / Ctrl+\, and open AI Chat with Cmd+L / Ctrl+L. They used to be Cmd+B and Cmd+I, which are bold and italic in the notebook editor. App shortcuts now work inside notebooks, Python cells, and app extensions; Cmd+K / Ctrl+K opens the command palette from any tab, even while typing. Keyboard shortcuts →Fixed¶
true / false in plot tooltips and measurements instead of an empty value.
This is one of our biggest updates yet: Zelos AI, redesigned sidebars, layout version history, new State and Raw panels, richer Plot readouts, and many more panel controls.26.0.5
Summary¶
Added¶
Cmd+I / Ctrl+I. Zelos understands your agents, traces, panels, time range, and cursor. It can find signals, inspect values, summarize ranges, detect crossings, and evaluate checks across live or recorded data. It can also build panels and tabs, control the workspace, and run enabled actions with confirmation. Conversations are saved for later. Zelos AI is rolling out in phases to select users first, then everyone, so it may not appear immediately after updating. Open AI Chat →Changed¶
Fixed¶
This release introduces App Extensions, a new extension surface for building rich, custom workflows directly inside the Zelos desktop app. Extensions can now provide full APP tabs for dashboards and purpose-built tools, alongside major improvements to workspace history, panel display controls, layout editing, search, and large-workspace performance.26.0.4
Summary¶
Added¶
Cmd+Z / Ctrl+Z and Cmd+Shift+Z / Ctrl+Shift+Z. The command palette shows the next undo/redo action when history is available. Learn more →Changed¶
signal_2 appears before signal_10.Fixed¶
This release adds a Command Palette for instant access to any action in the app, along with trace re-export, multi-file open, and automatic segment expansion. It also includes a redesigned plot tooltip, improved measurement tool, and various panel sizing and UI refinements.26.0.2
Summary¶
Added¶
Cmd+K / Ctrl+K to quickly search and run any action across the app — playback controls, workspace management, layouts, settings, extensions, and more. Includes keyboard shortcut hints and fuzzy search. Learn more →.trz file. Supports time-range selection and merging multiple open traces into a single output..trz files at once.Changed¶
Fixed¶
This release introduces a brand-new timeline and playback experience for live and trace workflows. You can now navigate with a shared cursor across any panel, compare traces more easily with 26.0.1
Summary¶
ABS/REL modes, and inspect data faster in any workflow.Added¶
Esc shortcut)ABS/REL):
REL mode aligns traces at t=0 for side-by-side run comparisonABS mode preserves wall-clock timestamps for real-world event correlationChanged¶
Fixed¶
This release introduces multi-trace and multi-agent support. You can now open multiple trace files simultaneously, connect to multiple live agents, and compare data across them in the same panels.26.0.0
Summary¶
Added¶
flight_test_001:agent::motor/status.rpm)
Changed¶
Fixed¶
This release introduces a major redesign of the application with a new Extension Marketplace, improved navigation with redesigned sidebars, and enhanced plot panel capabilities. Key highlights include the ability to browse and install extensions directly from the app, multi-select signals for easier plot creation, and improved user feedback throughout. The layout has been completely reimagined with dedicated sections for connections and layouts, making it easier to manage your workspace.25.0.22
Summary¶
Added¶
Changed¶
Fixed¶
25.0.21
Added¶
Settings -> General -> Memory Limit to enforce a particular limitChanged¶
Fixed¶
25.0.20
Added¶
Changed¶
Fixed¶
25.0.19
Added¶
Changed¶
Fixed¶
25.0.17
Added¶
25.0.16
Fixed¶
25.0.15
Added¶
25.0.14
Added¶
Fixed¶
25.0.13
Added¶
25.0.12
Fixed¶
25.0.11
Fixed¶
25.0.9
Added¶
Fixed¶
25.0.8
Fixed¶
25.0.7
Added¶
Changed¶
25.0.6
Fixed¶
25.0.5
Changed¶
25.0.4
Fixed¶
25.0.2
Fixed¶
25.0.1
Fixed¶
25.0.0
Changed¶
Fixed¶
24.1.11
Added¶
Changed¶
Fixed¶
24.1.10
Added¶
ctrl-t new tabctrl-w close tabctrl-[1-9] switch to tabctrl-shift-[ previous tabctrl-shift-] next tabalt-left-arrow previous tabalt-right-arrow next tab
24.1.9
Fixed¶
0.0.12
Notebook code is pandas code. A SignalFrame and a SignalSeries now answer to pandas' name for everything they do natively, and mean what pandas means by it, so a cell written the way you would write it for a DataFrame runs. A pandas name they don't have raises an error naming the .to_pandas() call that does.
Units convert, and a unit never lies: where the SDK cannot know a result's unit, the result has none, and the numbers are always pandas' numbers. On our notebook coding eval, production gpt-6-luna's first-attempt cells went from 73% correct to 95 of 95.
Added¶
- pandas' names on a series.
gt/lt/eq/…,isin,astype,fillna,replace,where,clip,shift(alsofreq=),rolling("1s"),resample("1s").mean(),describe(),quantile,idxmax,value_counts,reindex,asof,corr(spearman without scipy), andadd/sub/mul/divwithfill_value=. Masks combine with&,|,^and~, and a series slices by time:speed["+10s":"+20s"]. Read more. - pandas' names on a frame.
frame[label] = seriesandassign()add a column that keeps its unit.frame[mask],ilocandlocselect rows, andframe.iloc[-1]is one row.groupby,corr,index,dtypes,shape,to_numpy(),fillna(),rename(columns=)andSignalFrame.from_pandas()round out the hand-off. Read more. - Unit conversion.
power.to("kW"),energy.to("Wh"),current.integrate().to("Ah"), SI prefixes, and the units vehicle and battery catalogs use.V + mVreads inV. AdegCreading converts todegFwith its offset, and a difference of readings is aΔdegCthat converts by scale alone. runs()turns a boolean series into one row per event: start, end, duration, samples, and whether both ends were recorded.min_samples=andmin_duration=keep the long ones:(speed == 0).runs(min_duration="2s").rate()is the change per second between neighboring samples, in unit/s.- "Did you mean" when a name misses:
frame["pack_voltage"],catalog["motor_torque"], andcatalog.match("torque")all name what you meant. - numpy interop.
np.sqrt(series),np.isfinite(series)andnp.cumsum(series)return series on the same time axis, with the unit they imply. submit_text=relabels an action's Execute button:@action("Flash Firmware", submit_text="Flash").read_only=Truemarks an action that changes nothing, such as one that lists adapters. Zelos AI runs it without asking first:@action("Detect Adapters", read_only=True).zelos_sdk.agent_url()returns the agent URLinit()settled on, orNonebeforeinit().TraceLoggingHandlertakes an existingTraceSource, so log records land on the source an extension already owns.source_name=still works.TraceReaderreports each event'sevent_type.schemas.CanFrame, thezelos.can.frame.v1schema for undecoded CAN frames.- Batch emit and flush across the extension ABI, for producers that emit from a native thread.
Changed¶
frame.signalsis aSignalCatalog, the same objectagent.signals()returns, withkeys(),values()anditems(). Iteration,len(), and int and slice indexing are unchanged.frame.columnsis the column labels, as in pandas, and iterating a frame yields labels. The column metadata isframe.meta.- A missing sample compares as pandas compares NaN: unequal to everything, and neither above nor below anything. A NaN is a missing sample.
- Arithmetic between series on different time axes aligns them by timestamp, as pandas does, instead of raising. Comparisons still need one axis.
- A result whose unit cannot be known has none.
T * 2,-T,abs(T)andT.sum()of a temperature reading keep their numbers and drop the unit, and so does multiplying or dividing by a list or array. A number keeps the unit:power * 0.001is still inW, so use.to("kW"). - A derived name says what was computed. A number joins it (
v × 1000,torque < 0), anddiff,abs,cumsum,isna,notnaand negation name themselves. f"{v:.2f}"formats the bare number;f"{v}"andprint(v)still show the unit.- A boolean
sum()is anint, andseries == "RUN"compares sample by sample. plot()fits the y-axis to the data, as the app's plot panels do;zero=Truerestores the baseline. The legend sits on top and wraps long names.- A check on a reshaped frame (
ffill(),dropna(),resample()) evaluates the samples the frame holds, not the stored ones it was reshaped from. - Check artifacts from the CLI and the pytest plugin share one format,
schema_version3, and either loads back as a suite.
Deprecated¶
frame.column_names— useframe.columns.
Fixed¶
series == "RUN"returnedFalse, Python's identity fallback, instead of comparing samples.integrate()andderivative()computed in float epoch seconds, about 1 µs of resolution; they now work in nanosecond offsets.t - t.mean()of a temperature readdegC; it is aΔdegC.- A locally built frame showed no units in its repr, and
frame[signal]raised for its own signals. - A check on a CamelCase signal, which every DBC signal is, failed with "No field named".
- The pytest plugin failed to load on Python 3.10.
- A time in a named zone, such as a
TimestampinUS/Eastern, raised "time must be a datetime".
Upgrade notes¶
Most 0.0.11 code runs unchanged. Three spellings change meaning:
# 1. frame.columns is labels now; the metadata moved.
[c.signal for c in frame.columns] # before
[c.signal for c in frame.meta] # after
# 2. Iterating a frame yields labels; the Signals are in frame.signals.
for signal in frame: ... # before
for signal in frame.signals: ... # after
# 3. Units come back from pandas only when you name them.
SignalFrame.from_pandas(df, units=frame.signals)
The read side of the SDK — everything you do after the data is recorded — is now one surface. Everything you already wrote keeps working: every old spelling is kept as a deprecated alias that warns once. The behavior changes below are the exceptions, and each one replaces a result that was quietly wrong. Each of these still works and raises one Kept without a warning: the Three one-line fixes cover almost every migration: If you combine columns from different messages, add 0.0.11
connect() returns a handle that browses the catalog, queries live and recorded data, computes on unit-carrying series, charts, checks invariants, and re-opens .trz files, with one glob grammar and one time grammar across every call. It is the surface Zelos Notebooks run on.Added¶
connect() — one entry point for a running agent. It verifies the connection before returning, reads ZELOS_AGENT_URL when given no argument, and works as a context manager. Read more.SignalFrame — agent.query(paths, start=…) returns a frame that is one row per distinct timestamp, one column per signal, and null wherever a signal had no sample. ffill(), dropna(), between(), resample(), head(), rename(), short_names() and describe() operate on it directly. Read more.SignalSeries carries its unit through arithmetic. voltage * current arrives as W, .integrate() over time gives J, and power / current cancels back to V. Adding mismatched units raises instead of lying, and reductions return a NamedScalar that prints 3.629 V.plot() on a frame or a series emits Vega-Lite. It renders inline in the app, offline in an exported HTML page, and in any Jupyter viewer — with nothing added to dependencies.* and ? expand against the catalog everywhere a path is accepted: match(), query(), latest(), at(), window() and export()."-30s" is a point on the clock, "30s" is a length. ISO 8601 strings, datetime and timedelta are accepted wherever they fit.agent.check.that(lhs, op, rhs) reports a verdict instead of raising, so a suite always runs to completion. Every result carries its evidence — the first violating sample on a failure, the closest approach on a pass — with a timestamp. agent.check.count() measures without judging. Read more.CheckResults.raise_if_failed() raises AssertionError listing every check that did not pass, each with its predicate, window and evidence. For a single check, assert agent.check.that(...) — a result is truthy when it passed.agent.window() returns a ReplayWindow — the opening snapshot plus one entry per change — for states and events, where a query would return thousands of rows repeating one string.agent.watch() streams live values on an interval, with a deadline.agent.export() saves a window to a .trz file, and .open() re-reads it with the same calls you used against the agent.Agent, SignalCatalog, SignalFrame, SignalSeries, Snapshot, ReplayWindow, Trace and CheckResult all print a useful summary and render as a rich table in a notebook. to_pandas() is for handing data to pandas, not for looking at it.Changed¶
int passed where a time belongs raises below 1015 instead of being read as an epoch in nanoseconds. A number that small is nearly always seconds by mistake — write "-30s".datetime is accepted and read as UTC instead of being rejected.query() expand against the query window rather than a fixed 60-second catalog, so a glob over a long window matches what the window actually contains.latest() takes a wildcard. agent.latest("bus0/BMS_message/cells.*") returns a Snapshot of every match; one literal path still returns a single LatestValue.Trace.latest() / Trace.at() and the TraceSet pair return the live shapes — a LatestValue for one literal path, a Snapshot otherwise — and ReplayWindow.snapshot is a Snapshot rather than a list.TraceSet.query() on offset bounds puts the runs on one grid. Every sample is numbered by how many sample periods into its own run it sits, and the runs join on that number at the coarsest run's rate, so two runs recorded at different sub-second phases share rows instead of leaving each other's columns null. Only the edge of the window can leave a row unshared, when it holds one more sample of one run than of the other. to_pandas() on such a frame indexes by offset, not by a 1970 date.Trace.signals() and TraceSet.signals() come back sorted by path, and so do the columns of a Trace.query() frame.where() masks out values as nulls instead of NaN.to_pandas().count() returns a CheckResult whose .count is None on error, instead of 0 — a failed measurement no longer reads as a real zero.to_pandas() returns a timezone-aware UTC DatetimeIndex. Pass index=False for time as an ordinary column.producers= on query(), latest(), at() and window() defaults to every connected producer instead of localhost only. On check.that() / check.count() it stays the local store; a check reports one verdict per target.temporal="for_duration" / "within_duration") anchors at the end of the recording — its last N seconds — instead of the start.trace.check accepts lookback= for signature parity with agent.check and ignores it.zelos.check.result.v1 events.std() uses ddof=1 — the sample standard deviation, matching pandas.median() is exact instead of approximate.DataType.Int32 == "int32".voltage * current is voltage × current, and ÷, +, − follow; a chart legend never reads null.NamedScalar prints as a measurement: four significant digits and the unit (3.629 V), with f"{peak:.2f}" keeping the unit too. float(peak) is still the exact value.ExportResult and TraceSet.series() render themselves — one row per producer, one column per run.from zelos_sdk import * binds the agent TimeRange.window(duration=…) with a plain number means seconds, not nanoseconds.TraceSet.time_range is a TimeRange.watch() yields a Snapshot — a Mapping that renders itself — instead of a plain dict.AssertionError fails the notebook run (exit code 1); any other exception errors it.--param names must be ASCII Python identifiers — no keywords and no dunders — so a parameter is always usable as params.<name> in a cell. A bad name is refused before the run starts.dependencies: list when the notebook declares none.jsonschema and pyarrow. pytest, rich and colorama are no longer installed by default; pip install "zelos-sdk[test]" restores pytest and rich, and [notebook] adds the analysis stack (pandas, numpy).Deprecated¶
DeprecationWarning naming its replacement. Nothing is removed.
Old
New
Agent.connect()zelos_sdk.connect(target, timeout=…)
Agent.health_check()Agent.health()
Agent.resolve_paths()agent.signals().match(pattern)
SignalCatalog.by_path(path)catalog[path]
SignalCatalog.to_list()list(catalog)
SignalSeries.derive()SignalSeries.from_arrow(values, name=…, unit=…)
SignalSeries.get()SignalSeries.to_pandas()
SignalSeries.time_arrow()the
SignalSeries.time attribute
sort_order=sort="asc" / sort="desc"
relative_start= / relative_end=start="+30s" / end="+30s"
time_mode=nothing — the bounds pick the axis
export(paths=…)export(signals=…)
lookback= on query()nothing — wildcards resolve against the query window
[analysis] extra is an alias of [notebook];
TraceSet.traces and TraceSet.max_duration remain as properties beside the new
TraceSet.time_range; and every alphabetic and
natural-language operator spelling (gt, is greater than, is not equal to,
in, …) still parses to the same canonical operator as its symbol.Fixed¶
integrate() and derivative() refuse a series with nulls instead of guessing across a gap, and name ffill() / dropna() as the fix.Upgrade notes¶
# 1. to_pandas() already indexes by time.
frame.to_pandas().set_index("time") # before
frame.to_pandas() # after — or to_pandas(index=False) to keep the column
# 2. Look a signal up by path with an index.
catalog.by_path("bus0/BMS_message/status.pack_current") # before
catalog["bus0/BMS_message/status.pack_current"] # after
# 3. A relative time is a signed string, not a number.
agent.query(paths, start=-30) # before
agent.query(paths, start="-30s") # after
.ffill().dropna() — a frame is joined on time and sparse by construction, and integrate() / derivative() refuse a series with nulls rather than guessing across a gap.
0.0.10
Added¶
zelos_sdk.agent): Connect to a running Zelos Agent from Python and query live or recorded data through one handle — browse the signal catalog, query and downsample time-series into Arrow/pandas, read latest values, watch for live updates, run actions, manage extensions, and open .trz trace files. Read more.agent.layouts CRUD (list/show/create/update/delete) plus full version history — list a layout's versions, restore a previous version, or label one. Read more.agent.check.that(lhs, op, rhs) asserts a predicate against a live signal or .trz trace, with temporal quantifiers such as always/eventually evaluated server-side; agent.check.suite(...) runs a JSON suite of checks in one round-trip. Read more.zelos_sdk.schemas.Log — a predefined schema for typed log events (zelos.log.v1); pass a schema class directly to add_event(name, schema) instead of a field list. Read more.TraceSource.log_batch() — log a pyarrow.RecordBatch or Table directly for zero-copy, high-throughput ingestion.TraceSource.log_many() — log a list of (time_ns, event_name, data) tuples in a single call, cutting Python↔Rust overhead for high-frequency sources like CAN decoders.TraceSource(name, strict=True) — opt into strict type coercion; rejects lossy float→int narrowing instead of silently truncating it.TraceNamespace.drain() — flush every source and block until all queued events reach subscribers, for deterministic teardown ahead of closing a TraceWriter.Changed¶
ActionExecuteResult (value + status) with PASS/FAIL/ERROR/DONE states instead of a bare value; the old ActionResult/ExecuteStatus names remain as deprecated aliases.@action declaration order end-to-end instead of being alphabetized, so generated forms render fields in the order they were declared.Fixed¶
SignalFrame now resolves columns correctly when the same signal name appears across multiple producers or segments, raising AmbiguousSignal on a genuine tie instead of matching the wrong column.TraceReader no longer drops a trace's last row when its timestamp has sub-microsecond precision.TraceWriter now captures every event logged before its with block exits, without needing a sleep before close to avoid losing in-flight data.TraceSourceCache now stores a Python None as a null value instead of raising an error.
0.0.9
Added¶
FolderPickerWidget for Actions, enabling folder selection in extension UIspyarrow as a direct dependency — no longer needs to be installed separately for TraceReaderFixed¶
None instead of causing serialization errors
0.0.8
Added¶
.trz trace files for offline analysis, debugging, and post-processing. Read more.
0.0.6
Added¶
field_type, enabling richer, type-aware
rendering in the Zelos App without custom widgets.required=True, surfacing missing inputs during
validation instead of at execution time.Improved¶
0.0.5
Added¶
object.Changed¶
setuptools dependency.Fixed¶
TraceSegmentStart message was missed.TracePublishClient.