{% extends "base.html" %} {% from '_ui.html' import page_header, empty_state, id_chip %} {% from '_learning_tabs.html' import learning_tabs %} {% block title %}Learning control plane - Memorizz{% endblock %} {% block head %}{% endblock %} {% block content %} {#- Links out of an event: its trace, and the exact scope its compile stream covers. -#} {% macro trace_url(row) -%} {%- if row.trace_id -%}/traces?agent_id={{ selected_agent_id|urlencode }}&root_trace_id={{ row.trace_id|urlencode }}#trace-timeline {%- elif row.run_id -%}/traces?agent_id={{ selected_agent_id|urlencode }}&run_id={{ row.run_id|urlencode }}#trace-timeline {%- endif -%} {%- endmacro %} {% macro scope_url(scope) -%} /learning-control-plane?{{ {'agent_id': selected_agent_id, 'memory_id': scope.memory_id, 'user_id': scope.user_id, 'thread_id': scope.thread_id}|urlencode }} {%- endmacro %} {% set lcp_description = 'Inspect bounded retrieval evidence, immutable learning events, incremental compilation and reversible forgetting for one agent scope.' %} {% set tabs = learning_tabs('learning-control-plane', selected_agent_id) %} {% if selected_agent_id and view and not ui_read_only %} {% call page_header('Learning control plane', lcp_description, 'Continual learning', nav=tabs) %}
{% endcall %} {% else %} {{ page_header('Learning control plane', lcp_description, 'Continual learning', nav=tabs) }} {% endif %} {% set scoped = memory_id or user_id or thread_id %} {% if error %}{% endif %} {% if action %}{% endif %} {% if selected_agent_id and view %} {% set plan = view.plan %} {% set to_forget = plan.candidate_count if plan and plan.state == 'awaiting' else 0 %}Compile works on one exact scope. {{ view.pending_in_scope }} of {{ view.pending_count }} uncompiled events are in this scope{% if not scoped %} (no memory, user or thread){% endif %}. Pick a stream under Compile streams, or select an event and use Inspect this scope, then compile.
{% endif %} {% if not ui_read_only and action and action.kind == 'forget-plan' and action.report.candidate_count > 0 %}The dry run found {{ action.report.candidate_count }} memory record(s) whose retention score (importance, recalls, recency since last use) decayed below the threshold. Applying hides them from retrieval; nothing is deleted and each can be restored below.
{% else %}The dry run found {{ action.report.candidate_count }} candidate(s). Applying writes tombstones only; immutable events are kept.
{% endif %}{{ suppressed|length }} record(s) hidden from retrieval by an approved retention or consolidation plan. They are still stored and audited; restore one to make it retrievable again.
| Record | Type | Memory ID | Reason | Approved by | Since | {% if not ui_read_only %}Restore | {% endif %}
|---|---|---|---|---|---|---|
| {{ item.record_id[:12] }} | {{ item.memory_type|replace('_', ' ') }} | {{ (item.memory_id or '—')[:14] }} | {{ item.reason or '—' }} | {{ item.approver or '—' }} | {{ (item.suppressed_at or '')[:16] or '—' }} | {% if not ui_read_only %}{% endif %} |
{{ suppressed|length - 50 }} more suppressed in this scope.
{% endif %}Merges duplicate entities and summaries, retires the older of two facts about the same thing, and adds facts a user stated that were never stored, each quoting the user. The model steps run on {% if consolidation.model %}{{ consolidation.model }} ({{ 'background model' if consolidation.model_is_background else 'the chat model; set a background model on the agent' }}){% else %}no model, so only duplicates are checked{% endif %}. Nothing is deleted; every change can be undone. Conversations that ran sealed are never read.
This agent keeps entity facts per conversation, so only duplicate summaries and knowledge-base chunks are checked.
{% endif %} {% if not ui_read_only %} {% endif %} {% if cact and cact.kind == 'consolidation-plan' %} {% set report = cact.report %}Plan {{ report.plan_id }} for memory {{ report.memory_id }}{% if report.user_id %}, user {{ report.user_id }}{% endif %}{% if report.model %}, on {{ report.model }}{% endif %}{% if report.thread_ids %}, reading {{ report.thread_ids|length }} conversation(s){% endif %}.
{% for warning in report.warnings %}{{ warning }}
{% endfor %} {% if report.ops %}| Change | Memory | Why |
|---|
Nothing to change: no duplicates, no stale facts and nothing new in the conversations read.
{% endif %}Applied plan {{ report.plan_id }} as {{ report.approved_by }}: {{ report.applied.merge_entities }} merge(s), {{ report.applied.suppress_duplicate }} hidden duplicate(s), {{ report.applied.retire_attribute }} retired fact(s), {{ report.applied.add_fact }} added fact(s). The changes are on the memory evolution timeline.
{% for error in report.errors %}{% endfor %}| Plan | Memory ID | Changes | Approved by | When | {% if not ui_read_only %}Undo | {% endif %}
|---|---|---|---|---|---|
| {{ plan.plan_id[:24] }} | {{ plan.memory_id or '—' }} | {{ plan.changes }} recorded | {{ plan.approver or '—' }} | {{ (plan.at or '')[:16] }} | {% if not ui_read_only %}{% if plan.undone %}Undone{% else %} {% endif %} | {% endif %}
Looks for work the user keeps coming back to across conversations, then adds one sentence to the persona's goals or background, keeping the quotes it rests on. Reviews run on {% if pl.model %}{{ pl.model }} ({{ 'background model' if pl.model_is_background else 'the chat model; set a background model on the agent' }}){% else %}no model yet{% endif %}. The name and role never change this way, and conversations that ran sealed are never read.
This agent serves several users who share its persona, so it doesn't learn from any one of them.
{% elif not pl.enabled %}Off for this agent. Turn it on in the agent's persona settings, or run one review now.
{% endif %} {% if pl.last_review %}Last review {{ (pl.last_review.at or '')[:16]|replace('T', ' ') }}: {{ pl.last_review.reason }}
{% endif %} {% if not ui_read_only and not pl.multi_tenant %} {% endif %} {% if pl.pending %}Add to {{ pl.pending.field }}: {{ pl.pending.addition }}
| Version | Added | Rests on | Approved by | When | {% if not ui_read_only %}Undo | {% endif %}
|---|---|---|---|---|---|
| v{{ change.version }} | {{ change.addition or '—' }} {{ change.fields|join(', ') }} |
|
{{ change.approved_by or '—' }} | {{ (change.timestamp or '')[:16]|replace('T', ' ') }} | {% if not ui_read_only %}{% if change.undone %}Undone{% else %} {% endif %} | {% endif %}
| Summary | Run | ||||||
|---|---|---|---|---|---|---|---|
| {% if row.recorded %}{{ row.recorded[5:] }}{{ row.recorded[5:16] }}{% else %}—{% endif %} | {{ row.label }} | {{ row.summary }} | {{ row.scope.memory_id or '—' }} | {{ row.scope.user_id or '—' }} | {{ row.scope.thread_id or '—' }} | {{ row.run_ref or '—' }} | {{ 'Compiled' if row.compiled else 'Uncompiled' }} |
No events match this filter.
Showing the newest {{ view.events|length }} of {{ '{:,}'.format(view.event_count) }} events. Narrow the memory, user or thread scope to see older ones.
{% endif %} {% else %} {{ empty_state('No learning events in this scope', 'This scope holds compiled artifacts or checkpoints but no events. Clear the memory, user or thread filter to see the events they were built from.', '/learning-control-plane?agent_id=' ~ selected_agent_id|urlencode, 'Clear scope') }} {% endif %}| Scope | Events | Uncompiled | Last compile | Inspect |
|---|---|---|---|---|
| {{ s.memory_id or '—' }} · {{ s.user_id or '—' }} · {{ s.thread_id or '—' }} | {{ stream.events }} | {{ stream.pending }} | {{ stream.last_compile[5:16] if stream.last_compile else 'Never' }} | {% if current %}In scope{% else %}Inspect{% endif %} |
No event streams in this scope.
{% endif %}| Artifact | Reason | Utility | Age |
|---|---|---|---|
| {{ id_chip(item.target_id, 16) }} | {{ item.reason }} | {{ '%.2f'|format(item.utility) if item.utility is not none else '—' }} | {{ '%.0f'|format(item.age_days) ~ 'd' if item.age_days is not none else '—' }} |
{{ plan.candidates|length - 8 }} more in the plan.
{% endif %} {% endif %} {% if plan.state == 'awaiting' and not (action and action.kind == 'forget-plan') %}Nothing has been removed. {{ 'Select Plan forgetting (dry run) for this scope to review the latest candidates and approve them.' if not ui_read_only else 'This console is read-only; apply the plan from the SDK with agent.apply_forgetting().' }}
{% endif %} {% else %}No dry run yet. A plan lists compiled artifacts that are duplicates or whose utility has decayed, so they can be tombstoned. Nothing is removed until an operator approves, and immutable events are always kept.{% if ui_read_only %} This console is read-only; plan forgetting from the SDK with agent.plan_forgetting().{% else %} Select Plan forgetting (dry run) to create one.{% endif %}
{% endif %}| Updated | Kind | Verification | Utility | Sources | Content | Artifact ID |
|---|---|---|---|---|---|---|
| {{ artifact.updated[5:16] or '—' }} | {{ artifact.kind_label }} | {% if artifact.tombstoned %}Tombstoned{% elif artifact.verified %}Verified{% else %}Unverified{% endif %} | {{ '%.2f'|format(artifact.utility) if artifact.utility is not none else '—' }} | {{ artifact.sources or '—' }} | {{ artifact.content or '—' }} | {% if artifact.artifact_id %}{{ id_chip(artifact.artifact_id|string, 20) }}{% else %}—{% endif %} |