Agentic.AgentProtocol behaviour
(agentic v0.2.2)
Copy Markdown
Behaviour for agent communication protocols.
Implement this for backends that communicate via different wire formats, session management, or execution models.
Protocol vs Transport
- Protocol - High-level agent communication (LLM API vs CLI subprocess)
- Transport - Wire-level HTTP implementations (OpenAI, Anthropic, etc.)
Protocols handle session lifecycle, response parsing, and tool execution delegation. Transports handle the actual HTTP request/response mechanics.
Implementations
Built-in protocols:
Agentic.Protocol.LLM- Wraps existing callback-based LLM callsAgentic.Protocol.ClaudeCode- Claude Code CLI via subprocessAgentic.Protocol.OpenCode- OpenCode CLI via subprocess
Callbacks
Implement all required callbacks for lifecycle management and message handling. Optional callbacks provide streaming, cost estimation, and MCP integration.
Summary
Callbacks
Check if protocol is available on the system.
Estimate cost for a response.
Format messages for the wire protocol.
Get current usage stats for a session.
Parse a streaming chunk into messages.
Resume an existing session with new messages.
Send messages and receive a response.
Start a new agent session.
Stop and cleanup a session.
Stream a message chunk to the client.
Return the transport type.
Types
@type protocol_response() :: %{ optional(:content) => String.t(), optional(:tool_calls) => [map()], optional(:usage) => %{input: non_neg_integer(), output: non_neg_integer()}, optional(:stop_reason) => String.t() | nil, optional(:metadata) => map() }
@type send_opts() :: [stream: boolean(), timeout_ms: non_neg_integer()]
@type session_id() :: binary()
Callbacks
@callback available?() :: boolean()
Check if protocol is available on the system.
For local agent protocols, checks if CLI binary exists. For LLM protocols, checks if API credentials are configured.
Default: returns true
@callback estimate_cost(response :: protocol_response()) :: float()
Estimate cost for a response.
For subscription-based agents (hourly/daily limits), this estimates the cost in abstract units. For LLM protocols, uses token counts.
Default: returns 0.0
@callback format_messages(messages :: [map()], context :: Agentic.Loop.Context.t()) :: iodata()
Format messages for the wire protocol.
Converts Agentic message format to protocol-specific format.
Parameters
messages- List of Agentic messages (%{"role" => ..., "content" => ...})context- Agentic context
Returns
iodata()- Formatted message(s) ready for wire transmission
@callback get_usage(session_id()) :: map() | nil
Get current usage stats for a session.
Returns usage information for the current billing period. Useful for displaying progress bars or limits in the UI.
Default: returns nil (no tracking)
Parse a streaming chunk into messages.
Called during streaming to convert raw bytes to structured data. Different protocols have different streaming formats (JSON, JSONL, SSE, etc.)
Parameters
chunk- Raw bytes received from the agent
Returns
{:message, map()}- Complete message parsed from chunk:partial- Incomplete data, need more:eof- Stream completed{:error, reason}- Parse error
@callback resume(session_id(), messages :: [map()], context :: Agentic.Loop.Context.t()) :: {:ok, session_id(), protocol_response()} | {:error, term()}
Resume an existing session with new messages.
For session-based protocols, continues a paused session. For stateless
protocols, equivalent to send/3.
Parameters
session_id- Existing session ID from prior start/resumemessages- New messages to appendcontext- Agentic context
Returns
{:ok, session_id, response}- Session continued, with response{:error, reason}- Failed to resume
@callback send(session_id(), messages :: [map()], context :: Agentic.Loop.Context.t()) :: {:ok, protocol_response()} | {:error, term()}
Send messages and receive a response.
The core protocol interaction. Takes conversation history and returns the agent's response with potential tool calls.
Parameters
session_id- Session fromstart/2messages- List of message maps with :role and :content keyscontext- Agentic context
Returns
{:ok, response}- Successful response{:error, reason}- Failed to get response
@callback start(backend_config :: map(), context :: Agentic.Loop.Context.t()) :: {:ok, session_id()} | {:error, term()}
Start a new agent session.
Called when a new session begins. For session-based protocols (local agents), this spawns the subprocess. For stateless protocols (LLM), this may be a no-op or initialize rate limiting.
Parameters
backend_config- Protocol-specific configuration (API keys, CLI paths, etc.)context- The Agentic context with metadata, config, and callbacks
Returns
{:ok, session_id}- Session started successfully{:error, reason}- Failed to start session
@callback stop(session_id()) :: :ok | {:error, term()}
Stop and cleanup a session.
Gracefully terminates the session, persisting any state needed for resume.
Parameters
session_id- Session to stop
Returns
:ok- Stopped successfully{:error, reason}- Error during cleanup (non-fatal)
@callback stream_message( session_id(), chunk :: map(), context :: Agentic.Loop.Context.t() ) :: :ok
Stream a message chunk to the client.
Called during response streaming to push partial data. The callback should be invoked from context.callbacks[:on_stream_message].
Default: no-op
@callback transport_type() :: Agentic.Protocol.transport_type()
Return the transport type.
Identifies whether this is an LLM API or local agent protocol.