{% 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 body_attrs %} data-sidebar-scope="learning" data-sidebar-default="collapsed"{% 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 scoped and selected_agent_id %}Clear scope{% endif %}
{% if error %}{% endif %} {% if action %}

{{ action.message }}{% if action.at %} {{ action.at }}{% endif %}

{% 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 %}
{% if capture_enabled is not none %}
Capture{{ 'On' if capture_enabled else 'Off' }}
{% endif %}
Records{{ '{:,}'.format(view.record_count) }}
Events{{ '{:,}'.format(view.event_count) }}
Uncompiled{{ '{:,}'.format(view.pending_count) }}
Artifacts{{ '{:,}'.format(view.artifact_count) }}
To forget{{ to_forget }}
Tombstoned{{ view.tombstone_count }}
Run success{% if view.run_success_rate is none %}—{% else %}{{ '%.0f'|format(view.run_success_rate) }}%of {{ view.runs }}{% endif %}
Evidence used{% if view.evidence_mean_tokens is none %}—{% else %}{{ fmt_compact(view.evidence_mean_tokens) }}mean{% endif %}
Token budget{{ '{:,}'.format(view.evidence_budget) if view.evidence_budget else '—' }}
Last compile{{ relative_time(view.last_compile_at, now) if view.last_compile_at else 'Never' }}
{% if view.series %} {% set peak = [view.series|map(attribute='count')|max, 1]|max %}
14 days {% for day in view.series %}{% if day.count %}{% set h = [20 * day.count / peak, 2]|max %}{% set fh = h * day.failed / day.count %}{{ day.day }}: {{ day.count }}{% if day.failed %}{% endif %}{% endif %}{% endfor %}
{% endif %}
{% if view.pending_count and view.pending_count != view.pending_in_scope %}

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 %}

Approve reversible forgetting

The dry run found {{ action.report.candidate_count }} candidate(s). Applying writes tombstones only; immutable events are kept.

Awaiting approval
{% endif %} {% if view.record_count == 0 %} {% call empty_state('No learning events in this scope', ("Learning capture is off for this agent. Turn on the learning control plane in the agent's settings; while the agent runs it records retrievals, tool calls, workflows and outcomes here." if capture_enabled == false else "Events are recorded while an agent with the learning control plane enabled runs: retrievals, tool calls, workflows and outcomes. " ~ ("Clear the memory, user or thread filter to widen the scope, or turn on the control plane in the agent's settings." if scoped else "Run the agent in the playground to record some, or turn on the control plane in the agent's settings."))) %} Edit agent settings {% if scoped %}Clear scope{% elif capture_enabled %}Open playground{% endif %} {% endcall %} {% else %} {% if view.events %}
{% set shown_pending = view.events|rejectattr('compiled')|list|length %} {% if shown_pending %}{% endif %} {% set shown_failed = view.events|selectattr('signal', 'equalto', 'bad')|list|length %} {% if shown_failed %}{% endif %} {% for key, label, count in view.families %}{% set shown = view.events|selectattr('family', 'equalto', key)|list|length %}{% if shown %}{% endif %}{% endfor %}

j k movet traces scopec copy/ filter

{% endif %}
{% if view.events %}
{% for row in view.events %} {% endfor %}
Learning events in this scope, newest first. Select a row to read the full event.
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' }}
{% if view.has_more_events %}

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 %}

Compile streams

One per exact memory, user and thread
{% if view.streams %}
{% for stream in view.streams %} {% set s = stream.scope %} {% set current = s.memory_id == memory_id and s.user_id == user_id and s.thread_id == thread_id %} {% endfor %}
ScopeEventsUncompiledLast compileInspect
{{ 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' }}
{% else %}

No event streams in this scope.

{% endif %}

Forgetting plan

{% if plan %}{{ plan.state_label }}{% endif %}
{% if plan %}
Candidates
{{ plan.candidate_count }}
Retained
{{ plan.retained }}
{{ 'Tombstoned' if plan.state == 'applied' else 'Planned' }}
{% if plan.state == 'applied' %}{{ plan.tombstoned }}{% else %}{{ relative_time(plan.planned_at, now) if plan.planned_at else '—' }}{% endif %}

Plan {{ id_chip(plan.plan_id, 24) }}{% if plan.state == 'applied' %} · applied {{ plan.applied }}{% if plan.approved_by %} by {{ plan.approved_by }}{% endif %}{% endif %}

{% if plan.candidates %}
{% for item in plan.candidates[:8] %} {% endfor %}
ArtifactReasonUtilityAge
{{ 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 '—' }}
{% if plan.candidates|length > 8 %}

{{ 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 %}

Compiled artifacts

{% if view.artifacts %}{{ view.artifacts|length }} shown · newest first{% endif %}
{% if view.artifacts %}
{% for artifact in view.artifacts %} {% endfor %}
UpdatedKindVerificationUtilitySourcesContentArtifact 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 %}
{% else %} {{ empty_state('No compiled artifacts yet', 'Compilation turns recorded learning events into reusable artifacts. Select Compile pending events to build them now; agents also compile automatically as events accumulate.' if not ui_read_only else 'Compilation turns recorded learning events into reusable artifacts. This console is read-only, so compile from the SDK or CLI.') }} {% endif %}
{% if view.events %} {% endif %}
{% endif %} {% elif not selected_agent_id %} {{ empty_state('No saved agent selected', 'The learning control plane inspects saved agents. Create an agent here, or save one from Python with agent.save(), then return to inspect its learning stream.', '/agents/new', 'Create agent') }} {% endif %} {% endblock %}