An agent is a module that does use Condukt. Each agent runs as its own
GenServer backed by Condukt.Session, which manages the conversation
history, the tool loop, and any optional features (sessions, compaction,
redaction).
The behaviour
use Condukt exposes the following optional callbacks, all with defaults:
| Callback | Default | Purpose |
|---|---|---|
system_prompt/0 | nil | Static system prompt for the agent. |
tools/0 | [] | List of tool modules, {module, opts} tuples, or inline tools. |
model/0 | "anthropic:claude-sonnet-4-20250514" | ReqLLM provider:model identifier. |
thinking_level/0 | :medium | One of :off, :minimal, :low, :medium, :high. |
init/1 | identity | Called with the keyword opts at startup. |
handle_event/2 | no op | Receives events as they happen during a run. |
You only override what you need:
defmodule MyApp.ResearchAgent do
use Condukt
@impl true
def system_prompt do
"You are a careful research assistant. Always cite sources."
end
@impl true
def tools do
[Condukt.Tools.Read, Condukt.Tools.Bash]
end
@impl true
def model, do: "anthropic:claude-sonnet-4-20250514"
endStarting an agent
{:ok, agent} =
MyApp.ResearchAgent.start_link(
api_key: System.fetch_env!("ANTHROPIC_API_KEY"),
cwd: "/path/to/project"
)Resolution order for configuration is:
- Options passed to
start_link/1 config :condukt, ...- Module callback defaults
Common options
MyApp.ResearchAgent.start_link(
api_key: "sk-ant-...", # Provider key
model: "anthropic:claude-sonnet-4-20250514", # ReqLLM model id
base_url: "http://localhost:11434/v1", # Override provider URL
system_prompt: "You are helpful.", # Static prompt
thinking_level: :medium, # Thinking budget
load_project_instructions: true, # See Project Instructions guide
cwd: "/path/to/project", # Tool working directory
session_store: Condukt.SessionStore.Memory, # See Sessions guide
compactor: {Condukt.Compactor.Sliding, keep: 40}, # See Compaction guide
redactor: Condukt.Redactors.Regex, # See Redaction guide
name: MyApp.ResearchAgent # GenServer name
)Public API
For a running agent process, the Condukt module forwards these calls to
Condukt.Session:
Condukt.run/3runs a prompt to completionCondukt.stream/3returns a lazy stream of eventsCondukt.history/1returns the current conversation historyCondukt.clear/1clears historyCondukt.abort/1aborts the current operationCondukt.compact/1runs the configured compactorCondukt.steer/2injects a message mid run, skipping remaining tool callsCondukt.follow_up/2queues a message to be delivered after the current run
Condukt.run/2 also accepts a prompt as the first argument for anonymous
workflows that do not need an agent module. See the
Anonymous Workflows guide.
Handling events in the agent module
Override handle_event/2 to react to events without subscribing to the stream:
@impl true
def handle_event({:tool_call, name, _id, _args}, state) do
Logger.info("Calling tool: #{name}")
{:noreply, state}
end
def handle_event(_event, state), do: {:noreply, state}This is the easiest way to add logging, metrics, or pubsub broadcasts.
Custom init/1
init/1 lets you build per session state when the agent starts. The return
value is stored on the session and passed to handle_event/2:
@impl true
def init(opts) do
{:ok, %{started_at: System.monotonic_time(), opts: opts}}
end