Files
topoteretes--cognee/examples/demos/agentic_session_context_demo.py
wehub-resource-sync c889a57b6b
Test Suites / Build CI Environment (push) Has been cancelled
Test Suites / Basic Tests (push) Has been cancelled
Test Suites / End-to-End Tests (push) Has been cancelled
Test Suites / CLI Tests (push) Has been cancelled
Test Suites / Slow End-to-End Tests (push) Has been cancelled
Test Suites / Graph Database Tests (push) Has been cancelled
Test Suites / Vector DB Tests (push) Has been cancelled
Test Suites / Temporal Graph Test (push) Has been cancelled
Test Suites / Search Test on Different DBs (push) Has been cancelled
Test Suites / Example Tests (push) Has been cancelled
Test Suites / Notebook Tests (push) Has been cancelled
Test Suites / OS and Python Tests Ubuntu (push) Has been cancelled
Test Suites / OS and Python Tests Extended (push) Has been cancelled
Test Suites / LLM Test Suite (push) Has been cancelled
Test Suites / S3 File Storage Test (push) Has been cancelled
Test Suites / Run Integration Tests (push) Has been cancelled
Test Suites / MCP Tests (push) Has been cancelled
Test Suites / Docker Compose Test (push) Has been cancelled
Test Suites / Docker CI test (push) Has been cancelled
Test Suites / Relational DB Migration Tests (push) Has been cancelled
Test Suites / Distributed Cognee Test (push) Has been cancelled
Test Suites / DB Examples Tests (push) Has been cancelled
Test Suites / Test Completion Status (push) Has been cancelled
Test Suites / Claude Code Review (push) Has been cancelled
Test Suites / basic checks (push) Has been cancelled
build | Build and Push Cognee MCP Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
build | Build and Push Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.11) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.12) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (kuzu, kuzu) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (neo4j, neo4j) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Examples (push) Has been cancelled
Weighted Edges Tests / Code Quality for Weighted Edges (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:02:24 +08:00

516 lines
18 KiB
Python

"""Agentic session-context demo.
The agentic counterpart to ``live_session_context_feedback_demo.py``. Where the QA demo learns
lessons from conversation feedback, this one learns ``agent``-profile lessons from an agent's
tool/action traces — and, like that demo, the real lesson extraction is LLM-backed.
Run (full, requires a configured LLM provider):
uv run python examples/demos/agentic_session_context_demo.py
Run a reduced, no-LLM version (deterministic failure capture + recall only):
uv run python examples/demos/agentic_session_context_demo.py --offline
What it shows:
1. Processing agentic traces
Store five traces one by one. For demo readability only, the periodic extractor uses
interval=2 and overlap=1, so automatic LLM extraction triggers after traces 2 and 4.
The demo prints session memory after every trace.
2. Distillation
Run final batch context extraction for the pending tail, then distill_session() rewrites
accepted guidance into markdown documents and add+cognifies those documents into the graph.
3. Recall
recall(scope=["session_context"], context_profile="agent") returns the active guidance
read-only; context_profile="qa" returns nothing because this demo wrote no QA-profile
entries; scope=["trace"] still returns the raw evidence.
Mental model:
raw traces -> active agent guidance -> recall prompt context
|
+-> distillation -> session_learnings graph documents
Exact lesson wording varies by model.
"""
import argparse
import asyncio
import json
import os
import sys
from typing import Any
os.environ["CACHING"] = "true"
os.environ["CACHE_BACKEND"] = "fs"
os.environ["AUTO_FEEDBACK"] = "true"
os.environ["ENABLE_BACKEND_ACCESS_CONTROL"] = "false"
os.environ.setdefault("LOG_LEVEL", "ERROR")
import cognee
from cognee.memory import TraceEntry
from cognee.infrastructure.session.get_session_manager import get_session_manager
from cognee.modules.users.methods import get_default_user
DATASET_NAME = "agentic_session_context_demo"
SESSION_ID = "agentic_demo_session"
OUTPUT_PATH = "agentic_session_context_demo_output.json"
DEMO_TRACE_EXTRACTION_INTERVAL = 2
DEMO_TRACE_EXTRACTION_OVERLAP = 1
# One agent working a task. The full demo uses a tiny interval so five traces show:
# live capture, automatic periodic LLM extraction, a pending tail, and final flush.
TRACES = [
{
"origin_function": "run_tests",
"status": "error",
"method_params": {"command": "pytest -q"},
"error_message": "ModuleNotFoundError: No module named 'dotenv'",
},
{
"origin_function": "run_command",
"status": "success",
"method_params": {"command": "uv sync"},
"method_return_value": "Installed 42 packages in 1.2s",
},
{
"origin_function": "run_tests",
"status": "success",
"method_params": {"command": "uv run pytest -q"},
"method_return_value": "188 passed in 4.1s",
},
{
"origin_function": "read_file",
"status": "success",
"method_params": {"path": "pyproject.toml"},
"method_return_value": "[project] name = 'demo' # managed with uv",
},
{
"origin_function": "run_lint",
"status": "error",
"method_params": {"command": "uv run ruff check ."},
"error_message": "F401 'os' imported but unused",
},
]
def progress(message: str):
print(f"[agentic-demo] {message}", file=sys.stderr, flush=True)
def parse_args():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--offline",
action="store_true",
help="Skip LLM extraction/distillation; show deterministic trace capture and recall.",
)
return parser.parse_args()
async def main():
args = parse_args()
await setup_demo_data()
user = await get_default_user()
await reset_demo_session(user)
output = {
"dataset": DATASET_NAME,
"session_id": SESSION_ID,
"mode": "offline" if args.offline else "full",
}
if not args.offline:
output["demo_extraction_window"] = {
"min_new_traces": DEMO_TRACE_EXTRACTION_INTERVAL,
"overlap": DEMO_TRACE_EXTRACTION_OVERLAP,
}
if args.offline:
processing_explanation = (
"Store five traces one by one. Offline mode skips LLM extraction, so only "
"deterministic live failure context entries appear."
)
else:
processing_explanation = (
"Store five traces one by one. Session memory is printed after every trace; "
"trace 2 and trace 4 trigger a periodic extraction pass."
)
print_act_header("1. Processing agentic traces", processing_explanation)
output["act1_processing_agentic_traces"] = await process_agentic_traces(
user, drive_periodic_extraction=not args.offline
)
if not args.offline:
print_act_header(
"2. Distillation",
(
"Run final batch context extraction for pending trace 5, then distill the "
"resulting session-memory entries into graph documents."
),
)
output["act2_distillation"] = await run_distillation_act(user)
print_distillation(output["act2_distillation"])
print_act_header(
"3. Recall",
(
"Recall renders agent-profile session memory read-only. QA-profile context stays "
"empty because this demo only wrote agent context."
),
)
output["act3_recall"] = await run_recall_act(user)
print_recall(output["act3_recall"])
await save_demo_output(output)
async def run_distillation_act(user) -> dict:
"""Flush the pending tail, then run distillation."""
from cognee.infrastructure.session.agent_context_extraction import extract_pending_agent_context
from cognee.modules.session_distillation import distill_session
try:
before_flush = await snapshot(user)
touched_ids = await extract_pending_agent_context(
session_manager=get_session_manager(),
user_id=str(user.id),
session_id=SESSION_ID,
min_new_traces=1,
)
distillation_input = await snapshot(user)
result = await distill_session(SESSION_ID, dataset=DATASET_NAME, user=user)
return {
"flush_touched_entry_ids": touched_ids,
"before_flush": before_flush,
"distillation_input": distillation_input,
"distillation_status": result.status,
"cognified_documents": result.documents,
}
except Exception as exc:
progress(f"Act 2 failed: {exc}")
return {
"flush_touched_entry_ids": [],
"before_flush": await snapshot(user),
"distillation_input": await snapshot(user),
"distillation_status": "failed",
"cognified_documents": [],
"error": str(exc),
}
async def save_demo_output(output: dict):
with open(OUTPUT_PATH, "w", encoding="utf-8") as output_file:
output_file.write(json.dumps(output, indent=2))
print("", file=sys.stderr)
print("--- Saved structured output ---", file=sys.stderr)
print(OUTPUT_PATH, file=sys.stderr)
async def process_agentic_traces(user, *, drive_periodic_extraction: bool) -> dict:
"""Store the demo traces one by one and print session memory after each trace.
When ``drive_periodic_extraction`` is set, call the periodic batch pass with a tiny interval
after each trace. The trace-write path (``SessionManager.add_agent_trace_step``) already runs
this for you every ``TRACE_EXTRACTION_INTERVAL`` traces; we drive it directly with a smaller
interval so a five-trace demo shows the pass firing (after traces 2 and 4) instead of only at
the final flush.
"""
from cognee.infrastructure.session.agent_context_extraction import (
extract_pending_agent_context,
)
trace_states = []
for index, trace in enumerate(TRACES, start=1):
before = await snapshot(user)
await cognee.remember(
TraceEntry(**trace),
dataset_name=DATASET_NAME,
session_id=SESSION_ID,
self_improvement=False,
user=user,
)
if drive_periodic_extraction:
await extract_pending_agent_context(
session_manager=get_session_manager(),
user_id=str(user.id),
session_id=SESSION_ID,
min_new_traces=DEMO_TRACE_EXTRACTION_INTERVAL,
overlap=DEMO_TRACE_EXTRACTION_OVERLAP,
)
after = await snapshot(user)
trace_state = {
"trace_number": index,
"origin_function": trace["origin_function"],
"status": trace["status"],
"trace": trace,
"watermark_before": before["processed_trace_count"],
"watermark_after": after["processed_trace_count"],
"automatic_batch_triggered": (
after["processed_trace_count"] > before["processed_trace_count"]
),
"snapshot": after,
}
trace_states.append(trace_state)
print_trace_memory(trace_state)
return {"trace_states": trace_states}
async def run_recall_act(user) -> dict:
served_before = await served_state(user)
agent_ctx = await cognee.recall(
"what should I know before running tests in this repo?",
scope=["session_context"],
context_profile="agent",
session_id=SESSION_ID,
only_context=True,
user=user,
)
qa_ctx = await cognee.recall(
"what should I know before running tests in this repo?",
scope=["session_context"],
context_profile="qa",
session_id=SESSION_ID,
only_context=True,
user=user,
)
raw_traces = await cognee.recall(
"dotenv",
scope=["trace"],
session_id=SESSION_ID,
only_context=True,
user=user,
)
served_after = await served_state(user)
result = {
"agent_profile_block": block_text(agent_ctx),
"qa_profile_result_count": len(qa_ctx),
"trace_evidence_count": len(raw_traces),
"recall_made_no_writes": served_before == served_after,
}
return result
# --------------------------------------------------------------------------- evidence
async def snapshot(user) -> dict:
traces = await get_session_manager().get_agent_trace_session(
user_id=str(user.id), session_id=SESSION_ID
)
processed_trace_count = await processed_trace_count_for(user)
return {
"trace_count": len(traces),
"processed_trace_count": processed_trace_count,
"pending_trace_count": max(0, len(traces) - processed_trace_count),
"agent_lessons": await agent_lessons_for(user),
}
async def processed_trace_count_for(user) -> int:
from cognee.infrastructure.session.agent_context_extraction import (
TRACE_EXTRACTION_STATE_ID,
TRACE_EXTRACTION_STATE_KIND,
)
rows = await get_session_manager().get_session_context_entries(
user_id=str(user.id), session_id=SESSION_ID
)
for row in rows:
if (
row.get("id") != TRACE_EXTRACTION_STATE_ID
and row.get("kind") != TRACE_EXTRACTION_STATE_KIND
):
continue
try:
return max(0, int(row.get("processed_trace_count") or 0))
except (TypeError, ValueError):
return 0
return 0
async def agent_lessons_for(user) -> list[dict]:
rows = await get_session_manager().get_session_context_entries(
user_id=str(user.id), session_id=SESSION_ID
)
lessons = []
for row in rows:
if row.get("kind", "context") != "context":
continue
if row.get("context_profile", "qa") != "agent":
continue
lessons.append(
{
"section": row.get("section"),
"content": row.get("content"),
"confidence": row.get("confidence"),
"source_trace_ids": row.get("source_trace_ids", []),
"id": row.get("id"),
"harmful_count": row.get("harmful_count", 0),
}
)
return lessons
async def served_state(user) -> dict:
"""Map of agent-lesson id -> last_served_at, to prove recall does not stamp anything."""
rows = await get_session_manager().get_session_context_entries(
user_id=str(user.id), session_id=SESSION_ID
)
return {
row.get("id"): row.get("last_served_at")
for row in rows
if row.get("context_profile", "qa") == "agent"
}
def block_text(recall_result: Any) -> str:
for item in recall_result or []:
content = getattr(item, "content", None)
if content is None and isinstance(item, dict):
content = item.get("content")
if content:
return content
return ""
# --------------------------------------------------------------------------- printing
def print_act_header(title: str, explanation: str):
print("", file=sys.stderr)
print("===", file=sys.stderr)
print(title, file=sys.stderr)
print(explanation, file=sys.stderr)
print("===", file=sys.stderr)
print("", file=sys.stderr)
sys.stderr.flush()
def print_trace_memory(trace_state: dict):
label = f"Session memory content after trace {trace_state['trace_number']}"
if trace_state["automatic_batch_triggered"]:
label += f" ({_distillation_label(trace_state['trace_number'])})"
print("---", file=sys.stderr)
print(label, file=sys.stderr)
print("---", file=sys.stderr)
print_trace_details(trace_state["trace"])
print("", file=sys.stderr)
print_session_memory(trace_state["snapshot"])
print("", file=sys.stderr)
def _distillation_label(trace_number: int) -> str:
labels = {
2: "first batch context extraction",
4: "second batch context extraction",
}
return labels.get(trace_number, "batch context extraction")
def print_distillation(result: dict):
before = result["before_flush"]
distillation_input = result["distillation_input"]
print(
f"final batch context extraction: pending "
f"{before['pending_trace_count']} -> {distillation_input['pending_trace_count']}",
file=sys.stderr,
)
print(f"context entries touched: {len(result['flush_touched_entry_ids'])}", file=sys.stderr)
print("", file=sys.stderr)
print("Distillation input: session memory", file=sys.stderr)
print_snapshot(distillation_input)
print("", file=sys.stderr)
print("Graph documents written", file=sys.stderr)
print(f"status: {result['distillation_status']}", file=sys.stderr)
documents = result["cognified_documents"]
if not documents:
print("documents: none", file=sys.stderr)
if result.get("error"):
print(f"error: {result['error']}", file=sys.stderr)
return
for index, document in enumerate(documents, start=1):
print(f"document {index}:", file=sys.stderr)
print(indent_block(document.strip(), prefix=" "), file=sys.stderr)
def print_agent_lessons(agent_lessons: list[dict]):
if not agent_lessons:
print(" (none)", file=sys.stderr)
return
for lesson in agent_lessons:
provenance = "live trace" if lesson["source_trace_ids"] else "batch LLM"
print(
f" - [{lesson['section']}] {lesson['content']} "
f"(confidence={lesson['confidence']}, source={provenance})",
file=sys.stderr,
)
def print_snapshot(snap: dict):
print_session_memory(snap)
def print_trace_details(trace: dict):
print("Trace", file=sys.stderr)
print(f" tool: {trace['origin_function']}", file=sys.stderr)
print(f" status: {trace['status']}", file=sys.stderr)
params = trace.get("method_params") or {}
if params:
print(" input:", file=sys.stderr)
for key, value in params.items():
print(f" {key}: {value}", file=sys.stderr)
if trace.get("method_return_value") is not None:
print(" output:", file=sys.stderr)
print(indent_block(str(trace["method_return_value"]), prefix=" "), file=sys.stderr)
if trace.get("error_message"):
print(" error:", file=sys.stderr)
print(indent_block(str(trace["error_message"]), prefix=" "), file=sys.stderr)
def print_session_memory(snap: dict):
print("Session memory", file=sys.stderr)
if not snap["agent_lessons"]:
print(" entries: none", file=sys.stderr)
return
print(f" entries ({len(snap['agent_lessons'])}):", file=sys.stderr)
print_agent_lessons(snap["agent_lessons"])
def print_recall(result: dict):
print("Agent-profile session memory recall", file=sys.stderr)
print((result["agent_profile_block"] or " (empty)"), file=sys.stderr)
print("", file=sys.stderr)
print(
f"QA-profile session memory results: {result['qa_profile_result_count']}", file=sys.stderr
)
print(f"raw trace recall results: {result['trace_evidence_count']}", file=sys.stderr)
print(f"writes during recall: {not result['recall_made_no_writes']}", file=sys.stderr)
def indent_block(text: str, *, prefix: str) -> str:
return "\n".join(f"{prefix}{line}" for line in text.splitlines())
async def setup_demo_data():
progress("Clearing previous demo state.")
await cognee.prune.prune_data()
await cognee.prune.prune_system(metadata=True)
progress("Creating the demo dataset.")
await cognee.add(["Agentic session-context demo dataset."], dataset_name=DATASET_NAME)
async def reset_demo_session(user):
deleted = await get_session_manager().delete_session(
user_id=str(user.id), session_id=SESSION_ID
)
progress("Old demo session deleted." if deleted else "No previous demo session found.")
if __name__ == "__main__":
asyncio.run(main())