Skip to content

Dex agents 0.4.0

Choose a tag to compare

@github-actions github-actions released this 10 Nov 11:24
60efebd

Connector tools

  • Python and TypeScript cores: ConnectorToolSource offers reviewed Connector operations to a Stepwise agent as tools (dex_agents.connectors, extra dex-agents[connectors]; @superdurable/dex-agents/connectors, optional peer @superdurable/connectors-sdk). The application names each operation with connector_tool (connectorTool). Nothing is exposed by default, and an operation whose dex-connector.json entry lacks agent.exposed: true is refused when the agent is defined. Tool name, description, and argument schema come from the manifest.
  • Effect classes: a Query is READ in CallTool; a Mutation with agent.retry: safe is IDEMPOTENT_WRITE in CallTool; any other Mutation is NON_IDEMPOTENT_WRITE in CallSingleAttemptTool. A call's tool Step gets the operation's Execute timeout and retry policy, at most three attempts. A session tool_policies entry changes only approval.
  • Approval: agent.approval: recommended asks by default; other Mutations run without approval. requires_approval (requiresApproval) overrides it with a bool or a per-call predicate over the arguments.
  • Arguments are checked against inputSchema before decoding. Branches map through agent.branches: success is succeeded, rejected and defect are failed, uncertain, unknown, and unlisted Mutation branches are outcome_unknown, and unlisted Query branches are failed. Results are canonical JSON, cut at 64 KiB by default with a note.
  • A single-attempt write that asks to retry records outcome_unknown, unless its single-dispatch fence saved notSent or canResend in that attempt; then it records failed with "not sent; the provider asked to retry later".
  • Connector writes of one model response run one at a time in the model's order. Once one is not succeeded, the later ones record failed with the new AgentToolCall.skipped marker.
  • Needs Connector SDK 0.9.0 (dex-connectors-sdk==0.9.0, @superdurable/connectors-sdk 0.9.0), which exports raise_dex_retry and throwDexRetry.
  • Every Python and TypeScript framework adapter takes tool sources too; see Tool sources in framework adapters.
  • Go core: no connector tools. Its AgentToolCall carries the four new fields, so a record written by another core round-trips.
  • See Connector tools.

Tool call contract additions

  • DeferredToolExecution.context: the tool Step's live context, never stored.
  • ToolOutcomeUnknownError: a tool records outcome_unknown and does not retry.
  • DeferredToolCall.ordered, and AgentToolCall fields ordered_write, write_queued, skipped, and truncated_output_bytes, all defaulting to off. Records written by 0.2.0 decode unchanged. Dex Web reads skipped, and the "the tool did not run" error prefix of older records.
  • ToolExecutionResult.truncated_output_bytes, ToolCallStepOptions, and the optional runner method tool_call_step_options (toolCallStepOptions), which StepwiseAgentRunner routes to a tool source.
  • RunModel may move to itself once, to commit skipped writes before the model call, so the session graph gains that transition.

Tool sources in framework adapters

  • Every Python and TypeScript framework adapter takes tool_sources (toolSources), such as ConnectorToolSource, and tool_source_listing_timeout (toolSourceListingTimeoutMs), default 10 seconds. Each RunModel lists every source and offers its tools with the source's name, description, and JSON Schema. A source whose listing fails or times out offers no tools in that model call.
  • Every call of a source tool, reads included, runs once in its own tool Step through the source's execute_deferred_tool_call (executeDeferredToolCall), with the Step's context, heartbeat checkpoint, and attempt, so a connector call keeps its Call ID and fence. The source builds the call from the final input, so it decides the effect class, the approval, and the write order. tool_call_step_options (toolCallStepOptions) gives the tool Step the source's timeout and retry policy. A tool_policies entry for a connector tool changes only its approval.
  • Definition fails when two sources share a source_name (sourceName), or a name a source declares repeats an agent tool, a built-in or control tool, or another source's tool. The same names listed at run time fail that RunModel attempt. Mastra resolves agent tools per request, so it checks them at RunModel.
  • The Claude Agent SDK adapters, the TypeScript OpenAI Agents SDK adapter, and the Python Step-mode adapters keep a source tool that the turn already called but the current listing lacks as replay-only: its recorded call resumes, and a new call of it is refused inside the turn, so it never starts a tool Step under a new Call ID. A recorded result replays once, only under the tool name it was recorded for.
  • The framework adapters poll no tool tasks: a source call that starts a task records failed for a read call and outcome_unknown otherwise. In the TypeScript adapters, a source call that asks a question records failed.
  • Python and TypeScript Claude Agent SDK: ClaudeAgentRunner and claude_agent_session_flow (createClaudeAgentSessionFlow) take the tool sources, and the in-process custom tool server offers their tools after the custom tools. Source tools are served without MCP annotations, so the CLI runs them in the model's order; a later write of one reply is refused inside the turn, not recorded as skipped. An allowed_tools (allowedTools) entry for a source tool fails the attempt, because its calls must defer.
  • TypeScript Vercel AI SDK, OpenAI Agents SDK, and Mastra: createVercelAiAgentSessionFlow, createOpenAIAgentsSessionFlow, createMastraAgentSessionFlow, and MastraAgentRunner take toolSources. The OpenAI Agents SDK adapter offers source tools to every agent of the graph as non-strict function tools, and maps names the SDK rewrites back to the source's name. Mastra offers them as client tools without execute.
  • Python OpenAI Agents SDK, Pydantic AI, LangChain and LangGraph, and Google ADK: OpenAIAgentsRunner, PydanticAIRunner, LangGraphAgentRunner, and AdkAgentRunner and their session flow functions take tool_sources. Pydantic AI passes source tools as an ExternalToolset. The OpenAI Agents SDK adapter adds a gated function tool for each tool of a source's cached_tools() when it is built, so a source without cached_tools offers nothing; ConnectorToolSource caches its whole listing. LangGraph's DurableToolCallMiddleware adds the tools to each create_agent model request; a custom graph binds the new get_current_tool_source_tools() in its model node. Google ADK appends one SourceToolset to the tools of every LlmAgent of the tree when the runner has sources; it lists nothing outside the runner's RunModel.
  • TypeScript core: ToolSourceRouter, ToolSourceRouterOptions, ListedSourceTool, and defaultToolSourceListingTimeoutMs are exported from the package root, with ToolSource and SourceToolDefinition. The Stepwise runner and the Vercel AI, OpenAI Agents, and Mastra adapters use it.
  • Python core: dex_agents.stepwise adds FrameworkToolSourceRouter, SourceToolListing, FRAMEWORK_TOOL_SOURCE_ENVELOPE_KEY, and SOURCE_TOOL_NOT_OFFERED_RESULT for the Step-mode adapters.
  • Stored values: a source call stores its source's name in tool_source_reference under an adapter key. The Python Step-mode adapters use the new key dex_tool_source. Keep each source_name stable across deploys, because stored calls are routed by it.
  • See Connector tools.

Claude Agent SDK: replay of recorded tool calls

  • Python and TypeScript: an approved Claude Code tool, such as Bash, resumes with its recorded arguments as updatedInput: the input the application's hooks produced and the approval saw, not the model's original input. can_use_tool (canUseTool) answers a resumed call the same way.
  • A recorded call replays only as its own tool, with the same name and kind (a custom or source tool, or a Claude Code tool), at most once per segment, and only while the stored transcript has no result for it. Another tool, a second replay, or a call whose ID an earlier segment already resumed is denied and never runs.
  • Each tool use ID gets one PreToolUse decision per segment: allow, deny, defer, or a replay. A later PreToolUse for that ID is denied. A can_use_tool ask after a hook allowed the call is denied.
  • No stored value changes.

Changed

  • Python and TypeScript Claude Agent SDK: the application's PreToolUse hooks are not called for a resumed call that already has a final record, so a later deny cannot hide its recorded result. The call passed them when it was deferred.
  • TypeScript Claude Agent SDK: a call that the CLI deferred but the adapter's hook did not defer fails the segment, which then retries, instead of being recorded. Python already did this.
  • TypeScript Claude Agent SDK: the in-process custom tool server is built with @modelcontextprotocol/sdk, a new direct dependency at ^1.29.0, the range the Claude Agent SDK asks for. The custom tool listing is unchanged. Argument validation errors of inline custom tools now come from that SDK.
  • TypeScript Mastra: the new mastraRunToolNames names the tools Mastra adds itself, such as agent-researcher, that may run inside RunModel. With toolSources or mastraRunToolNames, a toolPolicies key no longer allows such a tool by itself. Without either, nothing changes.
  • TypeScript Vercel AI SDK: the toolPolicies type accepts any string key, so a source tool can have a policy. Without toolSources, a key that names no tool still throws TypeError.
  • Python LangGraph: a model tool call without a tool call ID fails the RunModel attempt, because the session records each call by its ID.

Full changelog: agents/CHANGELOG.md. Changes since 0.3.0: agents/v0.3.0...agents/v0.4.0