Skip to main content

The Low-Level API

The Agent System API is the recommended way to embed the Infinity Runtime: the builder, the step API, and the local driver package the machinery below into a few calls. This section documents the layer underneath, the pieces the high-level API is built from, all of which are public.

Drop down to this layer when the packaged behavior is not what you need:

  • Custom batch scheduling. You want to decide yourself which messages form a batch, when a thread runs, or how concurrent threads are prioritized, rather than using the built-in per-thread driver or one-step-per-delivery semantics.
  • Embedding in an existing event loop. Your process already has a reactor or actor framework, and you want the agent loop as a set of futures you compose rather than tasks the runtime spawns.
  • Custom deferral or replay semantics. The high-level API defers subscription events while a tool call is pending and replays in-flight turns to new subscribers in one fixed way; at this layer you control both.
  • A new platform binding. Porting the runtime to a new storage, transport, or serverless platform means implementing the platform traits below, and it helps to understand exactly what the loop does with them.

Everything here is the same code the high-level API runs. Thread::step is a composition of prepare_input, run_completion, and execute_action over a HistoryManager. Nothing is lost by starting high and dropping down only where needed.

Crate map

CrateRole
infinity-agent-coreThe agent loop: HistoryManager, process_batch, run_completion, built-in tools, the platform traits, and the agent system API on top
infinity-provider-protocolThe ModelProvider trait and the out-of-process provider transport. Deliberately lightweight so provider crates can depend on it alone
rap-protocolRAP wire types: RapInvocation, toolset manifests, display segments
rap-clientClient-side RAP plumbing: the HttpClient trait, ToolsetLoader for discovery, RapNotifier for cancellations, and a local callback server
infinity-agent-lambdaThe AWS binding: SQS handler, DSQL conversation store, DynamoDB state store
infinity-daemonThe embedded binding used by Infinity Code: one long-lived local agent system for the whole daemon, in-memory + JSON-file stores, lazily booted per-session RAP servers

The last two are the production embeddings of the core. If you are writing your own, they are the reference material: infinity-agent-lambda is the minimal step-mode embedding, and infinity-daemon is the full interactive local one.

The platform traits

The core talks to the outside world through four traits, defined in infinity_agent_core::traits and rap_client::http. The table shows what each production embedding plugs in:

TraitResponsibilityLambdaDaemon
ConversationStorePer-thread history, thread hierarchy, compaction summariesAurora DSQLIn-memory + JSON files
StateStoreProcessed IDs, metadata, active subscriptionsDynamoDBIn-memory + JSON files
InputSenderDelivering messages to the input queueSQS FIFOmpsc channels
HttpClientPOST/GET to tool serversSigV4-signed reqwestPlain reqwest

InputSender is the smallest and the most important, because it defines the yield boundary:

#[async_trait]
pub trait InputSender: Send + Sync + Clone {
type Error: std::error::Error + Send + Sync + 'static;

/// Send a message to the input queue for processing.
async fn send_to_input_queue(
&self,
message: InputMessage,
group_id: &str,
dedup_id: &str,
) -> Result<(), Self::Error>;
}

Anything the runtime wants to happen later (a child thread's seed message, a report to a parent, a timer wake-up) goes through send_to_input_queue rather than a function call. The group_id selects the target thread and the dedup_id makes redelivery safe. Whatever ordering guarantee your transport gives per group is the concurrency control for the whole runtime, so it must be FIFO within a group. The core ships one implementation, ChannelSender, the in-process queue behind local systems.

ConversationStore is the largest trait. Beyond appending and loading messages (append_messages, load_history_up_to) it models the thread tree (spawn_thread, get_ancestor_chain, close_thread) and compaction summaries (save_compaction_summary, load_latest_compaction_summary_up_to). The provided load_history_with_ancestors default method handles the subtle part, reconstructing a child thread's inherited history with the most recent compaction summary applied, so implementations only supply the primitive queries. The core also provides ready-made InMemoryConversationStore and InMemoryStateStore implementations, which are what the agent system examples use.

StateStore keeps the bookkeeping that makes redelivery and wake-ups safe: processed message and tool-call IDs (get_processed_ids, add_processed_message_ids, add_processed_tool_calls), per-conversation metadata (get_metadata, set_metadata), and active subscriptions (get_active_subscriptions, add_active_subscription, remove_active_subscription).

Model access goes through a fifth trait, ModelProvider, which streams completions and lists available models. The core never calls a model API directly.

In this section

  • The History Manager: the per-thread state object, covering committed history, the buffered in-flight turn, deduplication, and the sync() durability point.
  • The Completion Loop: prepare_input, run_completion, and execute_action (the three phases of a slice) and process_batch, which composes them over a batch of messages.