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:

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

protocol_response()

@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()
}

send_opts()

@type send_opts() :: [stream: boolean(), timeout_ms: non_neg_integer()]

session_id()

@type session_id() :: binary()

start_opts()

@type start_opts() :: [
  workspace: String.t(),
  user_id: String.t(),
  session_id: String.t(),
  cli_config: map()
]

Callbacks

available?()

@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

estimate_cost(response)

@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

format_messages(messages, context)

@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

get_usage(session_id)

@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_stream(chunk)

@callback parse_stream(chunk :: binary()) ::
  {:message, map()} | :partial | :eof | {:error, term()}

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

resume(session_id, messages, context)

@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/resume
  • messages - New messages to append
  • context - Agentic context

Returns

  • {:ok, session_id, response} - Session continued, with response
  • {:error, reason} - Failed to resume

send(session_id, messages, context)

@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 from start/2
  • messages - List of message maps with :role and :content keys
  • context - Agentic context

Returns

  • {:ok, response} - Successful response
  • {:error, reason} - Failed to get response

start(backend_config, context)

@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

stop(session_id)

@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)

stream_message(session_id, chunk, context)

@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

transport_type()

@callback transport_type() :: Agentic.Protocol.transport_type()

Return the transport type.

Identifies whether this is an LLM API or local agent protocol.

Functions

available?()

estimate_cost(_)

get_usage(_)

stream_message(_, _, _)