Skip to content
Contextely

Start here

Documentation

Contextely is the layer between your systems of record and every AI that reads from them. If you want the shortest path to a working retrieval, start with the quickstart; this page is the concepts behind it.

Quickstart →MCP server reference →REST API reference →CLI reference →Self-hosting →

Concepts

Your account is your company. A product partitions it: each one keeps its own memory and decides whether the others may read it. A member is an asker, either a person or the identity an agent's API key speaks as, carrying a role and a set of entitlement scopes. A source is a system of record Contextely reads from and re-reads when memory goes stale. A memory object is one condensed, source-cited fact-set with its own freshness contract and entitlement requirement.

1. Connect a source

Two connectors ship today, and adding a system of record means answering their questions rather than waiting for code. Both are asked for the same two things: how to list records once, and how to re-read one later. What connects today is the honest list of what has been run end to end.

Postgres, Supabase or Neon

A read-only connection string and two queries. Contextely refuses anything that is not a single SELECT or WITH, and opens the session read-only.

list:  select id as ref, name as title, notes as content
       from accounts where active

fetch: select id as ref, name as title, notes as content
       from accounts where id::text = $1

Saving runs both: it lists one row, re-reads that same row through the fetch query, and checks it got the same record back. A fetch query that ignores its parameter would otherwise give every memory object from this source the same content on the first refresh.

Any MCP server

Give Contextely the Streamable HTTP endpoint, the name of a tool that lists records and the name of a tool that fetches one, plus the argument names each expects and which fields of the returned record hold the identifier, the title and the text. Contextely connects as an MCP client. Servers disagree about all of those, so they are configuration rather than a convention every server is assumed to follow.

The argument that names the record is the one worth getting right. A fetch tool called with the wrong argument name usually ignores it and answers with its first record, so the connection test asks the server which arguments it accepts and then re-reads a real record to check it comes back.

2. Set the freshness contract

Each source has a TTL. Every memory object it produces inherits it. When a retrieval selects an object older than its TTL, Contextely re-reads that record from the source, re-condenses it and answers with what is true now. If the raw payload is unchanged the clock simply resets and no LLM call is made.

If the refresh fails, the result comes back labelled stale_refresh_failed with the reason. There is no path where an out-of-date answer is served as though it were current.

3. What a memory object holds

One record in, one memory object out. It carries three things, and the third is the one people are surprised by.

The condensed fact.A few hundred words of what is currently true, written from the record by the condenser, plus the entities, topics and keywords it is about. This is what goes into your agent's prompt.

Links to related memory. Two objects that share entities or topics are related, and how strongly is a number you can see. Call context_related (or open a memory in the dashboard) to walk from one to its neighbours, instead of guessing at a second search query. A neighbour outside your scopes is never named or counted.

The address of the original.The source, the record's stable reference in it, and a citation URL. Contextely does not store your documents, so when the summary is not enough, call context_expand: it goes back over the same connector and returns the whole current record, with no model in the way. That costs one source round-trip and is metered against your monthly refresh allowance, which is why it is a separate call rather than something every search pays for.

4. Decide who can see it

There are two independent questions here and a memory needs both. Collapsing them into one list is how a share dialog comes to reach nobody, or everybody.

Sharing is who it was given to, and it is a disjunction: being on any one list is enough. A memory is private to the person who stored it, shared with named teams, or shared with the whole account. Teams nest to any depth and sharing flows DOWN: share with Finance and everybody in Finance and in every team inside it can read it. Sharing with a team inside Finance does not reach the rest of Finance.

Clearance is what a reader must hold, and it is a conjunction: all of it must be satisfied. A source can require scopes and every object from it inherits them. A grant of finance:* satisfies a requirement of finance:payroll. Owners and admins hold *.

The two multiply, so a memory shared with the whole account and requiring finance reaches only the finance holders in it, and narrowing either one narrows the answer. Neither can widen the other.

The Sharing page counts, per person, what the store would actually return to them, using the same two functions the ranking uses. Ask as on the dashboard runs a real query as another member, through the identical code path an agent would hit.

5. Follow the relationships

Every record is condensed into a memory object, and the same model call names the relationships it asserts. Those become a graph you can walk with graph_search, which seeds from a normal search and follows what it found outward.

Every relationship carries asserted_by: the one memory object that asserted it. An answer built from the graph can therefore cite each claim rather than presenting an inference as a fact. That is also where entitlement lives, on the relationship rather than on the thing, because an entity existing is not a disclosure and an assertion about it is. Two people walk the same graph and get different answers.

A walk is bounded: a fixed depth, and a fixed number of relationships followed from any one thing. The cut is taken from the relationships you may see, so you never get fewer results because of neighbours you may not. A memory stored through memory_add has no condensation call to ride on, so it reports graph_state: "pending" and its relations arrive shortly after.

6. Call it

REST
curl -s https://www.contextely.com/api/v1/search \
  -H "Authorization: Bearer ctx_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"query":"acme renewal","limit":5}'
CLI
curl -fsSL https://www.contextely.com/cli.mjs -o contextely && chmod +x contextely

./contextely config set-key ctx_sk_...
./contextely search "acme renewal"
./contextely related <memory-id>   # walk to what it relates to
./contextely expand <memory-id>    # read the whole record from the source

The same call over MCP, with the full reference for HTTP and the CLI.

When a connected system stops answering, or an authorization is about to lapse with nothing able to renew it, source health is what checks and what tells you.