Skip to Content
Polyant is open source under AGPL-3.0 — star us on GitHub.

Memory

On this page

The Memory screen (/organizations/{org}/workspaces/{ws}/memory) shows the long-term knowledge the assistants have accumulated. Each agent also has its own Memory section, under its Activity group, showing only its entries. Memory entries are scoped per-instance: an entry written by support-bot is invisible to sales-bot.

Scope: shared, or per conversation

Managed services. Configurable memory scope is available with Polyant managed services.

Inside one agent, memory is a shared pool by default: anything learned in any conversation is readable from any other. Memory scope, in the agent’s Advanced section, narrows that:

SettingStored asWhat a turn reads and writes
Shared across conversations (default)agentthe whole pool - every memory the agent holds
Isolated per conversationconversationonly the memories written in the conversation the turn belongs to

Pick isolation where two conversations must not learn about each other - one agent serving several end customers over the same channel, say. Pick shared where the agent is meant to accumulate knowledge of one domain, which is why it is the default.

Two things are worth knowing before you switch:

  • It is not retroactive. Existing memories carry the key they were written with. Turning isolation on does not partition what is already there - those rows stay in the shared pool and stop being readable under the new scope. Turning it off again makes them readable again.
  • An ephemeral conversation always uses the shared pool, whatever the setting. Whether a conversation will still exist next time is declared by whoever built its id, not inferred from the channel: a scheduled task and some agent-to-agent calls mint an id for one turn. Keying a memory on one of those would write something nothing can ever recall.

The narrowing key is decided by the memory layer from server-side data - the configured scope and the conversation the turn belongs to - and never from a tool argument, so no caller and no model can widen it.

A third scope, principal (per end user rather than per conversation), exists in the engine’s types and in the resolver but cannot be set: until identity resolution lands it falls back to the conversation id, so accepting it would store a value promising per-user isolation while delivering per-conversation. The panel does not offer it.

Retrieval model

Memory retrieval uses hybrid search - pgvector cosine similarity merged with PostgreSQL FTS via Reciprocal Rank Fusion (k=60). See packages/engine/src/memory/hybrid-search.ts. Vector results capture semantic similarity; FTS results capture exact keyword and phrase matches; RRF combines the two ranked lists without requiring per-side score calibration.

Embeddings come from the instance’s embedder, chosen in Model → Embeddings independently of the chat provider. The selector lists the embedders this server serves: OpenAI text-embedding-3-small and Amazon Titan Text Embeddings v2 in every build, plus TensorX and Regolo.ai (Qwen3-Embedding-8B) with managed services. Anthropic and Nebius have no embeddings API, so an agent chatting on either needs one of the embedders configured - not necessarily OpenAI. The Model section warns when memoryEnabled is on but the selected embedder has no credentials, surfacing the most common misconfiguration before it produces runtime errors.

Switching the embedder deletes every memory for the agent: vectors are not converted between provider spaces.

Categories

Every memory is tagged with one of six categories:

CategoryUse forExample
generalAnything not in another category.”Acme uses Slack as primary comms tool.”
preferenceA user’s expressed preference.”Alice prefers tables over prose answers.”
factA neutral factual statement about the user or domain.”Bob’s office is in Munich.”
eventA specific dated event.”Demo with Acme on 2026-05-12 at 15:00 CEST.”
relationshipA connection between two entities.”Carol manages Dan’s team.”
decisionAn explicit decision made.”Decided to use Postgres for the prototype.”

Categories matter because memory search can be filtered by them, and because the auto-extractor uses them to decide priority.

Listing and searching

  • Pagination with the same scheme as Conversations.
  • Search - hybrid (vector + FTS) with Reciprocal Rank Fusion. The vector search uses the instance embedder; the keyword search uses PostgreSQL FTS; the two ranked lists are merged using RRF (k=60).
  • Filters - by instance and by category.

Each row shows: content, category, importance (1–10), source conversation (link), created at.

The underlying /memories API call is instance-scoped: it requires an instanceId query parameter and returns HTTP 400 "instanceId is required" if called without one. The instance picker in the UI drives this param - there is no “all instances at once” view.

/memories also accepts sourceConversationId, which narrows the list to the memories extracted from one conversation. The screen reads it from its own URL and shows it as a removable chip, so a link can open the list already narrowed to a conversation under investigation.

Manual creation

The New memory button inserts a memory by hand; it requires the memory:write permission (reading the list requires memory:read). Fill in:

  • Content (free text)
  • Category (dropdown)
  • Importance (1–10)
  • Optional source conversation id

Manual memories are immediately available to the assistant on the next turn.

Automatic extraction

When memoryEnabled is on for an instance, the engine runs an extractor in the background after every assistant response:

  1. The supervisor finishes its reply.
  2. A fire-and-forget task fires.
  3. The extractor takes the last 15 messages of the conversation, calls the LLM (tier fast) with an extraction prompt that returns structured JSON, and gets back a list of (content, category, importance) triples.
  4. Each triple is embedded with the instance embedder.
  5. For each triple, a deduplication check runs: cosine similarity above the agent’s Memory dedup threshold (Advanced → Behaviour overrides, default 0.90) against an existing memory triggers an UPDATE; otherwise an INSERT.

The user never waits for extraction. If extraction crashes, the conversation is not affected.

Deletion

Delete a memory from its row (trash icon) or bulk-delete from the list. Memories are not soft-deleted; the row is gone immediately.

Memories are also deleted with the conversation they came from. Deleting a conversation, and the unattended retention purge, both remove the memories scoped to it - a conversation-scoped memory does not outlive its conversation.

Last updated on