WeaveScope

Compose a Deep Agent

A deep agent is an ordinary agent with a workspace and capabilities for longer tasks: planning, files, skills, memory and delegation. There is no separate runtime or hidden orchestration service. Add only what the task needs.

Complete Installation . This example makes real OpenAI calls, reads supplied policy data and creates a note in thread state.

Run this page’s examples in order in one IEx session; later blocks reuse the modules and values defined earlier.

Plan, read and write

defmodule MyApp.SupportResearcher do
  use BeamWeaver.Agent

  model "openai:gpt-5.4-mini", max_tokens: 1_024, timeout: 60_000

  system_prompt """
  For a support investigation: write a task list, read the supplied policy,
  write a factual note, then mark the tasks completed. Do not claim a refund
  was paid. Use only the policy and customer facts provided.
  """

  filesystem BeamWeaver.Filesystem.State.new()
  middleware [BeamWeaver.Agent.Middleware.TodoList]
  recursion_limit 30
  run_timeout 180_000
end

saver = BeamWeaver.Checkpoint.ETS.new()
opts = [checkpointer: saver, config: %{"configurable" => %{"thread_id" => "investigation-A-1001"}}]

{:ok, result} =
  MyApp.SupportResearcher.invoke(
    %{
      messages: [
        "Investigate broken mug order A-1001. Read /policies/refunds.md and save a note at /notes/A-1001.md. Complete your task list."
      ],
      files: %{"/policies/refunds.md" => "Damaged goods are eligible for a full refund after staff approval.\n"}
    },
    opts
  )

true = result.todos != []
true = Enum.all?(result.todos, &(&1.status == "completed"))
note = result.files["/notes/A-1001.md"].content
true = note =~ "A-1001"
true = String.downcase(note) =~ "refund"
true = Enum.any?(result.messages, &(&1.role == :tool and &1.name == "read_file"))

Inspect artifacts and state, not just the final answer. A model can say it saved a note without doing so; the checks above read the actual file and plan. The policy is a local example dataset, not a live business policy service.

One agent, more capabilities

Capability Declaration or middleware Guide
Task list TodoList Below
File tools filesystem Filesystem
Path rules filesystem_permissions Permissions
On-demand procedures skills Skills
Short persistent instructions memory Memory Files
Delegated tasks subagents, async_subagents Subagents
Context policy compact_conversation, overflow_recovery Context Management
Commands An executable filesystem or ShellTool Sandboxes
Approval interrupt_on Human Review
Model tuning capability_profile Capability Profiles

Each capability adds tools, state or prompt instructions. More capabilities mean more model-visible choices and more possible side effects.

Plan with a task list

TodoList exposes write_todos. Each call replaces the entire list; items have content and pending/in_progress/completed status. Concurrent task-list writes in one model turn are rejected so the model can submit one coherent update. The default state key is todos.

Options include state_key:, tool_name:, tool_description: and system_prompt:. A plan is model-maintained progress, not independent evidence that each task happened. Verify the promised outputs separately.

Use a different state key when your application calls the list a plan. The agent must write the completed task:

defmodule MyApp.PlanningAgent do
  use BeamWeaver.Agent
  model "openai:gpt-5.4-mini", max_tokens: 512, timeout: 60_000
  system_prompt "Record the requested tasks with write_todos before replying."
  middleware [{BeamWeaver.Agent.Middleware.TodoList, state_key: :plan}]
end

{:ok, plan_result} = MyApp.PlanningAgent.invoke(%{messages: ["Record one completed task: Read supplied policy."]})
[%{content: content, status: "completed"}] = plan_result.plan
"Read supplied policy" = String.trim_trailing(content, ".")

Keep the plan and files between calls

{:ok, snapshot} = MyApp.SupportResearcher.get_state(opts)
true = snapshot.values.files["/notes/A-1001.md"].content == note
true = snapshot.values.todos == result.todos

State-backed plans/files are per-thread and follow checkpoint durability. This example's ETS data disappears with its owner process. Shared data needs a persistent filesystem or memory-store namespace; restart recovery needs a database saver. See Storage .

How capabilities combine

Capability middleware is assembled before explicitly listed middleware. The declared order covers skills, filesystem, subagents, compaction/overflow, async delegation, tool selection, prompt caching, memory and human review. An explicit middleware entry usually replaces the same capability module; missing options can inherit declaration settings. Tool-selection exclusions are cumulative rather than replaced.

DynamicPrompt supplies the base prompt before capability sections are added. When you customize prompt or middleware assembly, inspect the actual request and ensure required policy sections still reach the model.

Skills and memory can read state files without a filesystem declaration, but the model needs file tools to open skills or edit notes. Declare a filesystem when those actions are part of the task.

Context management

Offload large tool results into files, retain a recent conversation window and summarize older material. Use overflow recovery as a fallback when a provider still rejects an oversized request. Keep source material retrievable: a summary is not a lossless archive. Context Management shows an actual compaction trigger.

Code execution

An executable backend adds execute. LocalShell and local sandboxes run as your application user; file-tool permissions do not constrain shell commands. Use a suitable isolated runtime and explicit command policy for the work you allow. Sandboxes covers lifecycle and boundary differences.

Human review

Review file writes, commands or domain actions with interrupt_on when the application requires approval. Pauses need checkpoint storage. Nested review propagates to the parent, which resumes through the same thread. Approval must be tied to the actual proposed arguments, not a narrative plan.

Subagents

Delegate a bounded question with a clear output contract. A synchronous child returns a final result to the parent; remote async tasks require explicit status/result handling. Include the relevant source data in the task and check the result before using it. Subagents explains shared files, context, captured outputs and review inheritance.

Watch a long run

Stream tool events and graph updates to show work in progress. Plan updates come through state changes; file operations and delegation have tool events. A completed task-list item does not guarantee the workflow completed. Handle terminal errors and interrupts, and use Tracing to inspect provider attempts, tools and child runs afterward.

Collect a new investigation stream once and inspect real tool progress and final saved artifacts:

stream_opts = [checkpointer: saver, config: %{"configurable" => %{"thread_id" => "streamed-investigation"}}, live: true]

{:ok, stream} =
  MyApp.SupportResearcher.stream_events(
    %{
      messages: [
        "Investigate broken mug A-1002. Read /policies/refunds.md, write /notes/A-1002.md, and complete your task list."
      ],
      files: %{"/policies/refunds.md" => "Damaged goods qualify for a full refund after staff approval."}
    },
    stream_opts
  )

events = Enum.to_list(stream)
true = Enum.any?(events, &match?(%{event: %BeamWeaver.Stream.Events.ToolFinish{}}, &1))
{:ok, saved} = MyApp.SupportResearcher.get_state(Keyword.delete(stream_opts, :live))
true = saved.values.files["/notes/A-1002.md"].content =~ "A-1002"

Test a deep agent

For application tests, script model actions and assert actual files, task state, delegated outputs and tool arguments. Keep representative live checks for model behavior. Testing covers deterministic setup; Examples lists complete larger projects.