Skip to content

Dex agents 0.3.0

Choose a tag to compare

@zzheng-aiplay zzheng-aiplay released this 09 Nov 22:27
95f5616

Dex SDK 1.38.0

  • Every package requires Dex SDK 1.38.0 (dex-python-sdk==1.38.0, @superdurable/dex 1.38.0, Go sdk-go v1.38.0) and a Dex Server with protocol 4: server-zd v0.1.17 or later, as with 1.35.0.
  • Agents CI, the nightly long run, and the publish workflows test against dexcli 1.38.0.

The session graph for Dex Web

  • Python and TypeScript cores: render_session_flow_graph (renderSessionFlowGraph) and the dex-agents-session-graph command render the Flow Definition Graph 2.0 file of an agent's session Flow, with the application's Flow type and Action permission. Dex Web loads it with --flow-rendering-dir, draws the session Steps, and offers resolve_interrupt, answer_question, steer_user_message, and cancel_turn as Actions without a condition. Both cores write the same bytes.
  • The graph is derived from the Python core's registered Flow and source and ships in both packages. The conformance suite adds session_graph.json, and each core's tests fail when the graph and the session Flow disagree.
  • Dex Web needs a release that accepts Actions without a condition and calls no RPC for a view without fields.
  • Go core: no graph. It registers only part of the session Flow.
  • See Show sessions in Dex Web.

Compaction in framework adapters

  • The OpenAI Agents SDK (Python and TypeScript), Pydantic AI, LangChain and LangGraph, Google ADK, Vercel AI SDK, and Mastra adapters implement the compaction methods, so AgentSessionOptions.compaction works with them. A session compacts with the existing CompactContext Step and stores a ContextSummary, so retention can delete messages behind it. Without a policy nothing changes, and a session started on 0.2.0 starts compacting once its Workers have one.
  • Adapters compact only between turns, before a turn's first model call. A model call that resumes a turn after its tool calls never compacts, because the OpenAI Agents SDK and LangGraph resume from a run state that holds the input.
  • The adapters' session flow functions take compaction, retained_message_limit, and summary_model (summaryModel). LangGraph needs summary_model, because a compiled graph does not expose its model. The TypeScript OpenAI Agents and Mastra session flow functions now forward compaction and the message storage options to the core.
  • Google ADK: the summary ends with a line that carries the ADK session state and the agent that replied last, so the rebuilt session keeps both.
  • Claude Agent SDK: unchanged. Claude Code compacts its own transcript, and the adapter takes no compaction policy.
  • Python and TypeScript cores: new helpers for adapters, estimate_text_tokens, estimate_stored_input_tokens, select_turn_boundary_cutoff, describe_stored_message, and build_context_summary_prompt (camelCase in TypeScript). The Stepwise summary texts move to the core's root module and stay exported from Stepwise.
  • CompactionPolicy.reserved_input_tokens (reservedInputTokens), default 0, adds tokens the runner's estimate cannot see.
  • dex_agents.testing and @superdurable/dex-agents/testing add ContextWindowModelScript and ContextWindowSessionScenario.
  • No stored value changes. The conformance suite adds the summary prompt texts.
  • Go core: unchanged.
  • See Compaction and the storage bound.

Spend limits

  • Python and TypeScript cores: the new ModelSpendLimit interface is an application's spend limit. Pass it as AgentSessionOptions.spend_limit (spendLimit), or on a Stepwise AgentDefinition. The session calls check_model_call before each attempt of RunModel and CompactContext, and report_model_usage when the attempt's model call returns. Without one, nothing changes. Counters and limits belong to the application.
  • A refused call ends the turn. AgentState.last_turn_error starts with the new MODEL_CALL_REFUSED_ERROR_PREFIX (modelCallRefusedErrorPrefix), then the reason. The session keeps running and checks again on the next message.
  • A check that raises or takes longer than SPEND_LIMIT_CALL_TIMEOUT, 30 seconds, fails closed: it fails the attempt, and the Step retries. A report that raises is logged and dropped. ModelUsageReport.report_id is stable per attempt.
  • Python Claude Agent SDK adapter: claude_agent_session_flow takes spend_limit. A harness is checked once per turn segment, and its usage arrives when the segment ends.
  • No stored value changes. The conformance suite adds the refusal prefix.
  • Go core: unchanged. It does not check spend limits.
  • See Spend limits.

A configurable RunModel limit

  • Python and TypeScript cores: AgentSessionOptions.run_model_limit (runModelLimit) sets the method timeout and attempts of RunModel, with the new RunModelLimit. The retry budget moves with them: the method timeout times one less than the attempts, and at least twice the method timeout. The default stays 15 minutes and 3 attempts in 30 minutes. CompactContext and the heartbeat timeout do not change.
  • Python Claude Agent SDK adapter: claude_agent_session_flow takes run_model_limit. The TypeScript adapter takes it through its session options. A turn segment that needs more than 15 minutes can now finish. A failed segment reruns whole.
  • No stored value changes. A running session gets the limit on its next RunModel after the Worker restarts.
  • Go core: unchanged.

Sessions are searchable by the Step they wait in

  • Python core: AgentSessionClient.start_session sets FlowConfig.active_step_search_mode to the new AGENT_SESSION_ACTIVE_STEP_SEARCH_MODE (WITH_WAIT_FOR), so ActiveStepTypes = "AwaitToolApproval" and "AwaitQuestionAnswer" find a session while it waits and drop it when it moves on. A mode set in options.config_override wins, including DISABLED.
  • TypeScript core: the new startAgentSession(client, flow, sessionId, input, options) does the same, with the new agentSessionActiveStepSearchMode. Until this core supports a Dex SDK with ActiveStepSearchMode.WITH_WAIT_FOR, the value is ActiveStepSearchMode.DEFAULT, which the server treats the same way. AgentSessionTestClient.startSession calls it.
  • Client.startFlow and AsyncClient.start_flow are unchanged, so a session they start stays out of this search. So do sessions started on 0.2.0, until UpdateFlowConfig turns it on. No stored value changes.
  • Go core: unchanged.
  • See Find sessions that need a person.

A session at the state limit fails, with the reason

  • Python and TypeScript cores: when a Step fails on limits.maxFlowStateBytes, the session ends. RecordTurnFailure, RecordToolFailure, and RecordToolOutcomeUnknown recognize the recovery error (ValueTooLarge with the text run state of <X> bytes exceeds the maximum of <Y> bytes, or active run state of ... from a server that counts only active state), set the new AgentState.status value failed with that text in last_turn_error, and close the Flow FAILED with the same text in the same commit. A session no longer goes on to pay for model calls whose answers cannot be stored. A value over values.maxValueBytes still fails only its turn.
  • That commit is checked against the same limit, and it can fail too. The Flow then closes FAILED with the text anyway and AgentState keeps its last value, which is also what happens for a Step with no failure route, such as AwaitUserMessage. AgentSessionClient.get_agent_snapshot and the new read_agent_snapshot (readAgentSnapshot) read the Flow's close, so they report failed and the reason for any session that stored a state, for one more call to Dex. A session that fails before it stores a state keeps a snapshot with no state. The get_agent_snapshot RPC alone reads only what was stored.
  • UI streams: the event SSE generators end once the Flow failed and every event was sent, instead of sending keep-alives forever. A TypeScript UI message stream that finds the Flow failed ends with an error part with the reason and finishReason error. AgentSessionUIMessages has a new failure field.
  • New messages are refused. AgentSessionClient.send_user_message and steer_user_message, and the new sendAgentUserMessage, raise the new AgentSessionFailedError, a FlowNotActiveOrNotFoundError with reason. A direct RPC call still gets FlowNotActiveOrNotFoundError.
  • Queued messages never run. They stay readable in AgentSnapshot.queued_messages until retention deletes the session.
  • Go core: reads failed and keeps its behavior. It does not end a session at the limit yet.
  • See Upgrade agent code while sessions wait for the order of a rolling deploy.

Full changelog: agents/CHANGELOG.md. Changes since 0.2.0: agents/v0.2.0...agents/v0.3.0