AI Agent
This Python application is a general, long-running AI Agent. It can plan work for review, chat with a user, call trusted MCP tools, wait for approval before external writes, and suspend itself with a durable timer.
Dex owns the conversation state. The Agent rebuilds the model input from durable state for every call, so it does not depend on an LLM provider's conversation API.
Flow design
This interactive definition graph is generated from the runnable Python Flow.
Durable context
The application stores each user, assistant, and tool message as a separate AttributeMap instance. A small Attribute tracks sequence ranges, pending tools, and the active context summary.
When the model context reaches 85 percent of its configured window, a Step summarizes older messages. The model receives the system prompt, the cumulative summary, and the recent unsummarized messages. The current AttributeMap keeps the latest 2,000 messages by default.
config = Attribute("AgentConfig", AgentConfig)
state = Attribute("AgentState", AgentState)
summary = Attribute("ContextSummary", ContextSummary)
messages = AttributeMap("AgentMessages", AgentMessage)
plan = Attribute("AgentPlan", AgentPlan)
pending_approval = Attribute("PendingApproval", PendingApproval)
pending_timer = Attribute("PendingTimer", PendingTimer)
queued_user_messages = Channel("QueuedUserMessages", UserMessage)
steered_user_messages = Channel("SteeredUserMessages", UserMessage)
tool_approvals = ChannelMap("ToolApprovals", ToolApproval)
plan_executions = ChannelMap("PlanExecutions", PlanExecutionRequest)
reasoning_summary = Stream("ReasoningSummary", str, 10 * 1024 * 1024)
assistant_text = Stream("AssistantText", str, 10 * 1024 * 1024)
agent_activity = Stream("AgentActivity", AgentEvent, 10 * 1024 * 1024)
Example: examples/python/dex_examples/products/ai-agent/ai_agent_flow.py
Each map instance can be loaded and updated independently. This avoids rewriting one ever-growing conversation value. Dex blob storage and the Worker BlobCache handle large messages and MCP results.
Queued messages and Steer
User messages enter the durable QueuedUserMessages FIFO queue. While the Agent loop is running, they remain pending instead of interrupting the current model or tool call. The UI shows them as soon as they are accepted and lets the user edit, delete, or Steer a message while it is still pending. Editing removes the pending message first; resubmitting appends a new message with a new ID at the tail.
Steer sends only the selected message ID. Its transactional RPC explicitly loads QueuedUserMessages, finds the original Value, stages its deletion, and publishes that Value to SteeredUserMessages. If another operation consumes the message first, Dex commits neither effect. The Agent checks the steered queue before its next model call, tool, approval wait, or Timer continuation. It does not cancel an LLM or MCP request already running. At the next safe boundary, the Agent clears unexecuted tool calls and stale approval or Timer state, records structured cancellation results, and replans from the Steer message.
The message queue is not chat history. Pending Channel messages can be changed because the Agent has not consumed them. Once consumed, the user message becomes durable conversation history and its message ID is no longer valid.
The browser loads application state through one snapshot endpoint. One read-only RPC explicitly loads AgentMessages, QueuedUserMessages, and SteeredUserMessages, then returns the conversation, Agent description, run ID, and both queues from the same invocation. The browser refreshes after every mutation, when a live event arrives, when the page regains focus or connectivity, and every eight seconds as a fallback.
Durable plans
Turn on Plan mode for a message to ask the Agent to create or revise a plan without executing it. The planning call can only use the built-in write_todos tool. It cannot call MCP tools or start a durable wait.
The current plan is a regular Attribute. One update atomically replaces its ordered task list and advances the revision. Tasks are pending, in progress, or completed. Because the plan is independent of conversation messages, context compaction does not remove it.
The UI displays the draft and starts execution only after the user selects Execute plan. An active plan that stops with unfinished tasks remains incomplete and can be continued. The Agent waiting for another input does not mean that every plan task is complete.
MCP tools and approvals
The Worker loads trusted stdio or Streamable HTTP MCP servers from local configuration. The Agent Portal can enable registered servers and tools for a new session, but it cannot register an MCP command, URL, or credential.
Read-only tools can run immediately. Write, destructive, and unclassified tools wait on a durable approval Channel. Tool timeout and retry policies are configurable per server and per tool. A final failure becomes a tool result so the model and user can decide what to do next.
The example supports tools, resources, resource templates, prompts, progress, and logging. Server-initiated sampling, elicitation, and roots are not enabled.
Durable waits
The built-in durable_wait tool moves the Flow to a Step that waits on both a Timer and SteeredUserMessages. The Timer can survive Worker restarts. A queued message remains queued; choosing Steer interrupts the wait and lets the Agent replan.
Live events
Reasoning summaries and assistant text use separate buffered Streams. The Step passes both buffered writers to the model adapter. The SDK combines small chunks for up to one second or 16 KiB and flushes each tail before the Step result or error.
OpenAI models use LiteLLM's Responses adapter. Each request asks for an automatic reasoning summary but does not use provider-side conversation storage. Dex retains encrypted OpenAI reasoning items with the durable assistant message and replays them as stateless model context. The UI shows official reasoning-summary deltas under Thinking, visible output under Response, and tool calls or lifecycle progress under Agent activity. Other providers do not show a Thinking panel unless their adapter supplies a real reasoning-summary event.
Tool progress, timer state, and compaction status use a separate Stream. Stream delivery is best effort. Durable chat history always comes from Attributes, so refreshing the browser does not depend on replaying every progress event.
Run the example
The Flow and HTTP routes are under examples/python/dex_examples/products/ai-agent. The React UI and MCP configuration example are under examples/python/ai-agent.
The application opens on an Agent Portal. Choose a configured LiteLLM provider and model, then select registered MCP servers and tools before starting the Agent. Providers without their required environment variable remain visible but cannot be selected. Add credentials to examples/.env and restart the Python examples. The Portal never sends credential values to the browser or writes them to Dex state or Flow history.
The local playground starts credential-free search, Slack, and Google Docs demo MCP servers. The chat page separates Thinking, Response, and Agent activity while a call is running. Use Command/Ctrl+Enter or Alt+Enter to send a message. When work needs user input, the built-in request_user_input tool expands a durable input panel in the conversation. Known answers appear as selection buttons; open-ended questions use a text box. Execution waits for the submitted answer before continuing.
The browser page is the conversation scroll surface. The queue, user-input request, and composer stay fixed at the bottom while new activity remains visible above them. The queue panel collapses to a compact status row when empty and expands automatically when a pending message appears. Messages submitted during active work appear there as Queued. The UI keeps a submitted item visible until the Server reports it as pending or the Flow consumes it into history. Use Edit or Delete before consumption, or Steer to apply one at the next safe boundary.