Skip to content

Repository files navigation

☄️ Diffcp - AI Streaming Protocol

TypeScript JavaScript Python Codecov MIT License

Diffcp (Differential Context Protocol) is the new standard to stream AI Agent state to the user interface. Is a lightweight alternative to any bespoke AI protocol currently in the industry. Purpose built to be versatile unopinionated and highly efficient (95%+ compression). It provides all the foundations:

  • 🧠 Kills complexity → no more event orchestration logic
  • 🧩 One single model → no events, no tool calls, just one state model
  • Minimal over-the-wire cost → highly optimized transmission
  • 🔄 Deterministic state convergence → frontend always reflects backend truth
  • 🛠 Drop-in for any existing API → same endpoint, same type, zero rewrites
  • 🗜️ Efficient Compression → Smart embedded compression
  • 🍔 Zero dependency → just raw speed

Today's “real-time” APIs especially in AI and chat are a pile of ad-hoc events; tokens, partials, tool calls, patches, and retries, all in custom formats. Every system reimplements a fragile event interpreter or relies on opinionated and limiting libraries. Every edge case leaks through. Differential Context Protocol replaces all of that with one core idea: evolving state. No token streams, no custom event tax, and no frontend guesswork.

Differential Context Protocol Preview

What it does

Turn any JSON API into a continuous state synchronization channel between backend and frontend. Instead of emitting a zoo of custom events, the server streams compressed state diffs that progressively converge to the final value on the client. The schema is fully defined by you, your API requires minimal modification, and it remains backward compatible.

How Simple it is

You just need to shift your mental model: Stop streaming events about data, start streaming the data itself evolving.

On the server yield updated state objects

export async function* streamMessage(): AsyncIterable<MessageType> {
	yield { text: 'This' }
	yield { text: 'This is' }
	yield { text: 'This is a stream' }
	...
}

export function GET() {
	return new ObjectStreamResponse(streamMessage())
}

On the client consume an updating state stream

for await (const data of fetchObjectStream<MessageType>('/api')) {
	// Consume data
}

On in React just render the value

const { value } = useObjectStream<MessageType>({
    url: '/api'
})
return <p>{value?.text}</p>

Protocol

The protocol is designed to be a simple replacement to existing APIs without unnecessary developer burden, and without breaking backward compatibility of the endpoint itself. Any existing API that is returning a JSON object of type T can be extended with this protocol. The protocol streams a small set of event types to keep in synk the server-side state T and the frontend-side state T until the request is completed. The protocol is extremely efficient and only transmits a minified state diff via the network.

Messages

The DCP protocol is abstracted into messages, and is only composed of 4 message types all composed by a string type and a data payload.

Type Compressed Data Description
init 1 T Sends the complete state to the client, typically sent at the beginning of the stream, but can be skipped if the client already has a starting state. It can optionally be sent in between the stream to resync the state completely, useful to prevent state drift of more efficiently transmit large state changes.
delta 2 JSON diff Delta update of the JSON object containing one or many operations
done 3 Optional T Indicates the end of the stream and can optionally carry a complete state, to resync the state completely to prevent state drift of more efficiently transmit large state changes.
event 9 any Custom events that can be emitted during the stream and received by the client application. Events do not affect the state syncing in any way.

The messages protocol is carrier agnostic and can therefore support any message driven format and protocol. By default, DCP uses a NDJSON streamed carrier.

JSON Diff

JSON diffs or deltas are described as a set of operations with a compact notation inspired by JsonPatch. Here only add, remove, and replace are supported, but string appending is introduced which for obvious reasons is very important for AI applications. The diff format is compact and comprised of 3 operations.

Type Compressed Operation Description
s 3 Set value Sets the value indicated by the path
a 1 Append value Appends to the array or a string indicated by the path
d 2 Delete value Deletes the value indicated by the path

An example of the protocol looks like this:

[
  [
    // set a field
    "s",
    "/duration",
    8.511631965637207
  ],
  [
    // set a nested field
    "s",
    "/parts/0/state",
    "done"
  ],
  [
    // append to a string
    "a",
    "/parts/0/body/-",
    " a more focused iteration cycle for upcoming experimental model variants."
  ]
]

NDJSON Carrier

The protocol is implemented via a new-line separated JSON stream https://ndjson.com/ over HTTP. Similarly to the SSE standard this protocol can be used as a response to any HTTP request (GET, POST, ...) denoted with the application/x-ndjson MIME type. Messages are formatted in the form {t: string, d: any }

{ t: 'init', d: { ... }}  // set the initial value
{ t: 'delta', d: [ ... ]}  // change it via delta operations
{ t: 'delta', d: [ ... ]}
...
{ t: 'init', d: { ... }}  // set a new plain value
{ t: 'delta', d: [ ... ]}
...
{ t: 'done' }  // complete the stream

Tip

Empty lines and objects not matching the defined signature are ignored. JSON parsing errors will cause the request to fail.

🗜️ Compression

The base protocol already delivers highly efficient differential streaming, reducing payload size by ~90% on average. It remains human-readable, which makes debugging straightforward. On top of this, an additional zero-dependency compression layer reduces bandwidth by a further ~50%, bringing total average compression to ~95% 🚀

[1,{ ... }]
[2,[[1,"/text/-"," is n"],[1,"/parts/0/text/-"," is n"]]]
[2,[[1,2,"o dir"],[1,2,"o dir"]]]
[2,[[1,2,"ect s"],[1,2,"ect s"]]]
[2,[[1,2,"tatis"],[1,2,"tatis"],[3,"/meteo/temp",15.2999]]]
[2,[[1,3,"tic f"],[1,3,"tic f"]]]
[2,[[1,2,"or “i"],[1,2,"or “i"]]]
[2,[[1,2,"ncome"],[1,2,"ncome"]]]
[2,[[1,2," dens"],[1,2," dens"]]]

🚧 MessagePack Carrier

This is a work in progress

Wht not SSE?

The SSE protocol has been created to support server to client event streaming. Is ideal to keep a serer-to-client channel open, but as such the technology surrounding it is not ideal for the application.

First of all, browser provided EventSource clients are not designed to perform user initiated requests and only expect a streamed update. They often include retries and auto-reconnect logic which do introduce unexpected or unwanted behaviours. The same clients do not allow for arbitrary HTTP operations, they typically only support GET.

Given standard clients are not suitable for the use case and may also introduce security vulnerabilities in any case custom fetch base solutions have to be implemented. For this reasons SSE has been ruled out as a "better to avoid" protocol.

Tip

The protocol remains compatible and SSE can be used as a carrier without problems.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

Open-source standard for connecting AI user interfaces to backend systems

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages