7797ff88df
- Introduced a new reference document for streaming output issues, detailing the differences between streaming APIs and providing solutions for common problems. - Created a structured output issues reference, outlining the use of `with_structured_output`, `response_format`, and schema enforcement strategies. - Added a user query convention guide to standardize structured questions for skill authors, including block types for queries and data gathering. - Implemented a template for a chat model, encapsulating API key management, request payload construction, and response handling. - Established a symlink for the LangChain dev guide in the Claude skills directory for easier access. - Initialized a skills lock file to manage dependencies and versions for the LangChain dev guide.
265 lines
16 KiB
Markdown
265 lines
16 KiB
Markdown
# ContextSeek Middleware — Use Case Scenarios
|
||
|
||
Every scenario below answers one question: **"I'm building X — is ContextSeekMiddleware the right fit, and how do I wire it in?"** Parameter-level configuration issues that arise after the decision to use the middleware are covered in [contextseek-params.md](contextseek-params.md).
|
||
|
||
---
|
||
|
||
## Issue 1: Agent loses context across sessions — personal assistant or support bot
|
||
|
||
**Keywords**: semantic memory, context loss, cross-session, agent forgets, StoreBackend alternative, vector retrieval, passive memory, persistent memory
|
||
|
||
You're building a **personal coding assistant** or a **customer support bot**. Users notice the agent "forgets" everything between sessions: an architecture decision made last week ("we chose OceanBase over Redis because of HTAP requirements") is unknown to the agent today; a support user who already explained their account type and preferred language has to explain it again on every new conversation.
|
||
|
||
The instinct is to reach for `memory=` and `StoreBackend` (from Deep Agents). That works — but only for knowledge the agent actively chooses to write down. It degrades when conversation history is long or when the agent doesn't "know" what's worth remembering. What's missing is a passive retrieval layer that automatically surfaces semantically relevant history before each model call without any agent involvement.
|
||
|
||
`ContextSeekMiddleware` sidecars the agent loop transparently:
|
||
|
||
1. **Before every model call**: retrieves the top-k semantically relevant items from the vector store and appends them to the system message as a `[Relevant Context]` block.
|
||
2. **After every final answer**: stores the Q+A pair for future retrieval (skips intermediate tool-call turns to avoid noise).
|
||
|
||
The agent is never aware of either step.
|
||
|
||
- **Install**:
|
||
```bash
|
||
# Inside the agentseek project
|
||
pip install "agentseek[context]"
|
||
|
||
# Standalone
|
||
pip install contextseek contextseek-bridges-langchain
|
||
```
|
||
- **Configure via env vars** (copy from `.env.example`, section "ContextSeek semantic context layer"). All constructor parameters are optional — the middleware reads from env when nothing is passed:
|
||
```bash
|
||
# seekdb = local persistent storage, built-in ONNX embedder (no API key needed)
|
||
AGENTSEEK_CTX_STORAGE_BACKEND=seekdb
|
||
|
||
# Optional LLM for richer L1 summaries
|
||
AGENTSEEK_CTX_LLM_PROVIDER=openai
|
||
AGENTSEEK_CTX_LLM_MODEL=gpt-4o-mini
|
||
```
|
||
The `AGENTSEEK_CTX_*` prefix is automatically aliased to the env vars contextseek reads internally — no credential duplication.
|
||
- **Minimum integration** (zero constructor arguments):
|
||
```python
|
||
from langchain.agents import create_agent
|
||
from contextseek.bridges.langchain.middleware import ContextSeekMiddleware
|
||
|
||
agent = create_agent(
|
||
model=model,
|
||
tools=[...],
|
||
middleware=[ContextSeekMiddleware()],
|
||
)
|
||
```
|
||
- **Sharing the agent's own model and embedder** (avoids a second model instantiation):
|
||
```python
|
||
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
|
||
|
||
model = ChatOpenAI(model="gpt-4o")
|
||
embedder = OpenAIEmbeddings(model="text-embedding-3-small")
|
||
|
||
agent = create_agent(
|
||
model=model,
|
||
tools=[...],
|
||
middleware=[ContextSeekMiddleware(model=model, embedder=embedder)],
|
||
)
|
||
```
|
||
- **Lessons learned**: `ContextSeekMiddleware` and `memory=` (StoreBackend) solve different problems and can coexist in the same agent. Use `memory=` when the agent needs to explicitly read and write structured notes it controls. Use `ContextSeekMiddleware` when you want the agent's accumulated conversation history to automatically inform future answers — no agent-side file management required.
|
||
|
||
**Common configuration problems for this scenario** → see [contextseek-params.md](contextseek-params.md): scope isolation (multi-user context bleeding), auto_compact throttling and graceful shutdown, retrieval_tags / min_score filtering noisy context, tool_arg_overrides for injecting runtime arguments.
|
||
|
||
---
|
||
|
||
## Issue 2: Multi-tool data-pipeline agent — auditing tool call decisions
|
||
|
||
**Keywords**: tool provenance, audit trail, tool call history, data pipeline agent, record_tool_calls, why did agent choose this query, compliance, tool decision tracing
|
||
|
||
You're building a **data analysis agent** that, on each task, calls a chain of tools: `query_db` → `run_sql` → `transform_data` → `generate_chart` — typically 5–10 tool invocations per turn. A compliance requirement asks: *"Why did the agent choose that particular SQL query? Were the tool arguments reasonable?"* Your team also needs to reproduce past analyses from stored tool traces.
|
||
|
||
`ContextSeekMiddleware` with `record_tool_calls=True` writes a structured record for each tool invocation into the vector store. Each record captures:
|
||
|
||
- `tool` — tool name
|
||
- `args` — arguments passed (after any `tool_arg_overrides` are applied)
|
||
- `result` — the ToolMessage content
|
||
- `rationale` — the AIMessage text that preceded the tool call (the model's stated reasoning)
|
||
- `task` — the originating user message
|
||
|
||
These records are retrievable by future agent turns: *"What SQL did we use for the Q3 revenue report last month?"* can return the exact query with its rationale.
|
||
|
||
- **Integration**:
|
||
```python
|
||
middleware = ContextSeekMiddleware(
|
||
model=model,
|
||
embedder=embedder,
|
||
auto_store=True, # store final Q+A pairs
|
||
record_tool_calls=True, # additionally store each tool invocation
|
||
)
|
||
agent = create_agent(model=model, tools=[query_db, run_sql, transform_data, generate_chart], middleware=[middleware])
|
||
```
|
||
- **Retrieval-only mode** — read historical tool traces without writing new ones (e.g. a read-only audit agent):
|
||
```python
|
||
ContextSeekMiddleware(
|
||
model=model,
|
||
embedder=embedder,
|
||
auto_store=False,
|
||
record_tool_calls=False, # retrieval only; no writes
|
||
)
|
||
```
|
||
- **Lessons learned**: `record_tool_calls=True` multiplies write volume by the average number of tool calls per turn. Each call triggers the full summarizer + embedding + DB write pipeline. For a 5-tool agent, that is 5× the LLM cost per turn compared to `auto_store=True` alone. Only enable it when you actually need per-tool provenance.
|
||
|
||
**Common configuration problems for this scenario** → see [contextseek-params.md](contextseek-params.md): auto_store / record_tool_calls write volume and cost spikes.
|
||
|
||
---
|
||
|
||
## Issue 3: Research agent accumulates raw notes — cross-topic pattern discovery
|
||
|
||
**Keywords**: dream, cross-topic pattern, consolidation, divergence, knowledge synthesis, idle-time, hypothesis generation, dreaming, research agent, implicit connections
|
||
|
||
You're building a **literature research agent** that writes dozens of raw research summaries per day across multiple topics. After a week the store holds 200+ items but the agent can only retrieve "known" content — it misses implicit cross-topic connections: "gene editing for Alzheimer's treatment" and "mRNA vaccine T-cell activation" both involve immune regulation mechanisms, but no item makes that bridge explicit. You need the system to synthesize new insights from accumulated material without re-running the full agent.
|
||
|
||
`ctx.dream()` runs offline in two phases:
|
||
|
||
1. **Consolidation** — scans recently active, un-dreamed items. Within a similarity window `(0.35, 0.72)` — related but not duplicates — it synthesizes new `extracted`-stage items that surface implicit patterns. Tagged `dreamed`, `consolidation`.
|
||
2. **Divergence** — generates cross-cluster hypothesis items bridging dissimilar topic clusters. Tagged `dreamed`, `divergence`. Confidence is lower (×0.85 multiplier) — these are hypotheses, not facts.
|
||
|
||
Dream items have `stability=transient` and decay fast. They are "use it or lose it": `ctx.feedback(ref, score=1.0)` on a valuable hypothesis promotes it to a stable item; otherwise it fades within days.
|
||
|
||
- **Trigger dream manually after a research session**:
|
||
```python
|
||
from contextseek import ContextSeek
|
||
|
||
ctx = ContextSeek.from_settings()
|
||
report = ctx.dream(scope="research/immunology")
|
||
consolidation_count = len(report.consolidation.items)
|
||
divergence_count = len(report.divergence.items) if report.divergence else 0
|
||
print(f"Consolidations: {consolidation_count}, Divergences: {divergence_count}")
|
||
```
|
||
- **Review and promote valuable dream items**:
|
||
```python
|
||
scope = "research/immunology"
|
||
# ctx.overview() returns stage distribution counts; dreamed items are filtered by tag separately
|
||
all_items = ctx.items(scope=scope)
|
||
dreamed = [item for item in all_items if "dreamed" in (item.tags or [])]
|
||
|
||
for item in dreamed:
|
||
if human_review_approves(item):
|
||
# feedback() requires a full URI ref, not a bare item id
|
||
ref = ctx.resolver.ref_for(scope, item.id)
|
||
ctx.feedback(ref, scope=scope, score=1.0, reason="confirmed cross-domain insight")
|
||
```
|
||
- **Enable LLM-enhanced dream** (richer synthesis):
|
||
```bash
|
||
DREAM_LLM_ENABLED=true
|
||
# uses the same LLM configured for the agent; no separate key needed
|
||
```
|
||
- **Lessons learned**: By default `ContextSeekMiddleware` does **not** trigger `dream()` — it only handles `compact()`. For research workflows, call `ctx.dream()` explicitly after bulk ingestion sessions, or schedule it via cron / the contextseek daemon lifecycle. If you want automatic dream triggering inside the agent loop, set `auto_dream=True` on the middleware (see [contextseek-params.md](contextseek-params.md) Issue 11 for the dual-gate trigger mechanics). Dream without LLM falls back to keyword-overlap heuristics; results are coarser but still useful for surface-level clustering.
|
||
|
||
**Common configuration problems for this scenario** → see [contextseek-params.md](contextseek-params.md): dream trigger conditions not met (min_items, cooldown), dream-generated items disappearing due to transient stability.
|
||
|
||
---
|
||
|
||
## Issue 4: SRE incident postmortem agent — tracing knowledge confidence and conflicts
|
||
|
||
**Keywords**: evidence_chain, provenance, confidence propagation, conflict detection, SRE, postmortem, audit, upstream, chain_confidence, broken links, knowledge trustworthiness
|
||
|
||
You're building an **incident postmortem agent** that accumulates observations during a live incident: "Alert A fired at 14:03", "Log B shows connection timeouts", "Analysis: timeouts caused by DB connection pool exhaustion", "Recommendation: lower `connection_timeout` to 500ms". Each item is derived from the previous. A week later, during a recurring-incident review, an engineer asks: *"Is this recommendation actually well-supported? Were there any conflicting signals? How confident should we be in this diagnosis?"*
|
||
|
||
`ctx.evidence_chain()` constructs a full DAG starting from any item, traversing `derived_from`, `supported_by`, `merged_from` (positive) and `refuted_by` (negative) links. It returns:
|
||
|
||
- `overall_confidence` — propagated confidence score (Noisy-OR for supports, penalty for refutations)
|
||
- `critical_path` — `list[str]` of item ids forming the highest-weight inference path
|
||
- `conflicts` — `list[ConflictReport]`; each has `item_id`, `refuter_id`, `refutation_strength`
|
||
- `broken_links` — `list[str]` of item ids that no longer exist in the store
|
||
- `nodes` / `edges` — full DAG for visualization
|
||
- `needs_reverification` — `bool`; `True` when `overall_confidence < 0.4`
|
||
|
||
- **Write items with explicit derivation links**:
|
||
```python
|
||
from contextseek import ContextSeek
|
||
from contextseek.domain.links import Link, LinkType
|
||
|
||
ctx = ContextSeek.from_settings()
|
||
scope = "incidents/2026-06-01"
|
||
|
||
alert = ctx.add("Alert A fired at 14:03: p99 latency > 2s", scope=scope, source="pagerduty")
|
||
log = ctx.add("Log B: connection pool exhausted (pool_size=10, wait_timeout=30s)",
|
||
scope=scope, source="datadog",
|
||
links=[Link(target_id=alert.id, relation=LinkType.supported_by)])
|
||
analysis = ctx.add("Root cause: DB connection pool exhausted under 50-rps load",
|
||
scope=scope, source="agent_inference",
|
||
links=[Link(target_id=log.id, relation=LinkType.derived_from)])
|
||
rec = ctx.add("Recommendation: lower connection_timeout to 500ms",
|
||
scope=scope, source="agent_inference",
|
||
links=[Link(target_id=analysis.id, relation=LinkType.derived_from)])
|
||
```
|
||
- **Evaluate the recommendation's trustworthiness**:
|
||
```python
|
||
# evidence_chain and chain_confidence require a full URI ref, not a bare item id
|
||
ref = ctx.resolver.ref_for(scope, rec.id)
|
||
chain = ctx.evidence_chain(ref, scope=scope)
|
||
print(f"Confidence: {chain.overall_confidence:.2f}") # e.g. 0.71
|
||
if chain.conflicts:
|
||
# conflicts is list[ConflictReport]; each has item_id, refuter_id, refutation_strength
|
||
for c in chain.conflicts:
|
||
print(f"Conflicting evidence: {c.refuter_id} refutes {c.item_id} (strength={c.refutation_strength:.2f})")
|
||
if chain.overall_confidence < 0.4:
|
||
print("Low confidence — needs human review before applying")
|
||
```
|
||
- **Quick check without the full DAG**:
|
||
```python
|
||
ref = ctx.resolver.ref_for(scope, rec.id)
|
||
score = ctx.chain_confidence(ref, scope=scope)
|
||
# returns a float; same traversal as evidence_chain but no DAG construction overhead
|
||
```
|
||
- **Lessons learned**: the agent itself doesn't need to call `evidence_chain` — it's a postmortem / audit API. Wire it into your review pipeline or a separate audit agent that periodically evaluates low-confidence recommendations. Writing `links=` when calling `ctx.add()` is optional but unlocks this entire capability; without links, `evidence_chain` sees only isolated nodes.
|
||
|
||
**Common configuration problems for this scenario** → see [contextseek-params.md](contextseek-params.md): evidence_chain vs chain_confidence — when to use which.
|
||
|
||
---
|
||
|
||
## Issue 5: Enterprise knowledge migration — agent is retrieval-ready on day one
|
||
|
||
**Keywords**: pre-populate, cold start, bulk import, DataPlug, RAGPlug, PowerMemPlug, existing knowledge, seed context, initial corpus, plug, knowledge migration
|
||
|
||
You're migrating an enterprise knowledge base to a ContextSeek-backed agent. The existing data is 100 k+ FAQ entries, historical support tickets, and internal wiki pages — currently stored in a RAG vector store or PowerMem. The new agent cannot wait months for `auto_store` to accumulate conversation history; it needs to be able to retrieve from all of this on day one.
|
||
|
||
`ctx.plug()` consumes a `DataPlug` (a streaming iterator of `RawEvent` objects) and routes each event through the same full pipeline as `ctx.add()`: summarization, embedding, conflict detection, and persistence. Built-in plugs cover the most common sources:
|
||
|
||
| Plug class | Source |
|
||
|------------|--------|
|
||
| `RAGPlug` | Existing RAG / vector store chunks |
|
||
| `PowerMemPlug` | PowerMem memory store |
|
||
| `TracePlug` | Execution traces / agent logs |
|
||
| `MCPToolImporter` | MCP tool definitions → `skill` stage |
|
||
| `OpenAIFunctionImporter` | OpenAI function schemas → `skill` stage |
|
||
|
||
- **Import from an existing RAG store**:
|
||
```python
|
||
from contextseek import ContextSeek
|
||
from contextseek.plugs import RAGPlug
|
||
|
||
ctx = ContextSeek.from_settings()
|
||
|
||
# RAGPlug accepts a list of dicts; each dict needs at least "content" or "page_content"
|
||
docs = existing_vector_store.similarity_search("*", k=10000)
|
||
rag_plug = RAGPlug(
|
||
documents=[{"content": d.page_content, "metadata": d.metadata} for d in docs],
|
||
source_name="wiki-v2",
|
||
)
|
||
ctx.plug(rag_plug, scope="company/knowledge")
|
||
```
|
||
- **Import from PowerMem**:
|
||
```python
|
||
from contextseek.plugs import PowerMemPlug
|
||
|
||
# PowerMemPlug.from_records() accepts dicts from PowerMem get_all/search results
|
||
records = powermem_instance.get_all(user_id="shared") # yields list of dicts
|
||
plug = PowerMemPlug.from_records(records, source_prefix="powermem")
|
||
ctx.plug(plug, scope="company/support-history")
|
||
```
|
||
- **Run compact after bulk import** to consolidate and promote stages before the agent goes live:
|
||
```python
|
||
ctx.compact(scope="company/knowledge")
|
||
```
|
||
- **Combine pre-populated knowledge with live agent writes** — plug for the initial corpus, then deploy the agent with `ContextSeekMiddleware` writing new Q+A pairs into the same scope. The two pipelines are additive.
|
||
- **Lessons learned**: `plug()` is for one-time or scheduled batch ingestion outside the agent loop. `ContextSeekMiddleware` handles continuous, per-turn ingestion inside the agent loop. Use both: `plug()` to seed the store, middleware to keep it growing. Run `compact()` after large bulk imports before the first retrieval to maximize retrieval quality.
|
||
|
||
**Common configuration problems for this scenario** → see [contextseek-params.md](contextseek-params.md): DataPlug vs manual ctx.add() — which to use for bulk import; plug() scope priority and stage inference.
|