ARIA Adaptive Response & Intelligence Agent

Events in.
Bubbles out.

durable object · websocket · r2 · d1

The only one of the four that is not a request and a response. A conversation is a log of events that arrive when they arrive, so the design problem is deciding when a turn has actually finished being said.

01 Topology

Cloudflare Pages cannot define a Durable Object, so the realtime half lives in its own Worker. The Pages Worker stays the auth boundary and forwards.

Braingoogle/gemma-4-31B-it served through Novita, on a Gradio Space
Stateone Durable Object per conversation, AriaConversation
TransportWebSocket to the object, with the trusted user id injected by the Pages Worker
MediaR2 bucket, handed to the public Space as short-lived HMAC-signed URLs
PersistenceD1, shared with the rest of the app: events and bubbles, appended
Budget20 messages per rolling 5 h, enforced inside the object, the only layer that sees every message

02 The turn lifecycle

Everything that arrives is an event: a message, an image, a file, or the system noticing you went quiet.

Append

The client sends an event over the socket. The object appends it to the log and persists it. Nothing is overwritten, so the conversation has a history rather than a current state.

types message · image · file · system_idle

Cluster

Consecutive unanswered events are gathered into one logical turn, debounced. This is the whole trick: "buy apples" followed a second later by "and pears" is one thought, and answering the first before the second arrives makes the agent look like it is not listening.

debounce ~600 ms

Call

The object builds structured context from the recent events and the resolved topic, and calls the brain. Media is passed as a signed URL rather than bytes, so the public Space can fetch it without the bucket being public.

signed URL, short TTL

Stream

The brain returns bubbles as structured JSON, each tied to the event it answers. The object streams a typing signal, then the bubbles, then persists them.

types response · follow_up · proactive

Idle

An alarm rearms after every turn. When it fires, the object emits a system event and the brain may choose to say something unprompted, which is how an image sent with no caption gets a "what is this?" instead of silence. Capped per turn so it cannot nag.

alarm 10 s · capped per turn

03 Bubble contract

The brain does not return prose. It returns a list, and each item names the event it is answering, which is what lets a reply attach to the right message in a conversation that kept moving while the model was thinking.

[
  { "bubble": "co to?",              "target_event_id": "evt_123", "type": "proactive" },
  { "bubble": "jabłka ogarnięte",    "target_event_id": "evt_456", "type": "response"  },
  { "bubble": "gruszki też dorzucę", "target_event_id": "evt_457", "type": "follow_up" }
]

brain → durable object → client

04 Decisions worth defending

Event log, not a message arrayA conversation where things arrive mid-thought needs an append-only history. A mutable array of messages cannot express "this reply answers the message before last".
Debounce before callingAnswering each message as it lands is faster and reads as worse. Waiting 600 ms is the difference between a bot and something that lets you finish.
Limit inside the objectIt is the only layer that sees every individual message. Enforcing the budget anywhere else means enforcing it on an estimate.
Signed URLs for mediaThe Space is public. The bucket is not, and does not become public just because the model needs to see one image.