pub struct A2aServer { /* private fields */ }Expand description
This plane, served as an A2A agent.
Implementations§
Source§impl A2aServer
impl A2aServer
Sourcepub fn new(
runtime: Arc<Runtime>,
auth: Arc<dyn Authenticator>,
security: &CardSecurity,
manifest: &Manifest,
url: impl Into<String>,
) -> Result<Self, ServerSetupError>
pub fn new( runtime: Arc<Runtime>, auth: Arc<dyn Authenticator>, security: &CardSecurity, manifest: &Manifest, url: impl Into<String>, ) -> Result<Self, ServerSetupError>
Serve manifest’s agent at url.
url is where callers reach this plane, and it goes on the card — the
same deployment-wiring split as everywhere else: an agent’s declaration
must not change when its address does.
§Errors
ServerSetupError::NoPolicy when the runtime has no policy engine, and
ServerSetupError::Card when the manifest’s digest cannot be computed.
Sourcepub fn hosting(
runtime: Arc<Runtime>,
auth: Arc<dyn Authenticator>,
security: &CardSecurity,
manifests: &[&Manifest],
url: impl Into<String>,
) -> Result<Self, ServerSetupError>
pub fn hosting( runtime: Arc<Runtime>, auth: Arc<dyn Authenticator>, security: &CardSecurity, manifests: &[&Manifest], url: impl Into<String>, ) -> Result<Self, ServerSetupError>
Serve several declared agents from one plane.
A2A’s well-known card path is singular per host, so the alternative to
this is a server per agent. The first manifest is the one the well-known card
describes — a room’s orchestrator, in the shape the CLI already uses —
and every agent additionally gets its own full card at
agent_card_path, listed in the
EXT_AGENT_DIRECTORY extension so
a caller can find them.
Skill dispatch spans every agent, because they are all on the runtime already — what was missing was only discovery. Two agents advertising one skill id is refused: dispatch names a skill, and a name that resolves to two agents is a routing decision the caller did not make.
§Errors
ServerSetupError::NoPolicy, ServerSetupError::NoCases,
ServerSetupError::NoAgents for an empty slice, or
ServerSetupError::AmbiguousSkill.
Sourcepub fn streams_per_caller(self, limit: usize) -> Self
pub fn streams_per_caller(self, limit: usize) -> Self
Bound how many streams one caller may hold open at once.
A SendStreamingMessage or SubscribeToTask is a connection held for
as long as the run lives and a journal read per poll interval — so an
unbounded count is a cost any authenticated peer chooses for this
plane. Past the ceiling a new stream is refused as back-pressure
(code::QUOTA_EXHAUSTED), before anything is admitted; one ending,
or its client going away, frees the slot.
Sourcepub fn filter_scan_budget(self, budget: usize) -> Self
pub fn filter_scan_budget(self, budget: usize) -> Self
Bound what one ListTasks may cost.
The spec’s totalSize is the exact total, and a status or contextId
filter is decided only by reading each candidate’s journal — so the
cost of a listing is one the caller chooses.
The budget counts the caller’s own tasks. The index a listing reads is narrowed to them, so another caller’s volume — or the embedder’s — does not refuse this one’s listing.
Over budget, the request is refused with the narrowing lever named —
statusTimestampAfter is answered from the index, so tightening it
shrinks the candidate set without reading anything. A refusal is honest
where a truncated total would be a lie shaped like an answer: the spec
requires the exact count, and a bound that quietly stopped counting
would report a smaller tenant, not a bounded scan.
Sourcepub fn artifact_replay_budget(self, budget: usize) -> Self
pub fn artifact_replay_budget(self, budget: usize) -> Self
Bound what one ListTasks with includeArtifacts may cost.
A completed task’s artifacts are recovered by strict replay, and a page
holds up to a hundred tasks — so without a ceiling, one authenticated
request buys a hundred full replays, per request, forever. Replays
served from the sealed-run cache cost nothing and do not count; past
the budget a task’s artifacts are omitted and marked (see
ARTIFACTS_OMITTED_KEY) rather than the request refused, because
unlike totalSize an artifact list is per-task enrichment: omitting
one is honest as long as it does not look complete, and GetTask on
the marked id recovers exactly that task’s artifacts.
Sourcepub fn signing_cards_with(
self,
signer: &dyn CardSigner,
) -> Result<Self, CardSignatureError>
pub fn signing_cards_with( self, signer: &dyn CardSigner, ) -> Result<Self, CardSignatureError>
Publish a signed card.
The card is served unauthenticated from a host a caller may not control. TLS says the bytes came from that host; it says nothing about whether the host is the party whose capabilities the card describes. A signature says that, and it keeps saying it after the card has been copied into a registry, a cache, or somebody’s repository.
Signed here rather than at derivation because the signature covers the published card, interface URL and tenant included — those are deployment facts, and a signature taken before they were set would cover a document nobody serves.
§Errors
If the card cannot be canonicalized.
Sourcepub fn with_push(
self,
store: Arc<dyn PushStore>,
transport: Arc<dyn PushTransport>,
) -> Result<Self, ServerSetupError>
pub fn with_push( self, store: Arc<dyn PushStore>, transport: Arc<dyn PushTransport>, ) -> Result<Self, ServerSetupError>
Enable A2A push configuration and expose a durable delivery worker.
The worker uses the task journal itself as its outbox. A registration’s
cursor advances only after a receiver returns 2xx, so a crash between
POST and acknowledgement persistence repeats an event and never loses
one. Call A2aServer::push_worker before consuming the server into its
router and schedule
DeliveryWorker::run_once from
every instance.
Sourcepub fn push_worker(&self) -> Option<A2aPushWorker>
pub fn push_worker(&self) -> Option<A2aPushWorker>
A cloneable worker handle, when push is configured.
The worker is crate::push::DeliveryWorker itself, bound to the A2A
projection — rows belonging to an operator
Outbox are left alone, because the projection
declares the caller namespace and the store’s due query filters on it.
Retry ceiling, backoff and abandonment reporting are the loop’s own;
see its docs rather than a restatement here.
Sourcepub fn router(self) -> Router
pub fn router(self) -> Router
The router.
The card path is unauthenticated by design and everything else is not.
The RPC endpoint answers with and without a trailing slash, and that is
an interoperability fact rather than a courtesy: mainstream HTTP clients
that take a base URL — httpx among them, which is what the official A2A
conformance kit and the reference Python SDK are built on — resolve a
request for "/" against the card’s interface URL per RFC 3986, so the
wire carries POST {interface}/. A router that 404s the slash form
passes every test written here and fails the first real peer. Exactness
elsewhere (method names, version headers) guards protocol semantics;
a trailing slash is a client-side join artifact with none.
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for A2aServer
impl !UnwindSafe for A2aServer
impl Freeze for A2aServer
impl Send for A2aServer
impl Sync for A2aServer
impl Unpin for A2aServer
impl UnsafeUnpin for A2aServer
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more