Skip to content

What a deployment owes

What a deployment has to know is the other half of this page. That one is behaviour you have to plan around; this one is duties you have to discharge.

They are not discharged the same way, and the difference is the only thing on this page worth memorising:

kind of duty how you learn it what happens if you skip it
core can be given it it is a required argument core refuses — loud, immediate, first request
core cannot observe it a line on this page nothing at all; you are silently wrong

Every entry below says which kind it is. A list whose items look equally weighty, half of them enforced and half on your honour, is worse than no list: it teaches a reader that the enforced ones did not need saying, and therefore that the honour ones are equally safe.

The strongest thing this repository can do for a duty is make it an argument. presenter_key is the pattern — "the transport must authenticate the caller and hand the key to core" is not a sentence you have to find and believe, it is a parameter, and omitting it fails at the first request that carries a chain. Where a duty can be built that way, it has been. What is left on the honour list is what core cannot be given the ability to observe.


Enforced: core refuses without them

Authenticate the caller, and hand core the key you authenticated

presenter_key= on the AG-UI adapter, presenter_key_of on A2ARequestHandler. A chain arriving without it raises PresenterRequired; a key that is not the chain's last hop raises InvalidChain.

Verifying a chain is not authenticating a caller, and core cannot close the gap alone — a door receives bytes, not a connection. Why this is the first thing on the other page, and why the presenter check is the whole of the anti-replay story, is there.

A deployment with no authenticating seat in front of its doors does not accept chains at all. That is a working deployment, not a broken one — it is simply one where no caller has an authority.

Supply an identity key and a token signing secret

CoreSettings.identity_private_key and CoreSettings.token_signing_secret have no defaults, deliberately. There is no environment reader in this package: a deployment that keeps configuration in the environment reads it itself and passes the values. Configuration is an argument here, never ambient state.

Admit a provider before it can serve

issue_ticket mints a single-use ticket for one key; the provider signs provider_connect_payload over it and funduq signs back. Anything else raises InvalidRegistration — no proof, a ticket that was not issued to that key, a ticket already spent or gone stale, a signature that does not verify.

issue_ticket is the admission decision. It is not a method to project onto a wire for whoever can reach the door; how a ticket reaches a provider is an enrolment channel, and that is on the honour list below.

Carry a signed proof for an act on a bound thread

Cancelling a run, and answering a paused run's asks, are acts. On a thread with an authority they need a signature from that authority or the serving provider, over exactly this run and exactly the asks still open — InvalidCancel and InvalidResolution otherwise. On an unbound thread there is no authority to answer to and no proof is asked for.

Present a chain that verifies

InvalidChain. Hop signatures, the hash links between them, and the rule that a hop which dispatched and its successor must agree.

Carry the token and the signature on a KYOK completion call

KyokRejected, with a status a gateway can pass through.


On your honour: core cannot see whether you did

Publish funduq's signing key at a funduq-level address

Funduq.identity_public_key is here and reaches exactly one audience: a provider, through the connect handshake. A caller arriving at a door has no path to it — not a published one, not an unpublished one.

Not on the agent card. The card is the provider's declaration of what its agent is; funduq only relays it and swaps in its own supported_interfaces. Hanging the intermediary's key off the provider's agent is the wrong owner, and it shows: the same value would be printed on every card the deployment serves, which is what a fact about funduq rather than about the agent looks like.

Publish it at funduq's own address, naming no agent. That is already the shape on the provider side, where funduq proves funduq unrelated to any agent, and the caller side is the missing half. The caller gets the address for free: supported_interfaces[].url is your door, so its origin is already in the caller's hands.

Core cannot observe whether you did this, and cannot be given the ability to. The cost of skipping it is nothing today and everything later: a signature nobody can attribute is not evidence, so every future receipt funduq mints (#278) is unverifiable by the party it is for.

Decide who may read

Core does not, as of contract revision 22. Reads are not acts, a chain records acts, and authorisation belongs at the wire where a live channel exists.

What core offers is the question, not the verdict: Funduq.parties_of(thread_id) returns the head, the provider serving the agent, and every key on the thread's runs' chains — or None for a thread nobody bound, which names no parties at all. Deriving that stays core's alone, because the chains are here and only here are they verified. What follows from it is yours.

A deployment that keeps no rule of its own serves reads to whoever reaches its doors. Nothing will tell you that is happening.

Get the ticket to the provider

issue_ticket mints one; the channel that carries it to the party that should hold it is not in this package and cannot be. Whoever can obtain a ticket for a key can open a link as that key.

Own the socket

Listening, dialling, TLS, framing, timeouts, retries, rate limits. funduq's own code neither listens nor dials, and which protocol something arrived over is not a thing core knows. The adapters return objects, not responses.

There is no wire in this package to reuse and none is coming: a published method-to-endpoint mapping would be a wire authored here. What you may call survives a carrier swap and belongs here; how a call becomes an endpoint does not and belongs to you.

Keep the record

Core never deletes a run, a thread or an event. Your own operations can — retention policy, a dropped table, a restored backup — and nobody holds anything funduq signed that would contradict a record that is simply gone. A record removed and a record that never existed look exactly alike.


What this surface does not yet offer

Stated here rather than left to be discovered, because until it exists a host cannot discharge the duty by any means:

A registered provider cannot authenticate at a door with the key funduq already made it hold. funduq proves a provider's key once, when the link opens, and the open link is the credential from then on. But a provider arriving at an AG-UI or A2A door is a caller like any other, and there is no conventional way for it to present the key funduq already knows it by. So a deployment wanting agent-to-agent delegation through its own front door must invent a second credential for a party it has already authenticated once.

That is a question about the door's surface rather than about bytes, and it is tracked in #280 with the rest of this page.