Jido.Messaging.Demo.ChatAgent (Jido Messaging v1.0.0)

Copy Markdown View Source

A ReAct-based chat agent that participates in bridged conversations.

This agent demonstrates end-to-end agentic chat by:

  1. Listening for messages on the Signal Bus
  2. Processing messages through a ReAct reasoning loop
  3. Responding with contextual, tool-augmented replies

Usage

Start with the bridge demo:

# In config or env
export TELEGRAM_BOT_TOKEN="your_token"
export TELEGRAM_CHAT_ID="your_chat_id"
export DISCORD_BOT_TOKEN="your_token"
export DISCORD_CHANNEL_ID="your_channel_id"

# Start the demo with agent
Jido.Messaging.Demo.Supervisor.start_link(
  mode: :agent,
  telegram_chat_id: "...",
  discord_channel_id: "..."
)

The agent will respond when mentioned with @ChatAgent or when messages contain questions it can help with.

Summary

Functions

Returns the list of actions from all attached plugins.

Send a query to the agent asynchronously.

Send a query and return both its request handle and runtime event stream.

Send a query and wait for the result synchronously.

Await the result of a specific request.

Cancel an in-flight request.

Returns the union of all capabilities from all mounted plugin instances.

Returns the agent's category.

Process a chat message and return a response.

Execute actions against the agent: (agent, action) -> {agent, directives}

Returns the agent's description.

Inject user-visible input into an active request.

Returns the agent's name.

Creates a new agent with optional initial state.

Returns the configuration for a specific plugin.

Returns the list of plugin instances attached to this agent.

Returns the expanded and validated plugin routes.

Returns the expanded plugin and agent schedules.

Returns the list of plugin specs attached to this agent.

Returns the state slice for a specific plugin.

Returns the list of plugin modules attached to this agent (deduplicated).

Returns the merged schema (base + plugin schemas).

Updates the agent's state by merging new attributes.

Returns all expanded route signal types from plugin routes.

Steer an active request with additional user-visible input.

Returns the execution strategy module for this agent.

Returns the strategy options for this agent.

Returns a stable, public view of the strategy's execution state.

Returns the agent's tags.

Validates the agent's state against its schema.

Returns the agent's version.

Functions

actions()

@spec actions() :: [module()]

Returns the list of actions from all attached plugins.

ask(pid, query, opts \\ [])

@spec ask(
  pid() | atom() | {:via, module(), term()},
  String.t() | [ReqLLM.Message.ContentPart.t()],
  keyword()
) :: {:ok, Jido.AI.Request.Handle.t()} | {:error, term()}

Send a query to the agent asynchronously.

Returns {:ok, %Request{}} immediately. Use await/2 to wait for the result.

Options

  • :tool_context - Additional context map merged with agent's tool_context
  • :tools - Request-scoped tool registry override for this run only
  • :allowed_tools - Request-scoped allowlist of tool names
  • :request_transformer - Module implementing per-turn ReAct request shaping
  • :max_iterations - Request-scoped maximum reasoning iterations
  • :stream_timeout_ms - Request-scoped runtime inactivity timeout. :stream_receive_timeout_ms is accepted as a compatibility alias.
  • :req_http_options - Per-request Req HTTP options forwarded to ReAct runtime
  • :llm_opts - Per-request ReqLLM generation options forwarded to ReAct runtime
  • :file_id / :file_ids / :file_reference / :file_references - Uploaded file references appended to the user query as ReqLLM content parts when supported by the ReqLLM version
  • :output - :raw to bypass structured output or a request-scoped output config
  • :stream_to - Optional request-scoped runtime event sink, currently {:pid, pid}
  • :timeout - Timeout for the underlying cast (default: no timeout)

Examples

{:ok, request} = MyAgent.ask(pid, "What is 2+2?")
{:ok, result} = MyAgent.await(request)

ask_stream(pid, query, opts \\ [])

@spec ask_stream(
  pid() | atom() | {:via, module(), term()},
  String.t() | [ReqLLM.Message.ContentPart.t()],
  keyword()
) ::
  {:ok, %{request: Jido.AI.Request.Handle.t(), events: Enumerable.t()}}
  | {:error, term()}

Send a query and return both its request handle and runtime event stream.

The returned enumerable yields canonical ReAct runtime events until the request emits :request_completed, :request_failed, or :request_cancelled.

Options

Accepts the same options as ask/3, plus:

  • :stream_event_timeout_ms - Optional mailbox receive timeout for the enumerable

Examples

{:ok, %{request: request, events: events}} = MyAgent.ask_stream(pid, "What is 2+2?")
for event <- events do
  IO.inspect(event.kind)
end
{:ok, result} = MyAgent.await(request)

ask_sync(pid, query, opts \\ [])

@spec ask_sync(
  pid() | atom() | {:via, module(), term()},
  String.t() | [ReqLLM.Message.ContentPart.t()],
  keyword()
) :: {:ok, any()} | {:error, term()}

Send a query and wait for the result synchronously.

Convenience wrapper that combines ask/3 and await/2.

Options

  • :tool_context - Additional context map merged with agent's tool_context
  • :tools - Request-scoped tool registry override for this run only
  • :allowed_tools - Request-scoped allowlist of tool names
  • :request_transformer - Module implementing per-turn ReAct request shaping
  • :max_iterations - Request-scoped maximum reasoning iterations
  • :stream_timeout_ms - Request-scoped runtime inactivity timeout. :stream_receive_timeout_ms is accepted as a compatibility alias.
  • :req_http_options - Per-request Req HTTP options forwarded to ReAct runtime
  • :llm_opts - Per-request ReqLLM generation options forwarded to ReAct runtime
  • :file_id / :file_ids / :file_reference / :file_references - Uploaded file references appended to the user query as ReqLLM content parts when supported by the ReqLLM version
  • :output - :raw to bypass structured output or a request-scoped output config
  • :timeout - How long to wait in milliseconds (default: 30_000)

Examples

{:ok, result} = MyAgent.ask_sync(pid, "What is 2+2?", timeout: 10_000)

await(request, opts \\ [])

@spec await(
  Jido.AI.Request.Handle.t(),
  keyword()
) :: {:ok, any()} | {:error, term()}

Await the result of a specific request.

Blocks until the request completes, fails, or times out.

Options

  • :timeout - How long to wait in milliseconds (default: 30_000)

Returns

  • {:ok, result} - Request completed successfully
  • {:error, :timeout} - Request didn't complete in time
  • {:error, reason} - Request failed

Examples

{:ok, request} = MyAgent.ask(pid, "What is 2+2?")
{:ok, "4"} = MyAgent.await(request, timeout: 10_000)

cancel(pid, opts \\ [])

@spec cancel(
  pid() | atom() | {:via, module(), term()},
  keyword()
) :: :ok | {:error, term()}

Cancel an in-flight request.

Sends a cancellation signal to the agent. Note that this is advisory - the underlying LLM request may still complete.

Options

  • :reason - Reason for cancellation (default: :user_cancelled)

Examples

{:ok, request} = MyAgent.ask(pid, "What is 2+2?")
:ok = MyAgent.cancel(pid)

capabilities()

@spec capabilities() :: [atom()]

Returns the union of all capabilities from all mounted plugin instances.

Capabilities are atoms describing what the agent can do based on its mounted plugins.

Example

MyAgent.capabilities()
# => [:messaging, :channel_management, :chat, :embeddings]

category()

@spec category() :: String.t() | nil

Returns the agent's category.

chat(pid, message, opts \\ [])

@spec chat(pid(), String.t(), keyword()) :: {:ok, String.t()} | {:error, term()}

Process a chat message and return a response.

This is the main entry point called by the AgentRunner wrapper.

cmd(agent, action)

Execute actions against the agent: (agent, action) -> {agent, directives}

This is the core operation. Actions modify state and may perform required work; directives are runtime-owned external effects. Execution is delegated to the configured strategy (default: Direct).

Action Formats

  • MyAction - Action module with no params
  • {MyAction, %{param: 1}} - Action with params
  • {MyAction, %{param: 1}, %{context: data}} - Action with params and context
  • {MyAction, %{param: 1}, %{}, [timeout: 1000]} - Action with opts
  • %Instruction{} - Full instruction struct
  • [...] - List of any of the above (processed in sequence)

Options

The optional third argument opts is a keyword list merged into all instructions:

  • :timeout - Maximum time (in ms) for each action to complete
  • :max_retries - Maximum retry attempts on failure
  • :backoff - Initial backoff time in ms (doubles with each retry)

Examples

{agent, directives} = Jido.Messaging.Demo.ChatAgent.cmd(agent, MyAction)
{agent, directives} = Jido.Messaging.Demo.ChatAgent.cmd(agent, {MyAction, %{value: 42}})
{agent, directives} = Jido.Messaging.Demo.ChatAgent.cmd(agent, [Action1, Action2])

# With per-call options (merged into all instructions)
{agent, directives} = Jido.Messaging.Demo.ChatAgent.cmd(agent, MyAction, timeout: 5000)

cmd(agent, action, opts)

description()

@spec description() :: String.t() | nil

Returns the agent's description.

inject(pid, content, opts \\ [])

@spec inject(pid() | atom() | {:via, module(), term()}, String.t(), keyword()) ::
  {:ok, Jido.Agent.t()} | {:error, term()}

Inject user-visible input into an active request.

This is intended for programmatic or inter-agent steering and follows the same queuing rules as steer/3.

name()

@spec name() :: String.t()

Returns the agent's name.

new(opts \\ [])

@spec new(keyword() | map()) :: Jido.Agent.t()

Creates a new agent with optional initial state.

The agent is fully initialized including strategy state. For the default Direct strategy, this is a no-op. For custom strategies, any state initialization is applied (but directives are only processed by AgentServer).

Examples

agent = Jido.Messaging.Demo.ChatAgent.new()
agent = Jido.Messaging.Demo.ChatAgent.new(id: "custom-id")
agent = Jido.Messaging.Demo.ChatAgent.new(state: %{counter: 10})

plugin_config(plugin_mod)

@spec plugin_config(module() | {module(), atom()}) :: map() | nil

Returns the configuration for a specific plugin.

Accepts either a module or a {module, as_alias} tuple for multi-instance plugins.

plugin_instances()

@spec plugin_instances() :: [Jido.Plugin.Instance.t()]

Returns the list of plugin instances attached to this agent.

plugin_routes()

@spec plugin_routes() :: [{String.t(), module(), integer()}]

Returns the expanded and validated plugin routes.

plugin_schedules()

Returns the expanded plugin and agent schedules.

plugin_specs()

@spec plugin_specs() :: [Jido.Plugin.Spec.t()]
@spec plugin_specs() :: [map()]

Returns the list of plugin specs attached to this agent.

plugin_state(agent, plugin_mod)

@spec plugin_state(Jido.Agent.t(), module() | {module(), atom()}) :: map() | nil

Returns the state slice for a specific plugin.

Accepts either a module or a {module, as_alias} tuple for multi-instance plugins.

plugins()

@spec plugins() :: [module()]

Returns the list of plugin modules attached to this agent (deduplicated).

For multi-instance plugins, the module appears once regardless of how many instances are mounted.

Example

MyAgent.plugins()
# => [MyApp.SlackPlugin, MyApp.OpenAIPlugin]

schema()

@spec schema() :: Zoi.schema() | keyword()

Returns the merged schema (base + plugin schemas).

set(agent, attrs)

@spec set(Jido.Agent.t(), map() | keyword()) :: Jido.Agent.agent_result()

Updates the agent's state by merging new attributes.

Uses deep merge semantics - nested maps are merged recursively.

Examples

{:ok, agent} = Jido.Messaging.Demo.ChatAgent.set(agent, %{status: :running})
{:ok, agent} = Jido.Messaging.Demo.ChatAgent.set(agent, counter: 5)

signal_types()

@spec signal_types() :: [String.t()]

Returns all expanded route signal types from plugin routes.

These are the fully-prefixed signal types that the agent can handle.

Example

MyAgent.signal_types()
# => ["slack.post", "slack.channels.list", "openai.chat"]

steer(pid, content, opts \\ [])

@spec steer(pid() | atom() | {:via, module(), term()}, String.t(), keyword()) ::
  {:ok, Jido.Agent.t()} | {:error, term()}

Steer an active request with additional user-visible input.

Returns {:ok, agent} when the input is queued for the current ReAct run or {:error, {:rejected, reason}} when no eligible run is active.

Queued input is best-effort. If the run terminates before the runtime drains the queue into conversation state, the queued input is dropped.

strategy()

@spec strategy() :: module()

Returns the execution strategy module for this agent.

strategy_opts()

@spec strategy_opts() :: keyword()

Returns the strategy options for this agent.

strategy_snapshot(agent)

@spec strategy_snapshot(Jido.Agent.t()) :: Jido.Agent.Strategy.Snapshot.t()

Returns a stable, public view of the strategy's execution state.

Use this instead of inspecting agent.state.__strategy__ directly. Returns a Jido.Agent.Strategy.Snapshot struct with:

  • status - Coarse execution status
  • done? - Whether strategy reached terminal state
  • result - Main output if any
  • details - Additional strategy-specific metadata

tags()

@spec tags() :: [String.t()]

Returns the agent's tags.

validate(agent, opts \\ [])

@spec validate(
  Jido.Agent.t(),
  keyword()
) :: Jido.Agent.agent_result()

Validates the agent's state against its schema.

Options

  • :strict - When true, only schema-defined fields are kept (default: false)

Examples

{:ok, agent} = Jido.Messaging.Demo.ChatAgent.validate(agent)
{:ok, agent} = Jido.Messaging.Demo.ChatAgent.validate(agent, strict: true)

vsn()

@spec vsn() :: String.t() | nil

Returns the agent's version.