Search Context
Query the project-scoped reusable answer store before you escalate.
Search is the cheapest path and should usually happen first. Results are
scoped to the configured project, so separate projects can keep similar
questions and answers isolated. In the Admin UI this is the search box on
the Context Hub page; the SDK calls it context.search.
When to use search
- At the start of a support, ops, or assistant workflow.
- Before creating a new context request.
- After previous requests have already produced reusable answers.
Basic search
Inspect the returned project-scoped items and decide whether your app already has enough evidence to answer.
from valmar import ContextResource
results = client.context.search("deployment process")
for hit in results.hits:
resource = client.context.read(hit.reference)
print(f"{hit.title} ({hit.score})")
if isinstance(resource, ContextResource):
print(resource.content_md)
else:
print(resource.data)const results = await valmar.context.search({ query: "deployment process" });
for (const hit of results.hits) {
const resource = await valmar.context.read(hit.reference);
console.log(`${hit.title} (${hit.score})`);
if ("contentMd" in resource) {
console.log(resource.contentMd);
} else {
console.log(resource.data);
}
}Inspect provenance and source chat
Every Context Hub module returns the same trace envelope. If a resource was created from an employee conversation, the trace includes the source thread and authorized chat transcript.
if not results.hits:
raise RuntimeError("No context matched the query")
hit = results.hits[0]
trace = client.context.trace(hit.reference)
for thread_id in trace.source_thread_ids:
conversation = trace.conversations[thread_id]
for message in conversation.messages:
print(message.role, message.parts)With optional parameters
Tighten the result set when you need fewer hits or answers that came from specific people.
results = client.context.search(
"how do we roll back a failed migration",
limit=3,
modules=["unstructured"],
source_member_ids=[
"55555555-5555-4555-8555-555555555555",
"66666666-6666-4666-8666-666666666666",
],
)
print(f"{len(results.hits)} match(es)")
for hit in results.hits:
print(f"- {hit.title} (score={hit.score})")const results = await valmar.context.search({
query: "how do we roll back a failed migration",
limit: 3,
modules: ["unstructured"],
sourceMemberIds: [
"55555555-5555-4555-8555-555555555555",
"66666666-6666-4666-8666-666666666666",
],
});
console.log(`${results.hits.length} match(es)`);
for (const hit of results.hits) {
console.log(`- ${hit.title} (score=${hit.score})`);
}| Parameter | Default | What it does |
|---|---|---|
limit | 10 | Maximum number of items returned. Lower it when you only want the top hit. |
modules | all | Limit search to named Context Hub modules. Searchable modules are unstructured and structured; project settings determine which are active. |
source_member_ids / sourceMemberIds | none | Only return items credited to these source experts. |
For structured hits, excerpt remains a string and contains a valid JSON object keyed by the
project's configured structured fields. Parse it with json.loads(hit.excerpt) in Python or
JSON.parse(hit.excerpt) in TypeScript when you need the individual values. Unstructured excerpts
remain plain text.
Use Valmar as a tool in your agent
Search is well-suited to be exposed as an agent tool: the model decides when context is missing and calls Valmar before continuing. There are two ways to do this.
As an MCP server
If your agent runtime already speaks MCP, point it at the Valmar MCP endpoint. See MCP Integration. Nothing else to write.
As a custom tool (LangChain example)
When you want explicit control over how the model sees the tool — its
description, schema, return formatting — wrap the SDK call yourself. The
example below uses LangChain's @tool decorator
and create_agent, but the same shape works for any agent library
(LlamaIndex FunctionTool, OpenAI's function-calling API, the Anthropic
SDK's tools=[...] parameter, CrewAI tools, etc.) — wrap client.context.search
in whatever the library calls a tool.
uv add langchain langchain-openaiimport os
from langchain.agents import create_agent
from langchain.tools import tool
from valmar import Valmar
valmar = Valmar(
api_key=os.environ["VALMAR_API_KEY"],
organization_id=os.environ["VALMAR_ORGANIZATION_ID"],
project_id=os.environ["VALMAR_PROJECT_ID"],
base_url=os.environ["VALMAR_BASE_URL"],
)
@tool
def search_company_context(query: str, limit: int = 5) -> str:
"""Search the company's reusable context for an existing answer.
Use this BEFORE asking the user clarifying questions or before
creating a new context request. Returns a short list of titles
and excerpts with a relevance score.
Args:
query: Natural-language question to look up.
limit: Maximum number of results (default 5).
"""
results = valmar.context.search(query, limit=limit)
if not results.hits:
return "No matching context found."
return "\n\n".join(
"\n".join(
[
f"## {hit.title} (score={hit.score})",
(
"Experts: " + ", ".join(hit.metadata.expert_names)
if hit.metadata and hit.metadata.expert_names
else "Experts: unknown"
),
f"Approved at: {hit.metadata.approved_at if hit.metadata else None}",
hit.excerpt,
]
)
for hit in results.hits
)
agent = create_agent(
"openai:gpt-5.4",
tools=[search_company_context],
)Pair with gather
This is half of the loop. Pair search_company_context with a
request_context tool from
Create Context Requests so the agent can fall back to
a real person when search comes up empty.