v2.0.0b3
Pre-release
Pre-release
A hardening release: no new surface, sturdier runtime. Cancel a run from
another process, answer an approval from either of two servers without the
workflow running twice, and keep telemetry from growing memory or losing
events behind a wedged endpoint. Every guarantee here is enforced by the
event store itself rather than by in-process locks, so it holds when a
second worker joins. The v1 public surface remains byte-for-byte unchanged.
Added
- Run control (
agentdeck.core.ports.control,agentdeck.adapters.control): a
ControlPortfor cross-process cancel signals, backed by an in-memory adapter
for dev/tests and a SQLite-backed one durable enough for a second OS process to
reach a run it never held a reference to. The OpenAI Agents engine checks a
cooperative gate between stream items and stops cleanly on cancel, emitting a
singlerun.cancelledand leaving a truncated-but-coherent replay behind (no
message.completedfor the interrupted message). Newagentdeck runs signal <run_id> cancel --control-db <path>CLI command to send that signal from a
second terminal. EventStorePort(not yet part of any stable public API) gains focused
queries alongside its whole-log reads:last_seq(a run's highest
recordedseq),run_status(one run's status, derived from its own
events),list_runs(every run for a tenant, optionally filtered by
status), and pagination (offset/limit) onread. Both the memory and
SQLite stores implement all four; the SQLite ones use the existing
run/log indexes.EventStorePort.claim_resume(not yet part of any stable public API): a
conditional append that recordsrun.resumedonly if the run is still waiting
on a human answer and the event'sseqis still the run's next one, as one
indivisible step, and reports whether it won. The memory store gets that for
free; the SQLite store does it in a singleBEGIN IMMEDIATEtransaction, so
the events file itself picks the winner.
Changed
- Internal: the v2 event-log port (not yet part of any stable public API) is
now namedEventStorePortinstead ofSessionStorePort, to avoid confusion
with the OpenAI Agents engine's own session-scoped storage. No behavior
change and nothing outside the package imports this port. - Internal: the Runtime's resume path and the
/pendinglisting now use
EventStorePort's focused queries instead of folding a whole log to answer
one run's status or find waiting runs. Same results, much less work per call:
a resume deserializes only its own run's events instead of the whole session's
(22 instead of 4,400 on a 200-run session), and the pending listing is one
indexed statement returning each run's last lifecycle event — one event parsed
per run instead of every event of every log (4.2 ms instead of 32 ms for the
same 201 runs). - Event sinks are now fed from a bounded queue with one worker each, instead of a
fresh task per event per sink. A wedged sink (telemetry endpoint down, audit
store backpressured) now costs a fixed backlog and one task rather than growing
memory for as long as the process runs. A run still never waits on a sink: when a
sink's queue is full its stalest event is dropped rather than the run delayed — but
only once the sink has been given a turn to catch up, so a sink that is keeping up
loses nothing however fast the run produces events. Dropped events and failed
emits are counted per sink and reported in the logs — never discarded silently, and
never one stack trace per event — and a sink that raises five times in a row is
disabled instead of being retried for the rest of the process's life. Two side
effects worth knowing: each sink'semitis now called one event at a time in
submission order and is never re-entered, andRuntime.drain()flushes the queues
and stops the workers. Sinks remain a lossy tap by design; a consumer that must
see every event reads the event store, which is the complete copy.
Removed
EventStorePort.list_log_keys(not yet part of any stable public API), along
with the log-by-log pending scan that was its only caller.list_runsanswers
the same question without enumerating logs first.
Fixed
- Duplicate-resume protection now holds between processes, not just between tasks
in one process. Two servers (or a server and a second tool) sharing one SQLite
event store can answer the same interrupt at the same instant and exactly one of
them resumes the run; the other is a clean no-op, not an error. Previously the
guard was a process-local lock, so each process could claim the same waiting run
— running the workflow's next node twice and writing tworun.resumedevents
with the sameseq. A claim that was slow enough to miss a whole
interrupt-resume-interrupt round of its run now loses too, instead of answering
the run's second question with the first one's value. - The OpenAI Agents engine no longer runs the SDK's default trace exporter on
keyless/fake-model runs (tests, CI, the M0 demo): it now passes aRunConfig
with tracing disabled unlessAGENTDECK_OPENAI_AGENTS_TRACING_ENABLED=trueis
set, so a bare checkout no longer logs a non-fatalTracing client error 401
or attempts an unsanctioned outbound HTTPS call.