Giving AI Agents a Memory of Your Crisp Inbox
AI agents forget everything. Every LLM call starts from zero — no idea what your customer asked yesterday, what your team answered, or how a bug was actually diagnosed. Cognee is an open-source memory layer that fixes this: you feed it your data, it builds a knowledge graph, and agents can then recall that knowledge across sessions.
The hard part is getting the data in. Cognee has a handful of connectors, but the world runs on dozens of tools. As part of Mergetober (open-source month with Cognee), I built a Crisp connector — wiring the shared-inbox support tool into Cognee so agents can answer questions about what customers actually asked.
This is the story of how it works, and a few design decisions that were less obvious than they looked.
The problem: chat inboxes don't fit neatly
Crisp is a shared inbox — visitors chat with your team, conversations accumulate. At first glance it's simple: read conversations, feed them to Cognee. But chat data has a shape that fights you:
- Sessions are short and numerous. One conversation might be three messages ("how do I reset?" → "click here" → "thanks"). Ingest each message as its own node and your graph explodes into thousands of near-identical fragments.
- There's no delete feed. When a conversation is closed or aged out, Crisp just... stops listing it. There's no webhook saying "this was deleted."
Both of these drove the design.
Decision 1: one node per conversation, not per message
The connector aggregates an entire conversation into a single markdown document — visitor context plus the full transcript:
Visitor: Ada · chat
Visitor: I can't find my invoice
Agent: I've resent it to your email — check spam?
Visitor: Got it, thanks!
This keeps the graph readable and matches how humans think about a support thread as one unit of meaning. It's the same call the existing Slack connector makes, and it's what the Cognee maintainers flagged in the issue itself.
Decision 2: full-snapshot sync, because there's no delete feed
Cognee already has a "forget-on-delete" mechanism: if a record drops out of the snapshot between two syncs, its orphan-cleanup removes it from the graph. But that only works if the connector loads a complete snapshot each run — the exact set of conversations currently visible — rather than merging in new ones.
So the connector uses write_disposition="replace": every sync rewrites staging with all current conversations. A conversation deleted upstream simply isn't in the new snapshot, and Cognee forgets it. Unchanged conversations keep a stable content-hash ID, so they're not re-processed and re-cognified.
The subtle trap: a partial snapshot must never land
Here's the part that took the most care. Because "absent from snapshot" is interpreted as "deleted," a partial snapshot — say the API returned an error halfway through — would make Cognee forget a batch of perfectly live conversations. A flaky network would silently erase memory.
So the connector treats a render or API failure as fatal: it aborts the run and leaves the previous memory intact, rather than committing a truncated snapshot. Transient errors (rate-limits, 5xx, timeouts) are retried with backoff first; only a persistent failure aborts. Getting this wrong would be the kind of bug that quietly corrupts memory for weeks before anyone notices.
Incremental sync
https://github.com/topoteretes/cognee
Re-cognifying the entire inbox on every run is wasteful. Crisp exposes updated_at on each conversation, so the connector takes an optional since= watermark and only renders conversations updated after it. Combined with the stable content-hash IDs, a daily sync only touches what actually changed.
Auth: Crisp's two-part token
Crisp authenticates with a two-part keypair (identifier + key) sent as HTTP Basic, plus an X-Crisp-Tier: plugin header, plus a website_id selecting the workspace. Not OAuth — a plugin token you generate from the Marketplace. Straightforward once you know the trio.
Using it
pip install cognee-community-connector-crisp
export CRISP_IDENTIFIER="..."
export CRISP_KEY="..."
export CRISP_WEBSITE_ID="..."
import cognee
from cognee_community_connector_crisp import crisp_source
await cognee.remember(
crisp_source(), # or since= for incremental
dataset_name="crisp",
max_rows_per_table=0, # ingest the whole inbox
)
answer = await cognee.search(
query_text="What did customers complain about this month?",
query_type=cognee.SearchType.GRAPH_COMPLETION,
datasets=["crisp"],
)
Then you can ask your agent "summarize the billing complaints from last week" and it actually knows — because the inbox is in its memory.
Testing without a live account
The test suite mocks the Crisp API (no token needed) and covers the parts most likely to break: message/conversation rendering, Crisp's page-number pagination, the document-source tagging that routes conversations through normal cognify, and the full-snapshot forget-on-delete behavior — including the case where a conversation vanishes from the listing between syncs and must be forgotten. 13 tests, all passing, plus ruff clean.
What's next
The connector is open as a PR against cognee-community. Once merged, "ask my Crisp inbox" becomes one crisp_source() away — and it's a template for the next chat-tool connector, because the same full-snapshot + aggregate-per-thread pattern applies to Intercom, Front, Zendesk, and the rest.
If you're doing Mergetober too, the Crisp connector is a decent reference for the shape a Cognee data-source connector should take. And if you try it against your own inbox, I'd genuinely like to hear what breaks — chat APIs always have one more pagination quirk than the docs admit.
Top comments (1)
tr.ee/dev-to