Skip to main content
Connic
Build

Retrieval Tools

Give your agents persistent memory with store, query, and delete operations.

Last updated

Retrieval tools vs. Database tools

Connic provides two storage systems for agents. They serve different purposes and are often used together.

Retrieval toolsthis page

Stores text and finds it by meaning. Content is turned into vector embeddings, so a query for "cancellation rules" can surface a document titled "return and refund policy".

Use when:

  • Searching unstructured text (FAQs, docs, notes)
  • Results should be ranked by relevance, not filtered exactly
  • The agent needs long-term memory it can recall naturally
  • You don't know the exact query in advance
Database toolssee docs

Stores structured data and finds it by exact field values. Query with operators like $gt, $in, or $and against any field in any collection.

Use when:

  • Data is structured (orders, users, events, records)
  • You need exact lookups, counts, or filters by field
  • The agent creates, reads, updates, or deletes records
  • Data volume or structure calls for real querying
Rule of thumb: if you'd Google it, use Retrieval. If you'd look it up in a spreadsheet, use the Database.

Retrieval tools let your agents store and retrieve information that persists across runs. Store documents, FAQs, or any text, then query it naturally. The system finds relevant content based on meaning, not just keywords.

Storing content is asynchronous: uploads are accepted first, processed as ingestion jobs in the background, and become retrievable after indexing completes. Queries and metadata-filter deletes only see entries whose ingestion job has finished — an entry stored moments ago may not be visible yet, so sequence cleanup that depends on fresh entries after a bulk store has finished indexing.

Namespaces let you organize content into a hierarchy using dot-separated names (e.g., "policies.hr.leave", "products.pricing"). Querying a parent namespace also searches all sub-namespaces. Entry IDs are unique within a namespace. Max depth is 10 levels.

You can view, search, and manage all indexed content from the Retrieval page in your project dashboard.

Custom tool wrappers

A common pattern is to wrap the retrieval tools in domain-specific functions so the agent callsremember or recall instead of working with namespaces and entry IDs directly. The routing logic is encoded once in the wrapper, keeping the agent focused on its task. You can also expose the tools directly in YAML for simpler agents. It's up to you.

tools/memory.py
from connic.tools import retrieval_store, retrieval_query, retrieval_delete

async def remember(content: str, topic: str) -> dict:
    """Store information under a topic."""
    return await retrieval_store(content=content, namespace=topic)

async def recall(question: str, topic: str | None = None) -> list:
    """Search indexed content for relevant information."""
    result = await retrieval_query(question, namespace=topic)
    return result["results"]

async def forget(entry_id: str, topic: str) -> dict:
    """Remove an entry from a topic."""
    return await retrieval_delete(entry_id=entry_id, namespace=topic)

Use the custom tools in the agent YAML:

agents/my-agent.yaml
name: my-agent
model: gemini/gemini-2.5-flash
tools:
  - memory.remember
  - memory.recall
  - memory.forget
retrieval_query
Search the retrieval. Finds the most relevant content based on meaning, not just exact keywords.

Parameters

ParameterTypeDefaultDescription
querystringrequiredThe search query. Be specific for best results
namespacestring?nullFilter results to a specific namespace; sub-namespaces are included
min_scorefloat0.7Minimum relevance score (0.0 to 1.0)
max_resultsint3Maximum number of results to return
metadata_filterdict?nullMongoDB-style filter applied to entry metadata — same operators as db_find ($eq, $ne, $in, $gt, $exists, $or, …). Dot-notation for nested keys

Return Value

Returns matching results with: content, entry_id, score (relevance 0-1), namespace, metadata

Examples

tools/search.py
# Simple query
result = await retrieval_query("What is the refund policy?")

# Results contain matching content with relevance scores
for item in result["results"]:
    print(f"[{item['score']:.0%}] {item['content'][:100]}...")
tools/search.py
# Filter by namespace
result = await retrieval_query(
    query="How do I reset my password?",
    namespace="support"
)

# Filter by metadata — same MongoDB-style operators as the database tools
# (\$eq, \$ne, \$gt, \$gte, \$lt, \$lte, \$in, \$nin, \$exists,
#  \$regex, \$contains, \$elemMatch, \$and, \$or, \$nor, \$not).
# Bare values are equality shorthand.
result = await retrieval_query(
    query="latest changes",
    namespace="products",
    metadata_filter={
        "product_id": "X",
        "status": {"$in": ["active", "pending"]},
    },
)

# Adjust score threshold and result count
result = await retrieval_query(
    query="pricing information",
    min_score=0.5,    # Lower threshold = more results
    max_results=10    # Return up to 10 results
)
retrieval_store
Queue new information for async indexing in the retrieval.

Parameters

ParameterTypeDefaultDescription
contentstringrequiredThe text content to store, queued for asynchronous indexing
entry_idstring?autoCustom ID for the entry (UUID generated if omitted)
namespacestring?nullCategory for organizing content
metadatadict?nullAdditional key-value data to store

Return Value

Returns: entry_id, job_id, status, queued, and success. Storage is asynchronous, so the entry becomes searchable after the background job completes.

Examples

tools/store.py
# Simple store (auto-generated ID)
result = await retrieval_store(
    content="The company refund policy allows returns within 30 days."
)
# Returns immediately with a queued job
# {"entry_id": "abc123...", "job_id": "...", "status": "pending", "queued": true, "success": true}
tools/store.py
# Store with custom ID for later updates
result = await retrieval_store(
    content="Q1 sales target is $1M with focus on enterprise.",
    entry_id="q1-sales-target",
    namespace="planning",
    metadata={"quarter": "Q1", "year": "2024"}
)

# Store user preferences
result = await retrieval_store(
    content="User prefers dark mode and metric units.",
    entry_id="user-preferences",
    namespace="user_data"
)
retrieval_delete
Remove entries by entry ID, or bulk-delete a namespace with an optional metadata filter.

Parameters

ParameterTypeDefaultDescription
entry_idstring?nullID of a single entry to delete. Omit when bulk-deleting by namespace
namespacestring?nullNamespace to scope the deletion. Required for metadata-filter deletes; sub-namespaces are included
metadata_filterdict?nullMongoDB-style filter applied to entry metadata — same operators as db_find ($eq, $ne, $in, $or, …). Requires namespace

Return Value

Returns: ok (success), deleted_chunks (number of underlying chunks removed). Provide either entry_id, or a namespace (optionally with metadata_filter). Entry IDs are unique per namespace, so specify the namespace if the same ID exists in multiple.

Examples

tools/cleanup.py
# Delete a single entry by id
result = await retrieval_delete(entry_id="old-product-info")

# Delete a single entry scoped to a namespace
result = await retrieval_delete(
    entry_id="q1-sales-target",
    namespace="planning",
)

# Bulk delete by metadata — uses the same MongoDB-style filter syntax
# as the database tools, so operators like \$ne / \$in / \$not work
result = await retrieval_delete(
    namespace="products",
    metadata_filter={"product_id": "X"},
)

# Orphan cleanup pattern: re-ingest a source, then delete every entry
# in scope from previous runs (everything that isn't the current run_id).
# Run the delete only after the re-ingest jobs have completed: entries
# still being indexed keep their previous run_id and would be deleted.
result = await retrieval_delete(
    namespace="confluence",
    metadata_filter={
        "root_page_id": page_id,
        "run_id": {"$ne": current_run_id},
    },
)

# Wipe an entire namespace subtree (sub-namespaces included)
result = await retrieval_delete(namespace="meetings")
retrieval_list_namespaces
Discover how content is organized by listing namespaces and their hierarchy.

Parameters

ParameterTypeDefaultDescription
parentstring?nullParent namespace to list children of. If omitted, lists top-level namespaces
depthint1How many levels deep to list (1 = direct children, 0 = all descendants, max 10)

Return Value

Without parent: returns a list of namespace objects with name, entry_count, total_entry_count, has_children.

With parent: returns parent (info about the parent) and namespaces (list of children).

Examples

tools/namespaces.py
# List top-level namespaces
result = await retrieval_list_namespaces()
# Returns: [{"name": "policies", "entry_count": 5, "total_entry_count": 12, "has_children": true}, ...]

# Drill into a specific namespace
result = await retrieval_list_namespaces(parent="policies")
# Returns: {"parent": {...}, "namespaces": [{"name": "policies.hr", ...}, ...]}

# List all namespaces at all depths
result = await retrieval_list_namespaces(depth=0)

Complete Agent Example

agents/retrieval-agent.yaml
version: "1.0"

name: retrieval-agent
model: gemini/gemini-2.5-pro
description: "Agent with persistent memory"
system_prompt: |
  You are an assistant with access to a retrieval.
  Always search the retrieval first before answering.

tools:
  - retrieval_query
  - retrieval_store
  - retrieval_delete
  - retrieval_list_namespaces
Tips
  • Use descriptive entry IDs so you can update entries by ID later
  • Use namespaces to scope searches and avoid unrelated results
  • Query before answering so the agent uses indexed content
  • Test queries directly in the Retrieval dashboard