Comprehensive system architecture and critical implementation details.
Cognifold is a dynamic concept graph system that processes event streams and maintains an evolving knowledge representation. It uses LLM agents to analyze events, identify patterns, and build a graph of concepts, intents, and temporal relationships.
┌───────────────────────────────────────────────────────────────────────────────────────────────┐
│ COGNIFOLD SYSTEM │
└───────────────────────────────────────────────────────────────────────────────────────────────┘
═══════════════════════════════════════════════════════════════════════════════════════════════
EVENT PROCESSING (Write Path)
═══════════════════════════════════════════════════════════════════════════════════════════════
┌──────────────┐ ┌──────────────┐
│ Generator │ │ Importer │
│ (LLM) │ │ (Data) │
└──────┬───────┘ └──────┬───────┘
│ │
└────────┬───────────┘
│
▼
┌──────────────┐
│ Event Stream │ (unified Event schema)
└──────┬───────┘
│
▼
┌─────────────────────┐
│ Context Selector │
│ (ContextRanker) │
│ │ ┌─────────────────────────────────────────────┐
│ • PageRank │ │ CONCEPT GRAPH │
│ • Recency decay │ │ ┌───────────────────────────────────────┐ │
│ • Access frequency │◄────►│ │ Nodes (Pydantic models) │ │
│ • Urgency boost │ │ │ ├── event ├── concept │ │
│ │ │ │ ├── intent └── time │ │
│ Output: Context │ │ ├───────────────────────────────────────┤ │
│ Window (top-k) │ │ │ Edges (typed, weighted) │ │
└──────────┬──────────┘ │ │ • 8 semantic types │ │
│ │ │ • Weight: 0.0-1.0 │ │
▼ │ └───────────────────────────────────────┘ │
┌─────────────────────┐ │ │
│ LangGraph Agent │ │ Storage: NetworkX MultiDiGraph │
│ (CognifoldAgent) │ │ Persistence: JSON │
│ │ └──────────────────▲──────────────────────────┘
│ Input: │ │
│ • Event │ │
│ • Hierarchical │ │
│ context window │ │
│ │ │
│ Output: │ │
│ • UpdatePlan │ │
└──────────┬──────────┘ │
│ │
▼ │
┌─────────────────────┐ │
│ Executor │─────────────────────────┘
│ (PlanExecutor) │
│ │
│ • Validates plan │
│ • Atomic execution │
│ • Rollback on fail │
└─────────────────────┘
═══════════════════════════════════════════════════════════════════════════════════════════════
QUERY & RETRIEVAL (Read Path)
═══════════════════════════════════════════════════════════════════════════════════════════════
┌──────────────────┐
│ Natural Language │
│ Query │
└────────┬─────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ MemoryQueryAgent │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Entry Point Selection │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ LEGACY │ │ BM25 │ │ SEMANTIC │ │ HYBRID │ │ │
│ │ │ (keyword) │ │ (inverted │ │ (embedding │ │ (BM25 + │ │ │
│ │ │ │ │ index) │ │ cosine) │ │ RRF fusion)│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ ┌─────────────┐ │ │
│ │ │ AGENTIC │ │ │
│ │ │ (2-round + │ │ │
│ │ │ sufficiency│ │ │
│ │ │ check) │ │ │
│ │ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ GraphTraverser │───►│ QueryScorer │───►│ ContextAssembler │ │
│ │ (BFS + decay) │ │ (relevance) │ │ (format result) │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│ ▲
│ ┌────────────────────┐ │
└───────────►│ CONCEPT GRAPH │◄─────────────────┘
└────────────────────┘
│
▼
┌──────────────────┐
│ QueryResult │
│ • summary │
│ • nodes │
│ • relationships │
└──────────────────┘
═══════════════════════════════════════════════════════════════════════════════════════════════
REPLAY & VISUALIZATION
═══════════════════════════════════════════════════════════════════════════════════════════════
┌──────────────────┐
│ Run Logs │
│ (JSONL) │
│ • events │
│ • plans │
│ • snapshots │
└────────┬─────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ Replay System │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ ReplayLogger │───►│ ReplayParser │───►│ ReplayRenderer │ │
│ │ (capture logs) │ │ (parse states) │ │ (pyvis HTML) │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────┐
│ Interactive │
│ HTML Replay │
│ • play/pause │
│ • step controls │
│ • node colors │
│ • context glow │
└──────────────────┘
All models are Pydantic BaseModel subclasses in src/cognifold/models/.
class Node(BaseModel):
id: str # Unique identifier (e.g., "e-001", "c-001")
type: NodeType # EVENT, CONCEPT, INTENT, TIME
data: dict[str, Any] # Payload (title, description, etc.)
created_at: datetime # Creation timestamp
last_accessed: datetime # For recency scoring
access_count: int # For access frequency scoring
# Explainability (required for non-event nodes)
reasoning: str | None # Why this node was created
grounded_in: list[str] # Event IDs that justify this node
update_history: list[UpdateHistoryEntry] # Audit trailNode Types (NodeType enum):
| Type | Description | ID Prefix |
|---|---|---|
EVENT |
Direct representation of input events | e- |
CONCEPT |
Patterns that emerge from multiple events | c- |
INTENT |
Goals/desires (formerly "action") | i- |
TIME |
Temporal anchors (deadlines, schedules) | t- |
Intent Status Lifecycle (IntentStatus enum):
PENDING → ACTION_SCHEDULED → RESOLVED
↘ REJECTED
↘ DEFERRED
class Edge(BaseModel):
source: str # Source node ID
target: str # Target node ID
edge_type: str | None # Semantic type (None for legacy)
weight: float # 0.0-1.0, defaults by type
created_at: datetime # For edge recency in PageRank
metadata: dict[str, Any] # Optional additional dataEdge Types (BaseEdgeType enum with default weights):
| Type | Weight | Typical Usage |
|---|---|---|
GROUNDS |
0.9 | Event → Concept/Intent (direct evidence) |
CAUSES |
0.9 | Event → Event (causal link) |
TRIGGERS |
0.8 | Concept/Event → Intent |
REINFORCES |
0.7 | Event → Concept (supporting evidence) |
PART_OF |
0.7 | Event/Concept → Concept (structural, subtopic) |
DERIVED_FROM |
0.6 | Concept → Concept |
DEADLINE_FOR |
0.6 | Time → Intent |
RELATED_TO |
0.5 | Generic (default) |
USER_FEEDBACK |
0.8 | Feedback event → Intent |
Multiple Edges: The graph supports multiple edges between the same node pair with different types, keyed by (source, target, edge_type).
class Event(BaseModel):
event_id: str # Unique identifier
timestamp: datetime # When event occurred
source: str # Domain identifier (e.g., "personal-timeline")
event_type: str # Free-form type (e.g., "meal", "http.request")
title: str # Short description
description: str | None # Detailed description
location: str | None # Physical location
duration_minutes: int | None # Duration
context: dict[str, Any] # Domain-specific structured data
metadata: dict[str, Any] # Additional unstructured dataDomain-Agnostic Design: The context field holds domain-specific data that the LLM interprets. Same Event model works for personal timelines, computer activity, service logs, wiki chunks, etc.
class UpdatePlan(BaseModel):
plan_id: str # Unique identifier
trigger_event_id: str # Event that triggered this plan
reasoning: str # Agent's explanation
operations: list[Operation] # Operations to execute atomicallyOperation Types (OperationType enum):
| Operation | Required Fields | Description |
|---|---|---|
ADD_NODE |
node_type, data, reasoning, grounded_in |
Create node |
UPDATE_NODE |
node_id, data, update_reasoning* |
Modify node |
REMOVE_NODE |
node_id |
Delete node and edges |
ADD_EDGE |
source_id, target_id, edge_type?, weight? |
Connect nodes |
REMOVE_EDGE |
source_id, target_id, edge_type? |
Disconnect nodes |
MERGE_NODES |
node_ids[], merged_data, reasoning |
Combine similar nodes |
*Required for non-event nodes
The ConceptGraph class wraps NetworkX MultiDiGraph for in-memory graph operations.
Key Design Decisions:
- MultiDiGraph: Supports multiple edges between same node pair (different types)
- Node attributes stored directly: Type, data, timestamps, reasoning, grounded_in
- Edge keying:
(source, target, edge_type)allows typed edge lookups - Legacy compatibility:
edge_type=Noneuses key"__legacy__"
Critical Methods:
class ConceptGraph:
def add_node(self, node: Node) -> None
def get_node(self, node_id: str) -> Node
def update_node(self, node_id: str, data: dict) -> None
def remove_node(self, node_id: str) -> None
def add_edge(self, edge: Edge) -> None
def get_edge(self, source: str, target: str, edge_type: str | None) -> Edge
def remove_edge(self, source: str, target: str, edge_type: str | None) -> None
def get_neighbors(self, node_id: str) -> list[str]
def get_predecessors(self, node_id: str) -> list[str]
def get_nodes_by_type(self, node_type: NodeType) -> list[Node]Persistence (graph/persistence.py):
save_graph(graph, path): Serialize to JSON with ISO timestampsload_graph(path) -> ConceptGraph: Deserialize from JSONgraph_to_dict(graph) -> dict: Convert graph to serializable dict (version, saved_at, nodes, edges). Used by the/graph/exportHTTP endpoint and session persistence.
Edge Inference (graph/edge_inference.py):
Post-ingestion edge inference for disconnected graphs. When LLM-generated UpdatePlans fail to create edges (e.g., referencing titles instead of IDs), the graph ends up with few or no edges.
EdgeInferenceEngine: CreatesRELATED_TOedges between nodes with high embedding similarity- Uses kNN approach: connects each node to top-k nearest neighbors by cosine similarity
- Parameters:
similarity_threshold(default 0.30),max_edges_per_node(default 3) - Requires stored node embeddings (
Node.embeddingfield) - Called from
ConceptGraph.infer_edges()andMemoryQueryAgent._expand_with_neighbors()
Graph Projection (graph/projection.py):
Read-only abstraction layer for graph access. Any downstream system (neural network, symbolic tracker, consolidation engine) reads through this interface.
GraphProjection:@runtime_checkableProtocol with 5 methods (get_active_concepts,get_concept_connections,get_cluster_summary,get_lifecycle_state,get_node_count_by_type)NetworkXProjection: Default implementation wrappingConceptGraph+ optionalContextRankerGraphSnapshot: Serializable point-in-time graph state (node/edge counts, top concepts, active intents, cluster count) withto_dict()/from_dict()round-tripgraph_to_snapshot(): Factory function to create a snapshot from aConceptGraph
Observability (utils/llm_metrics.py, utils/budget.py):
LLMMetricsCollector: Thread-safe collector for LLM call metrics (model, tokens, latency, cost). Cost estimation covers 15+ models (OpenAI, Gemini, Claude).BudgetEnforcer: Per-session token/cost/call limit enforcement. RaisesBudgetExceededErrorwhen limits breached.
Computes node relevance scores using composite formula:
Score(node) = α × StructuralRank + β × RecencyScore + γ × AccessScore
Default Weights (ScoringConfig):
| Weight | Default | Description |
|---|---|---|
alpha |
0.4 | Structural importance (PageRank) |
beta |
0.4 | Recency (time decay) |
gamma |
0.2 | Access frequency |
Score Components:
-
StructuralRank: Weighted PageRank on graph topology
- Uses edge weights:
effective_weight = edge.weight × recency_factor(edge.created_at) - Higher weight edges contribute more to node importance
- Uses edge weights:
-
RecencyScore:
exp(-decay_rate × hours_since_last_accessed)- Default
decay_rate = 0.01
- Default
-
AccessScore:
access_count / max_access_count(normalized) -
UrgencyScore (multiplier): Boost for nodes connected to approaching TIME nodes
urgency_boost = 2.0max multiplierurgency_window_hours = 24.0hours before deadline
Three-level context with different scoring strategies:
| Level | % of Budget | Content | Scoring Strategy |
|---|---|---|---|
| Immediate | 10% | Recent events, urgent intents | 70% recency, 30% urgency |
| Working | 30% | Active concepts, patterns | 50% PageRank, 30% recency, 20% type |
| Background | 50% | Historical context | 80% PageRank, 20% diversity |
Features:
- Deduplication: Node appears in highest priority level only
- Each level includes Nodes + Relationships sections
- Edges show type, weight, and connected node summaries
Orchestrates end-to-end event processing:
class Pipeline:
def __init__(self, config: CognifoldConfig)
def load_timeline(self, path: str) -> int
def process_event(self, event: Event) -> PipelineResult
def step(self) -> PipelineResult | None
def run(self, max_events: int | None) -> PipelineStats
def save_graph(self, path: str) -> None
def visualize(self, path: str) -> PathProcessing Flow for Each Event:
- Get context window from
ContextRanker - Generate
UpdatePlanvia agent (or default plan if no API key) - Validate plan with
PlanValidator - Execute plan atomically with
PlanExecutor - Update statistics
Atomic Execution with Rollback:
- Take graph snapshot
- Sort operations by type priority (ADD_NODE → UPDATE_NODE → ADD_EDGE → REMOVE_EDGE → REMOVE_NODE → MERGE_NODES) to handle LLM-generated plans where edges may appear before their target nodes
- Execute operations in sorted order
- On failure: restore snapshot and abort
- Optionally validate graph integrity after execution
Node ID Resolution (_resolve_add_node_id):
For ADD_NODE operations, the executor resolves the node ID through a multi-layer lookup:
op.node_id— explicit ID on the operation- Well-known data keys:
event_id,id,concept_id,action_id,intent_id,time_id - Dynamic
{node_type}_idkey (e.g., intent →intent_id) - Scan all data values for IDs referenced by edge operations in the plan
- Auto-generate fallback:
{node_type}-{uuid[:8]}
class PlanExecutor:
def execute(self, plan: UpdatePlan) -> ExecutionResultValidates UpdatePlan before execution:
- Node existence for UPDATE/REMOVE operations
- Edge endpoint existence for ADD_EDGE
- Duplicate node detection
- Grounding validation (concepts/intents must link to events)
- Reasoning validation (non-events need reasoning field)
Returns ValidationResult with errors (blocking) and warnings (soft).
LangGraph-based agent that generates UpdatePlans.
Input:
- Event being processed
- Hierarchical context window (immediate/working/background)
- Graph tools for exploration
Output: UpdatePlan with operations
Tools Available (agent/tools.py):
| Tool | Description |
|---|---|
get_node(node_id) |
Get full node details |
get_neighbors(node_id, direction) |
Find connected nodes |
find_nodes_by_type(node_type) |
List nodes of a type |
search_nodes(keyword) |
Keyword search in titles/data |
get_graph_stats() |
Graph statistics |
System prompt includes:
- Role definition
- Node type descriptions with examples
- Edge type semantics
- Guidelines for concept creation, strength management, hierarchy
- Reasoning mode instructions (quick, analytical, consolidation)
- Plan self-review section for retroactive connections
- Concept refinement for overloaded nodes (5+ connections)
Domain-specific prompt customization without changing core logic:
| Domain | Source |
|---|---|
personal-timeline |
Event generator |
computer-activity |
Computer activity generator |
service-logs |
Service logs generator |
wiki |
Wiki importer |
Retrieves relevant context for natural language queries.
Retrieval Modes (RetrievalMode enum):
| Mode | Backend | Requirements |
|---|---|---|
LEGACY |
Keyword matching | None |
BM25 |
Inverted index + BM25 | None |
SEMANTIC |
Embedding similarity | NodeEmbedder |
HYBRID |
BM25 + semantic with RRF (default) | NodeEmbedder (auto-degrades to BM25) |
Additionally, the retrieval/ module has a RetrievalStrategy enum with an AGENTIC mode:
| Strategy | Backend | Requirements |
|---|---|---|
AGENTIC |
2-round hybrid with LLM sufficiency check + complementary queries + multi-RRF | HybridRetriever + LLM API key |
The agentic retriever (retrieval/agentic.py) orchestrates: Round 1 hybrid retrieval → LLM sufficiency check → if insufficient, generate complementary queries → Round 2 parallel retrieval → multi-RRF fusion.
Query Flow:
- Entry Point Selection (
EntryPointSelector): Find starting nodes using configured retrieval mode - Graph Traversal (
GraphTraverser): BFS from entry points with score decay - Scoring (
QueryScorer): Rank traversed nodes by relevance - Assembly (
ContextAssembler): Format results intoQueryResult
agent = MemoryQueryAgent(graph, config, embedder=embedder)
result = agent.query("exercise habits")
# result.summary, result.nodes, result.statisticsStandard BM25 scoring with inverted index:
k1=1.5,b=0.75(default parameters)- Document = node text (title + description + reasoning)
- Tokenization with stopword removal
Provider Abstraction (EmbeddingProvider):
MockEmbeddingProvider: Deterministic embeddings for testing (numpy RNG)GeminiEmbeddingProvider: Google AI embeddingsOpenAIEmbeddingProvider: OpenAI embeddings
NodeEmbedder (embeddings/embedder.py):
- Generates embeddings for nodes (title + description + reasoning)
- LRU caching for efficiency
- Batch embedding support
- Export/import for persistence
SemanticSearch (embeddings/search.py):
- Cosine similarity over node embeddings
- Optional FAISS ANN index (
IndexFlatIP+IndexIDMap) for fast lookup, numpy fallback - Index building for fast lookup
- Type filtering in results
Reciprocal Rank Fusion (RRF):
RRF_score(d) = Σ 1 / (k + rank_i(d))
Where k=60 (default RRF constant).
Combines BM25 and semantic rankings for best results.
LLM-based synthetic event generation:
| Generator | Input | Output |
|---|---|---|
EventGenerator |
Persona | Personal timeline events |
ComputerActivityGenerator |
WorkProfile | Computer usage events |
ServiceLogsGenerator |
ServiceTopology | Microservice events |
Data transformation without LLM:
| Importer | Input | Output |
|---|---|---|
WikiImporter |
Markdown/PDF files | Chunked wiki events |
Wiki Chunking Strategies:
heading: Split at markdown headingsparagraph: Split at paragraph boundariesfixed: Fixed-size chunks with overlap
Transforms graph intents into executable actions.
Components:
IntentToActionAgent: Converts intents to concreteActionobjectsActionQueue: Manages scheduled actions sorted by timeActionExecutor: Simulates action execution, generates result eventsIntentSelector: Identifies actionable intents based on urgency
Action vs Intent:
| Aspect | Intent | Action |
|---|---|---|
| Storage | Graph node | ActionQueue |
| Timing | No specific time | Scheduled execution |
| Nature | Goal/desire | Concrete step |
Graph evolution visualization from run logs.
ReplayLogger (replay/logger.py):
- Logs events, plans, graph snapshots to JSONL
- Each entry timestamped for ordering
ReplayRenderer (replay/renderer.py):
- Generates interactive HTML with pyvis
- Play/pause/step controls
- Node colors by type, sizes by score
cognifold run timeline.json --agent --steps 15
cognifold query "exercise habits" --graph graph.json
cognifold build-timeline --source wiki --input data/wiki/ -o timeline.json
cognifold replay logs/replay.jsonl# cognifold.yaml
model:
name: "gemini-2.0-flash"
temperature: 0.7
max_tokens: 4096
max_exploration_steps: 3
scoring:
alpha: 0.4
beta: 0.4
gamma: 0.2
decay_rate: 0.01
context:
max_nodes: 50
min_score_threshold: 0.01
logging:
level: INFO
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"The service layer exposes Cognifold as a stateful REST API, allowing clients to create sessions, ingest events, query the graph, and inspect graph state over HTTP.
┌─────────────────────────────────────────────────────────────────────────────┐
│ HTTP Service Layer │
│ │
│ ┌────────────┐ ┌────────────────────────────────────────────────┐ │
│ │ FastAPI │ │ Route Handlers │ │
│ │ App │ │ ┌──────────┐ ┌────────┐ ┌───────┐ ┌───────┐ │ │
│ │ (app.py) │───►│ │ sessions │ │ events │ │ query │ │ graph │ │ │
│ │ │ │ └──────────┘ └────────┘ └───────┘ └───────┘ │ │
│ │ Middleware: │ └──────────────────┬───────────────────────────┘ │
│ │ • Auth │ │ │
│ │ • Logging │ ▼ │
│ └────────────┘ ┌────────────────────────────────────────────────┐ │
│ │ Session Manager │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ Session 1 Session 2 Session N │ │ │
│ │ │ ┌─────────┐ ┌─────────┐ │ │ │
│ │ │ │ Graph │ │ Graph │ ... │ │ │
│ │ │ │ Ranker │ │ Ranker │ │ │ │
│ │ │ │ Agent │ │ Agent │ │ │ │
│ │ │ └─────────┘ └─────────┘ │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ • TTL eviction • Persist on delete │ │
│ │ • LRU when full • Per-session locks │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────┴──────────────────┐ │
│ │ Event Processor │ │
│ │ 1. Build Event from input │ │
│ │ 2. Compute context window (Ranker) │ │
│ │ 3. Generate plan (Agent or default) │ │
│ │ 4. Validate plan │ │
│ │ 5. Execute atomically │ │
│ └──────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────┴──────────┐ │
│ │ Task Tracker (async) │ │
│ │ • Pending/Running/Done │ │
│ │ • Poll for status │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
AppSettings (service/app.py):
@dataclass
class AppSettings:
persist_dir: str = "./sessions" # Graph persistence directory
max_sessions: int = 100 # Max concurrent sessions
session_ttl_hours: float = 24.0 # Inactive session expiry
api_keys: set[str] | None = None # None = auth disabledSession (service/session.py):
Each session owns an isolated graph, ranker, and lazy-loaded agent:
graph: ConceptGraph— the session's concept graphranker: ContextRanker— scoring with session-specific configagent: CognifoldAgent | None— lazy-initialized on first event with LLM keyslock: asyncio.Lock— per-session concurrency controlllm_env()— context manager that temporarily sets API key env vars
SessionManager (service/session.py):
- Creates sessions with unique 16-char hex IDs
- LRU eviction when
max_sessionsreached (oldest bylast_accessed) - TTL cleanup for sessions inactive beyond
session_ttl_hours - Persists graph to
{persist_dir}/{session_id}/graph.jsonon delete/evict persist_all()called on shutdown
Event Processor (service/processor.py):
Wraps the Pipeline's event processing for the service layer:
- Convert
EventInput→ internalEventmodel (generatesevent_id) - Compute context window via
ContextRanker - Generate plan inside
session.llm_env(): use agent if LLM keys provided, else default plan (adds event as node) - Validate plan with
PlanValidator(falls back to default plan if invalid) - Execute atomically with
PlanExecutorinsidesession.llm_env()(embeddings need API keys) - Log and return execution errors (propagated to client via
errorfield)
Authentication (service/auth.py):
X-API-Keyheader, validated byAPIKeyValidatordependency- If
api_keys=None: auth disabled (dev mode), all requests pass - Health endpoints (
/health,/ready) always bypass auth
Task Tracker (service/tasks.py):
- In-memory tracking for async event ingestion
- States:
pending→running→completed|failed - Client polls
/tasks/{task_id}for result
All API routes are prefixed with /api/v1.
Health (no auth):
| Method | Path | Response |
|---|---|---|
| GET | /health |
{"status": "ok"} |
| GET | /ready |
{"status": "ok", "active_sessions": N} |
Sessions:
| Method | Path | Request Body | Response | Status |
|---|---|---|---|---|
| POST | /sessions |
CreateSessionRequest |
SessionInfo |
201 |
| GET | /sessions/{id} |
— | SessionInfo |
200 |
| DELETE | /sessions/{id} |
— | — | 204 |
| POST | /sessions/{id}/load |
LoadGraphRequest |
SessionInfo |
200 |
Events:
| Method | Path | Request Body | Response | Status |
|---|---|---|---|---|
| POST | /sessions/{id}/events |
IngestEventRequest |
IngestEventResponse or AsyncTaskResponse |
200 |
| POST | /sessions/{id}/events/batch |
BatchIngestRequest |
BatchIngestResponse |
200 |
| GET | /sessions/{id}/tasks/{task_id} |
— | TaskStatusResponse |
200 |
Query:
| Method | Path | Request Body | Response |
|---|---|---|---|
| POST | /sessions/{id}/query |
QueryRequest |
QueryResponse |
Graph State:
| Method | Path | Query Params | Response |
|---|---|---|---|
| GET | /sessions/{id}/graph |
max_nodes (1-1000) |
GraphStateResponse |
| GET | /sessions/{id}/graph/stats |
— | GraphStatsResponse |
| GET | /sessions/{id}/graph/concepts |
top (1-100) |
list[QueryNodeResponse] |
| GET | /sessions/{id}/graph/intents |
recent (1-100) |
list[QueryNodeResponse] |
| GET | /sessions/{id}/graph/events |
recent (1-100) |
list[QueryNodeResponse] |
| GET | /sessions/{id}/graph/nodes/{node_id} |
— | NodeResponse |
| GET | /sessions/{id}/graph/nodes/{node_id}/expand |
layers, direction, max_nodes |
NodeExpansionResponse |
| GET | /sessions/{id}/graph/export |
— | dict (persistence format) |
Domains (in-memory only, re-register on server restart):
| Method | Path | Request Body | Response | Status |
|---|---|---|---|---|
| POST | /domains |
DomainRegisterRequest |
{"status", "name"} |
201 |
| GET | /domains |
— | {"domains": [...]} |
200 |
| GET | /domains/{name} |
— | {"name", "description"} |
200 |
SessionConfig: model_name, temperature, max_nodes, domain, scoring_alpha/beta/gamma
EventInput: event_type (required), title (required), timestamp, source, description, location, duration_minutes, context, metadata
IngestEventResponse: event_id, plan_id, reasoning, operations_completed, success, execution_time_ms, graph_stats, error (execution failure message, if any)
QueryRequest: query (required), max_nodes, max_context_chars, query_mode
QueryResponse: context, nodes[], traversal_path[], query_metadata, query_time_ms
GraphStatsResponse: node_count, edge_count, concepts, events, intents, time_nodes
NodeResponse: node_id, node_type, data, created_at, last_accessed, access_count, reasoning, grounded_in[], neighbors[], predecessors[]
CognifoldClient wraps all API endpoints using stdlib urllib.request (no extra dependencies). ClientREPL provides an interactive REPL with : command dispatch for session management, event ingestion, querying, and graph exploration. Natural language queries (input without : prefix) are sent directly to the query endpoint.
models/ # No internal dependencies
└── node.py, event.py, plan.py
graph/ # Depends on: models, (numpy for edge_inference)
└── store.py, persistence.py, validator.py, metrics.py, edge_inference.py
scoring/ # Depends on: graph, models
└── ranker.py, hierarchical.py
executor/ # Depends on: graph, models
└── runner.py, validator.py
agent/ # Depends on: graph, models, scoring, executor
└── agent.py, prompts.py, prompt_sections.py, tools.py, domain.py, prompt_profile.py
query/ # Depends on: graph, models, scoring, embeddings, retrieval
└── agent.py, strategies.py, scoring.py, assembly.py, probe.py, llm.py, prompts.py
embeddings/ # Depends on: graph, models
└── providers.py, embedder.py, search.py, config.py
retrieval/ # Depends on: graph, models, embeddings
└── bm25.py, hybrid.py, agentic.py, config.py, result.py
temporal/ # Depends on: (standalone, uses dateparser)
└── extractor.py
intent/ # Depends on: graph, models, agent
└── agent.py, queue.py, selector.py, executor.py, models.py, prompts.py
generator/ # Depends on: models
└── base.py, event_generator.py, computer_activity.py, service_logs.py
importers/ # Depends on: models
└── base.py, wiki.py
replay/ # Depends on: graph, models
└── logger.py, player.py, renderer.py
utils/ # Shared utilities
└── embeddings.py
pipeline.py # Depends on: all above modules
service/ # Depends on: graph, models, scoring, executor, agent, query
└── app.py, session.py, processor.py, auth.py, tasks.py, models.py, routes/
simulator/ # Depends on: graph, models, scoring, executor, agent, replay
└── cli.py, visualizer.py, timeline.py
cli/ # Depends on: all modules (CLI entry points)
└── __init__.py, run.py, query.py, generate.py, replay.py, serve.py,
client.py, build.py, config.py
Status: Accepted
Context: EdgeInferenceEngine (graph/edge_inference.py) uses kNN cosine similarity to infer RELATED_TO edges between nodes. During benchmarking, auto-invoking it on small graphs (< 100 nodes) caused regressions by over-connecting unrelated nodes, which degraded retrieval precision.
Decision: Edge inference is library code only -- callers must explicitly instantiate EdgeInferenceEngine and call infer_edges(). It is never auto-invoked during normal event ingestion or plan execution. The executor's orphan detection (executor/runner.py, PlanExecutor._detect_orphan_nodes) handles the common case of reconnecting orphaned concept/intent nodes via their deterministic grounded_in references, creating GROUNDS edges without similarity computation.
Consequences:
- Users who want dense similarity-based edges must explicitly call
EdgeInferenceEngine. - Small-graph benchmarks remain stable (no spurious over-connection).
- Large-graph scenarios (e.g. bulk wiki ingestion) can benefit from explicit edge inference calls.
- The orphan detection in the executor remains the default safety net for disconnected nodes produced by faulty LLM plans.