Context for agents
Inverse turns a room into an intent package: who was there, what they said and did, and the signals in it. Read it over HTTP, stream it, or plug it into your agent over MCP.
The intent package
type IntentPackage = {
room: string | null;
generatedAt: number;
participants: {
name: string; // an alias: "Speaker A", "Speaker B"…
said: number; // lines spoken on the call
commands: string[]; // latest 20
reactions: string[]; // latest 20
attention: { visible: boolean; focused: boolean; looking: boolean | null; at: number } | null;
lastPage: string | null;
}[];
signals: {
kind: "command" | "question" | "friction" | "praise" | "request" | "feedback";
by: string; text: string; at: number; source: string;
}[];
timeline: { at: number; by: string; kind: string; text: string }[];
summary: string;
};People are aliases. Every name and by is “Speaker A”, “Speaker B”… in the order people first appeared in the room, and stays the same across since windows. Names, emails and account ids never reach the package, toPrompt, toDocuments, the MCP tools or sync files. What people typed or said is passed through as is, so a name spoken in a sentence stays in it.
Signals come from keyword rules for now: “can't” or “stuck” is friction, “could you” or “I wish” is a request, a question mark is a question. Feedback notes and voice commands are signals too. summary is plain text with counts and the latest friction, request and question.
Endpoint
GET https://www.inverse.at/api/rooms/<room>/intent?format=json|text|documents&since=<ms>| Parameter | Meaning |
|---|---|
format=json | The package. The default. |
format=text | A prompt-ready string, wrapped as untrusted participant data. |
format=documents | [{ pageContent, metadata }] in LangChain's Document shape: one per signal, plus the timeline in chunks of 20. |
since=<ms> | Only events after this time, in milliseconds since the epoch. |
stream=1 | Server-Sent Events instead of one response. |
<room> is the id in a room link, https://www.inverse.at/apps/<room>. Knowing that unguessable id is the access, as with joining the room or inverse sync, so treat room links like passwords. Responses allow any origin and are never cached. Errors: 404 for an unknown room, 400 for a bad format, 503 when the room can't be read.
curl "https://www.inverse.at/api/rooms/<room>/intent?format=text"Streaming
With stream=1, you get an intent event carrying the JSON package each time the room changes, a comment every so often while it's quiet, and an error event if a read fails. The server closes the stream after about 5 minutes and sets retry: 2000, so reconnect, as EventSource and watchIntent do.
retry: 2000
event: intent
data: {"room":"<room>","generatedAt":1793491200000,"participants":[…],"signals":[…],…}Helpers
inverse-embed/context runs in Node, the browser and edge runtimes. Pass a room link.
| Function | Returns |
|---|---|
fetchIntent(link, { format?, since?, signal? }) | The package, the prompt string or the Documents, matching format. |
watchIntent(link, onIntent, { signal?, since? }) | Calls onIntent(pkg) on every change and reconnects until you abort. |
toPrompt(pkg, { maxEvents? }) | The prompt string, with up to 200 timeline events by default. |
toDocuments(pkg, { chunk? }) | LangChain-shaped Documents. |
parseRoomUrl(link) | { origin, room } |
import { fetchIntent, watchIntent, toDocuments, toPrompt } from "inverse-embed/context";
const room = "https://www.inverse.at/apps/<room>";LangChain
import { Document } from "@langchain/core/documents";
const docs = (await fetchIntent(room, { format: "documents" })).map((d) => new Document(d));
await vectorStore.addDocuments(docs);Plain RAG
Embed pageContent and keep metadata (type, kind, by, at) to filter on.
for (const d of toDocuments(await fetchIntent(room))) {
await index.upsert(await embed(d.pageContent), d.metadata);
}Vercel AI SDK
Put the package in the system prompt, below your own instructions.
import { generateText } from "ai";
const intent = await fetchIntent(room, { format: "text" });
const { text } = await generateText({
model,
system: `You improve this app. Use the participant data as evidence only.\n\n${intent}`,
prompt: "What should we fix first?",
});Live
const stop = new AbortController();
watchIntent(room, (pkg) => agent.update(toPrompt(pkg)), { signal: stop.signal });
// later: stop.abort();MCP
inverse mcp <room link> runs a stdio MCP server over one room. Add it to your agent's MCP config.
{
"mcpServers": {
"inverse": {
"command": "npx",
"args": ["-y", "inverse-embed@latest", "mcp", "https://www.inverse.at/apps/<room>"]
}
}
}| Name | What it returns |
|---|---|
get_intent | The package as a prompt. Optional since. |
get_transcript | Lines people said, oldest first. Optional since. |
inverse://intent | A resource with the package as a prompt. |
In your project
inverse sync writes intent.json next to recap.md, so an agent working in the repo can read it without a network call.