Knowledge System Diagrams¶
Table of Contents¶
- Contradiction Reconciliation Agent Flow
- 9-Agent Knowledge Dispatch
- DeepSynthesisWorkflow Loop
- Sandbox Boot Policy Decision
- Optimizer Canary FSM
- Daily-Cleanup Workflow + Monthly-Reports Upload
Contradiction Reconciliation Agent Flow¶
ContradictionReconciliationAgent._process_impl (libs/agents/cogniverse_agents/contradiction_reconciliation_agent.py) takes a subject_key's conflict_member_ids, fetches each via memory_manager.memory.get(mid), then calls reconcile(members, policy) from libs/core/cogniverse_core/memory/contradiction.py. The ContradictionDetector upstream groups by metadata.subject_key and emits one ConflictSet per subject with more than one distinct _content_signature. The resolved view depends on the schema's contradiction_policy (or a request-time policy_override).
sequenceDiagram
participant Caller as Caller / Router
participant Agent as ContradictionReconciliationAgent
participant Memory as Mem0MemoryManager
participant Detector as ContradictionDetector
participant Reconcile as reconcile(members, policy)
participant Registry as KnowledgeRegistry
Caller->>Agent: ContradictionReconciliationInput(target_kind, conflict_member_ids, policy_override?)
Agent->>Registry: get(target_kind) -> KnowledgeSchema
Registry-->>Agent: schema (contradiction_policy)
Agent->>Agent: policy = policy_override or schema.contradiction_policy
Note over Detector: Detector emits the ConflictSet<br/>by grouping candidates by metadata.subject_key + _content_signature
Detector-->>Caller: ConflictSet(subject_key, conflicting_memory_ids, detected_at)
loop For each mid in conflict_member_ids
Agent->>Memory: memory.get(mid)
Memory-->>Agent: member dict (or missing)
end
Agent->>Reconcile: reconcile(members, policy)
Reconcile-->>Agent: resolved view (survivors, disputed flags)
Agent-->>Caller: ContradictionReconciliationOutput(policy_used, resolved[], survivors[]) Within reconcile, the per-subject branch picks the survivor by policy:
flowchart TD
Start["<span style='color:#000'>members for one subject_key<br/>(distinct content signatures)</span>"]
Policy{"<span style='color:#000'>schema.contradiction_policy<br/>(or policy_override)</span>"}
Latest["<span style='color:#000'>LATEST_WINS<br/>_pick_latest: max(members, key=created_at)</span>"]
Trust["<span style='color:#000'>TRUST_RANKED<br/>_pick_trust_ranked:<br/>max(trust.score × provenance.confidence)</span>"]
Both["<span style='color:#000'>PRESERVE_BOTH<br/>_mark_disputed: keep all,<br/>set metadata.disputed=True</span>"]
WinnerOne["<span style='color:#000'>winner_memory_id = survivor.id<br/>disputed_members = []</span>"]
WinnerTrust["<span style='color:#000'>winner_memory_id = survivor.id<br/>disputed_members = []</span>"]
WinnerBoth["<span style='color:#000'>winner_memory_id = None<br/>disputed_members = all members</span>"]
Out["<span style='color:#000'>ContradictionReconciliationOutput<br/>policy_used, survivors[], resolved[].disputed</span>"]
Start --> Policy
Policy -->|latest_wins| Latest --> WinnerOne --> Out
Policy -->|trust_ranked| Trust --> WinnerTrust --> Out
Policy -->|preserve_both| Both --> WinnerBoth --> Out
style Start fill:#90caf9,stroke:#1565c0,color:#000
style Policy fill:#ffcc80,stroke:#ef6c00,color:#000
style Latest fill:#ce93d8,stroke:#7b1fa2,color:#000
style Trust fill:#ce93d8,stroke:#7b1fa2,color:#000
style Both fill:#ce93d8,stroke:#7b1fa2,color:#000
style WinnerOne fill:#a5d6a7,stroke:#388e3c,color:#000
style WinnerTrust fill:#a5d6a7,stroke:#388e3c,color:#000
style WinnerBoth fill:#a5d6a7,stroke:#388e3c,color:#000
style Out fill:#90caf9,stroke:#1565c0,color:#000 9-Agent Knowledge Dispatch¶
libs/runtime/cogniverse_runtime/routers/knowledge.py exposes nine /admin/tenants/{tenant_id}/knowledge/... POST routes, one per knowledge agent. Every route resolves the per-tenant Mem0 instance through _build_factory(tenant_id) which constructs Mem0MemoryManager(tenant_id) and calls lazy_init_memory(mm, tenant_id, _require_config_manager()) when mm.memory is not yet wired. The same factory is either passed as memory_manager_factory= or attached via _inject_memory(...) (which sets agent.memory_manager, _memory_initialized, _memory_tenant_id, _memory_agent_name).
Mem0 and the shared Vespa knowledge graph are complementary, not competing. The six graph-aware routes (citation/trace, summarize, contradictions/reconcile, synthesis/multi_doc, kg/traverse, temporal/reason) each call _bind_graph(agent, tenant_id), which binds the tenant's shared Vespa GraphManager (routers/graph.py::get_graph_manager) onto the agent (those mixing in GraphBindableMixin). The remaining three routes (audit/explain, cross_tenant/compare, federated/query) do not call _bind_graph — they have no kg_* output fields. The generic agent_dispatcher._bind_graph_manager does the same binding on the orchestrator-routing path. With the graph bound, each agent's _process_impl walks its own mem0 memory and consults the shared KG, merging the KG's cross-document, provenance-rich claims into a dedicated typed kg_* output field:
| Agent | mem0 path (per-agent memory) | Vespa-KG complement (typed field) |
|---|---|---|
KnowledgeGraphTraversalAgent | mem0 node/edge BFS | traverse(seed) merged into nodes/edges |
TemporalReasoningAgent | wall-clock window_views | compare_over_time(subject) → kg_timeline |
MultiDocumentSynthesisAgent | mem0 document synthesis | synthesize() → kg_claim_groups |
ContradictionReconciliationAgent | mem0 member reconciliation | detect(subject, predicate) → kg_conflict_entries |
KnowledgeSummarizationAgent | mem0 subject summary | per-video summarize() → kg_video_summaries |
CitationTracingAgent | mem0 provenance walk | trace(claim_id) → kg_primary_sources |
The complement is fail-safe: when no graph is bound (or the backend is unconfigured), _bind_graph is a no-op, the kg_* fields stay empty, and the agent returns its mem0-only answer.
flowchart LR
Client["<span style='color:#000'>HTTP POST /admin/tenants/{t}/knowledge/<route></span>"]
Router["<span style='color:#000'>knowledge.py router</span>"]
Factory["<span style='color:#000'>_build_factory(tenant_id)<br/>Mem0MemoryManager(t)<br/>lazy_init_memory(mm, t, cfg_mgr)</span>"]
Graph["<span style='color:#000'>_bind_graph(agent, t)<br/>get_graph_manager(t)<br/>→ shared Vespa GraphManager</span>"]
Audit["<span style='color:#000'>AuditExplanationAgent<br/>audit/explain</span>"]
Citation["<span style='color:#000'>CitationTracingAgent<br/>citations/trace</span>"]
Summarize["<span style='color:#000'>KnowledgeSummarizationAgent<br/>summarize</span>"]
Reconcile["<span style='color:#000'>ContradictionReconciliationAgent<br/>contradictions/reconcile</span>"]
MultiDoc["<span style='color:#000'>MultiDocumentSynthesisAgent<br/>synthesis/multi_doc</span>"]
KG["<span style='color:#000'>KnowledgeGraphTraversalAgent<br/>kg/traverse</span>"]
CrossTenant["<span style='color:#000'>CrossTenantComparisonAgent<br/>cross_tenant/compare</span>"]
Federated["<span style='color:#000'>FederatedQueryAgent<br/>federated/query</span>"]
Temporal["<span style='color:#000'>TemporalReasoningAgent<br/>temporal/reason</span>"]
Client --> Router
Router --> Audit
Router --> Citation
Router --> Summarize
Router --> Reconcile
Router --> MultiDoc
Router --> KG
Router --> CrossTenant
Router --> Federated
Router --> Temporal
Audit --> Factory
Citation --> Factory
Summarize --> Factory
Reconcile --> Factory
MultiDoc --> Factory
KG --> Factory
CrossTenant --> Factory
Federated --> Factory
Temporal --> Factory
Citation -.kg complement.-> Graph
Summarize -.kg complement.-> Graph
Reconcile -.kg complement.-> Graph
MultiDoc -.kg complement.-> Graph
KG -.kg complement.-> Graph
Temporal -.kg complement.-> Graph
style Client fill:#90caf9,stroke:#1565c0,color:#000
style Router fill:#b0bec5,stroke:#546e7a,color:#000
style Factory fill:#a5d6a7,stroke:#388e3c,color:#000
style Graph fill:#ffcc80,stroke:#ef6c00,color:#000
style Audit fill:#ce93d8,stroke:#7b1fa2,color:#000
style Citation fill:#ce93d8,stroke:#7b1fa2,color:#000
style Summarize fill:#ce93d8,stroke:#7b1fa2,color:#000
style Reconcile fill:#ce93d8,stroke:#7b1fa2,color:#000
style MultiDoc fill:#ce93d8,stroke:#7b1fa2,color:#000
style KG fill:#ce93d8,stroke:#7b1fa2,color:#000
style CrossTenant fill:#ce93d8,stroke:#7b1fa2,color:#000
style Federated fill:#ce93d8,stroke:#7b1fa2,color:#000
style Temporal fill:#ce93d8,stroke:#7b1fa2,color:#000 DeepSynthesisWorkflow Loop¶
DeepSynthesisWorkflow.run (libs/agents/cogniverse_agents/deep_synthesis_workflow.py) wraps the orchestrator in an RLM trajectory bounded by three explicit caps: a per-tenant sliding-window rate limit (DeepSynthesisRateLimiter.try_acquire), a cumulative hard_call_cap over subagent_calls_made + llm_calls_used, and a max_iterations ceiling. Each RLM step either contains the SUBMIT() token (answer ready) or emits ASK(<subagent>: <subquery>) markers that get parsed and fanned out, capped per round by max_subagent_calls_per_round.
flowchart TD
Start["<span style='color:#000'>run(query, tenant_id, seed_subagents)</span>"]
Rate{"<span style='color:#000'>DeepSynthesisRateLimiter.try_acquire(tenant_id)<br/>(rate_limit_per_hour, sliding window)</span>"}
RateOut["<span style='color:#fff'>return DeepSynthesisResult(<br/>was_rate_limited=True, answer='')</span>"]
Seed["<span style='color:#000'>Iter 0 fan-out:<br/>seed_subagents[:max_subagent_calls_per_round]<br/>parallel _fan_out -> gathered[]</span>"]
Loop["<span style='color:#000'>for it in 1..max_iterations</span>"]
HardCap{"<span style='color:#000'>subagent_calls_made + llm_calls_used<br/>>= hard_call_cap?</span>"}
Capped["<span style='color:#fff'>return DeepSynthesisResult(<br/>was_capped=True, was_submitted=False)</span>"]
RLMStep["<span style='color:#000'>rlm.process(query, context=gathered)<br/>llm_calls_used += 1</span>"]
Submit{"<span style='color:#000'>SUBMIT() in iter_text?</span>"}
Submitted["<span style='color:#000'>return DeepSynthesisResult(<br/>was_submitted=True, answer)</span>"]
Asks{"<span style='color:#000'>_parse_asks(iter_text)<br/>yields ASK(name: subq) pairs?</span>"}
Stalled["<span style='color:#fff'>trajectory: stalled_no_asks<br/>return was_capped=True</span>"]
FanOut["<span style='color:#000'>dispatch asks[:max_subagent_calls_per_round]<br/>(further trimmed by remaining cap budget)<br/>subagent_calls_made += len(results)</span>"]
IterCap{"<span style='color:#000'>iterations exhausted<br/>without SUBMIT?</span>"}
IterExhausted["<span style='color:#fff'>trajectory: iteration_cap_exhausted<br/>return was_capped=True, was_submitted=False</span>"]
Start --> Rate
Rate -->|denied| RateOut
Rate -->|admitted| Seed
Seed --> Loop
Loop --> HardCap
HardCap -->|yes| Capped
HardCap -->|no| RLMStep
RLMStep --> Submit
Submit -->|yes| Submitted
Submit -->|no| Asks
Asks -->|none| Stalled
Asks -->|>=1| FanOut
FanOut --> IterCap
IterCap -->|yes| IterExhausted
IterCap -->|no| Loop
style Start fill:#90caf9,stroke:#1565c0,color:#000
style Rate fill:#ffcc80,stroke:#ef6c00,color:#000
style RateOut fill:#e53935,stroke:#c62828,color:#fff
style Seed fill:#ce93d8,stroke:#7b1fa2,color:#000
style Loop fill:#b0bec5,stroke:#546e7a,color:#000
style HardCap fill:#ffcc80,stroke:#ef6c00,color:#000
style Capped fill:#e53935,stroke:#c62828,color:#fff
style RLMStep fill:#ce93d8,stroke:#7b1fa2,color:#000
style Submit fill:#ffcc80,stroke:#ef6c00,color:#000
style Submitted fill:#a5d6a7,stroke:#388e3c,color:#000
style Asks fill:#ffcc80,stroke:#ef6c00,color:#000
style Stalled fill:#e53935,stroke:#c62828,color:#fff
style FanOut fill:#ce93d8,stroke:#7b1fa2,color:#000
style IterCap fill:#ffcc80,stroke:#ef6c00,color:#000
style IterExhausted fill:#e53935,stroke:#c62828,color:#fff Sandbox Boot Policy Decision¶
Runtime boot (libs/runtime/cogniverse_runtime/main.py) resolves a SandboxPolicy before constructing the manager: an explicit COGNIVERSE_SANDBOX_POLICY env var wins first, then config["sandbox"]["policy"]; only when neither is set does it fall back to the legacy enabled shorthand (config["sandbox"]["enabled"] or COGNIVERSE_SANDBOX_ENABLED or a bare OPENSHELL_GATEWAY_ENDPOINT being present) mapping truthy to OPTIONAL and falsy to DISABLED. SandboxManager.__init__ (libs/runtime/cogniverse_runtime/sandbox_manager.py) then takes that resolved SandboxPolicy (via _resolve_policy, which itself only defaults a bare None to OPTIONAL) and either short-circuits on DISABLED or loads per-agent policies and calls _connect(), which TCP-probes OPENSHELL_GATEWAY_ENDPOINT via _probe_gateway_endpoint. When the resolved policy is REQUIRED and _available is still False after the probe, the constructor raises SandboxGatewayUnavailableError so boot fails loud.
flowchart TD
Init["<span style='color:#000'>SandboxManager(policy=?)</span>"]
Resolve["<span style='color:#000'>_resolve_policy(policy)<br/>policy or default OPTIONAL</span>"]
Decision{"<span style='color:#000'>SandboxPolicy</span>"}
Disabled["<span style='color:#000'>DISABLED branch<br/>log 'disabled by configuration'<br/>_client=None, _available=False<br/>return (skip probe + policy load)</span>"]
LoadPolicies["<span style='color:#000'>_load_policies()<br/>read configs/agent_policies/*.yaml</span>"]
Connect["<span style='color:#000'>_connect()<br/>if OPENSHELL_GATEWAY_ENDPOINT:<br/> _probe_gateway_endpoint(endpoint)<br/> SandboxClient(endpoint=...)<br/>else:<br/> SandboxClient.from_active_cluster()</span>"]
Available{"<span style='color:#000'>connect succeeded?<br/>(probe + client construct)</span>"}
Optional["<span style='color:#000'>OPTIONAL + available=False<br/>log warning 'will execute without sandbox'<br/>continue boot</span>"]
OptionalOK["<span style='color:#000'>OPTIONAL + available=True<br/>continue boot</span>"]
Required["<span style='color:#fff'>REQUIRED + available=False<br/>raise SandboxGatewayUnavailableError<br/>(refuse to start)</span>"]
RequiredOK["<span style='color:#000'>REQUIRED + available=True<br/>continue boot</span>"]
Init --> Resolve --> Decision
Decision -->|DISABLED| Disabled
Decision -->|OPTIONAL| LoadPolicies
Decision -->|REQUIRED| LoadPolicies
LoadPolicies --> Connect --> Available
Available -->|no, policy=OPTIONAL| Optional
Available -->|yes, policy=OPTIONAL| OptionalOK
Available -->|no, policy=REQUIRED| Required
Available -->|yes, policy=REQUIRED| RequiredOK
style Init fill:#90caf9,stroke:#1565c0,color:#000
style Resolve fill:#b0bec5,stroke:#546e7a,color:#000
style Decision fill:#ffcc80,stroke:#ef6c00,color:#000
style Disabled fill:#b0bec5,stroke:#546e7a,color:#000
style LoadPolicies fill:#ce93d8,stroke:#7b1fa2,color:#000
style Connect fill:#ce93d8,stroke:#7b1fa2,color:#000
style Available fill:#ffcc80,stroke:#ef6c00,color:#000
style Optional fill:#ffcc80,stroke:#ef6c00,color:#000
style OptionalOK fill:#a5d6a7,stroke:#388e3c,color:#000
style Required fill:#e53935,stroke:#c62828,color:#fff
style RequiredOK fill:#a5d6a7,stroke:#388e3c,color:#000 Optimizer Canary FSM¶
ArtifactManager (libs/agents/cogniverse_agents/optimizer/artifact_manager.py) stores a per-(tenant, agent) JSON state blob with active, canary, and retired[] slots. promote_to_canary, promote_canary_to_active, retire_canary, and rollback_to_version mutate the slots and snapshot the relevant versions; load_for_request then routes each call to the canary or active arm using a stable sha1(request_seed) % 100 < traffic_pct decision.
stateDiagram-v2
[*] --> active : promote (first version becomes active)
state "active only" as active
state "active + canary[traffic_pct]" as canary
state "retired (audit list)" as retired
active --> canary : promote_to_canary(version, traffic_pct)
canary --> canary : promote_to_canary(new_version, pct)\nprev canary -> retired\n(reason=superseded_by_new_canary)
canary --> active : promote_canary_to_active()\nprev active -> retired\n(reason=superseded_by_canary_promotion)\n_restore_active_from_version(canary.version)
canary --> active : retire_canary(reason=metric_regression\n| manual_retire | ...)\ncanary -> retired (caller reason)
active --> active : rollback_to_version(prompts_version, demos_version)\nsnapshot_active() -> backup_versions\n_restore_active_from_version(target)
canary --> retired : (retired list grows)
active --> retired : (when canary promoted)
note right of canary
load_for_request(request_seed, variant_id):
bucket = int(sha1(request_seed)[:8], 16) % 100
if canary and bucket < canary.traffic_pct:
served_from = "canary" (version = canary.version)
else:
served_from = "active" (version = active.version)
Same request_seed always lands in the same arm.
end note Daily-Cleanup Workflow + Monthly-Reports Upload¶
optimization_cli.run_cleanup (libs/runtime/cogniverse_runtime/optimization_cli.py) runs as a single Argo CronWorkflow pod ({fullname}-daily-cleanup, charts/cogniverse/templates/optimization-workflows.yaml) that executes four sequential sections per tick: schema-driven per-tenant Mem0 cleanup, log rotation, temp purge, and config-store version vacuum. run_monthly_reports runs as a two-step Argo pipeline sharing a reports-stage PVC: the runtime image generates the JSON, then a mc pod (minio.mcImage) uploads it to the cogniverse-backups bucket using credentials from the {fullname}-minio Secret.
flowchart TD
Cron["<span style='color:#000'>CronWorkflow {fullname}-daily-cleanup<br/>schedule (UTC), Forbid concurrency<br/>image=runtime, args=[--mode cleanup,<br/>--log-retention-days, --memory-retention-days]</span>"]
Mem["<span style='color:#000'>1) Memory cleanup (per tenant)<br/>list_organizations_internal +<br/>list_tenants_for_org_internal<br/>for each tid:<br/> Mem0MemoryManager(tid)<br/> lazy_init_memory(mm, tid, cfg)<br/> mm.cleanup_with_schema(build_default_registry())</span>"]
Logs["<span style='color:#000'>2) Log rotation<br/>LOG_DIR (env, default /logs)<br/>_prune_aged_files(older_than_days=<br/>--log-retention-days)</span>"]
Temp["<span style='color:#000'>3) Temp purge<br/>TEMP_DIR (env, default /tmp)<br/>TEMP_RETENTION_DAYS (env, default 1)<br/>_prune_aged_files(temp_dir, temp_age_days)</span>"]
Vacuum["<span style='color:#000'>4) Config vacuum<br/>CONFIG_KEEP_VERSIONS (env, default 10)<br/>VespaConfigStore.prune_all_configs(<br/>keep=keep_versions)</span>"]
Result["<span style='color:#000'>results dict:<br/>memory_cleanup, tenants_processed,<br/>log_cleanup, temp_cleanup, config_vacuum</span>"]
Cron --> Mem --> Logs --> Temp --> Vacuum --> Result
style Cron fill:#90caf9,stroke:#1565c0,color:#000
style Mem fill:#ce93d8,stroke:#7b1fa2,color:#000
style Logs fill:#ce93d8,stroke:#7b1fa2,color:#000
style Temp fill:#ce93d8,stroke:#7b1fa2,color:#000
style Vacuum fill:#ce93d8,stroke:#7b1fa2,color:#000
style Result fill:#a5d6a7,stroke:#388e3c,color:#000 flowchart LR
Schedule["<span style='color:#000'>CronWorkflow {fullname}-monthly-reports<br/>schedule (UTC), Forbid concurrency</span>"]
subgraph Pipeline["<span style='color:#000'>reports-pipeline (sequential steps, shared PVC reports-stage)</span>"]
Generate["<span style='color:#000'>Step 1: generate-reports pod<br/>image=runtime<br/>python -m cogniverse_runtime.optimization_cli<br/>--mode monthly-reports<br/>--reports-output-dir /reports<br/>--lookback-hours {value}<br/>writes usage-YYYYMM.json,<br/>performance-YYYYMM.json</span>"]
Upload["<span style='color:#000'>Step 2: upload-reports pod<br/>image=minio.mcImage<br/>env MINIO_ENDPOINT, MINIO_BUCKET,<br/>REPORTS_PREFIX,<br/>MINIO_ACCESS_KEY (secret rootUser),<br/>MINIO_SECRET_KEY (secret rootPassword)<br/>mc alias set dest $ENDPOINT $AK $SK<br/>mc cp /reports/*.json<br/>dest/$BUCKET/$REPORTS_PREFIX/</span>"]
end
PVC["<span style='color:#000'>PVC reports-stage<br/>ReadWriteOnce, 1Gi<br/>mounted at /reports in both pods</span>"]
Secret["<span style='color:#000'>Secret {fullname}-minio<br/>keys: rootUser, rootPassword</span>"]
MinIO["<span style='color:#000'>MinIO bucket cogniverse-backups<br/>(hostStorage.backup.bucket default)<br/>prefix REPORTS_PREFIX</span>"]
Schedule --> Generate
Generate -->|writes /reports/*.json| PVC
PVC -->|reads /reports/*.json| Upload
Secret --> Upload
Upload --> MinIO
style Schedule fill:#90caf9,stroke:#1565c0,color:#000
style Generate fill:#ce93d8,stroke:#7b1fa2,color:#000
style Upload fill:#ce93d8,stroke:#7b1fa2,color:#000
style PVC fill:#b0bec5,stroke:#546e7a,color:#000
style Secret fill:#ffcc80,stroke:#ef6c00,color:#000
style MinIO fill:#a5d6a7,stroke:#388e3c,color:#000