{% extends "base.html" %} {% block title %}Memory Hub — Agent Wasp{% endblock %} {% block page_css %} {% endblock %} {% block content %} {# ── Type meta ────────────────────────────────────────────────────────────── #} {% set TYPE_META = { "facts": {"color":"#3B82F6","badge":"badge-info"}, "working": {"color":"#F59E0B","badge":"badge-warning"}, "episodic": {"color":"#9CA3AF","badge":"badge-neutral"}, "semantic": {"color":"#8B5CF6","badge":"badge-secondary"}, "policy": {"color":"#A78BFA","badge":"badge-primary"}, "meta": {"color":"#F87171","badge":"badge-error"}, } %} {% set type_counts = [ ("facts",stats.facts),("working",stats.working),("episodic",stats.episodic), ("semantic",stats.semantic),("policy",stats.policy),("meta",stats.meta), ] %} {% set total_safe = stats.total if stats.total > 0 else 1 %} {%- set _dv = namespace(count=stats.total,color="",label="total") -%} {% if current_type and tab=="store" %} {%- for _n,_c in type_counts -%}{%- if _n==current_type -%} {%- set _dv.count=_c -%}{%- set _dv.color=TYPE_META[_n].color -%}{%- set _dv.label=_n -%} {%- endif -%}{%- endfor -%} {% endif %} {% set display_count=_dv.count %}{% set display_color=_dv.color %}{% set display_label=_dv.label %} {# ── Tab definitions ──────────────────────────────────────────────────────── #} {% set tabs = [ ("store", "Store", stats.total, "M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4", "#A78BFA"), ("kg", "Graph", layer_counts.kg_nodes, "M9 3H5a2 2 0 00-2 2v4m6-6h10a2 2 0 012 2v4M9 3v18m0 0h10a2 2 0 002-2V9M9 21H5a2 2 0 01-2-2V9m0 0h18", "#8B5CF6"), ("procedural","Procedural",layer_counts.procedural, "M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01","#F97316"), ("timeline", "Timeline", layer_counts.timeline, "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z", "#10B981"), ("dreams", "Dreams", layer_counts.dreams, "M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z", "#6366F1"), ] %}

Memory Hub

{{ stats.total }} store entries · {{ layer_counts.procedural }} procedures · {{ layer_counts.timeline }} timeline · {{ layer_counts.dreams }} dreams

{% for tid, tlabel, tcount, ticon, tcolor in tabs %} {{ tlabel }} {% if tcount > 0 %} {{ tcount }} {% endif %} {% endfor %}
{% if tab == "store" %}
{%- set circ=276.46 -%}
{% if current_type %} {%- set seg=(display_count/total_safe*circ)|round(2) -%} {% else %} {%- set min_seg=10.0 -%}{%- set ns_nz=namespace(n=0) -%} {%- for _n,_c in type_counts -%}{%- if _c>0 -%}{%- set ns_nz.n=ns_nz.n+1 -%}{%- endif -%}{%- endfor -%} {%- set avail=circ-ns_nz.n*min_seg -%}{%- set ns=namespace(off=0) -%} {% for name,count in type_counts %}{%- if count>0 -%} {%- set meta=TYPE_META[name] -%}{%- set seg=(min_seg+count/total_safe*avail)|round(2) -%} {%- set ns.off=ns.off+seg -%}{%- endif -%}{% endfor %} {% endif %}
{{ display_count|int }} {{ display_label }}
{{ "%.1f"|format(stats.size_bytes/1024) }} KB
{% for name,count in type_counts %} {% set meta=TYPE_META.get(name,{"color":"#6B7280"}) %} {% set pct=((count/total_safe)*100)|round(1) %}
{{ name }}
{{ count }} {{ pct }}%
{% endfor %}
All {{ stats.total }} {% for name,count in type_counts %} {% set meta=TYPE_META.get(name,{"color":"#6B7280"}) %} {% if count>0 %} {{ name }} {{ count }} {% endif %} {% endfor %}
{% if current_type %}{% endif %}
{% if search %}×{% endif %}
{% if entries %}
Showing {{ (page-1)*per_page+1 }}–{{ (page-1)*per_page+entries|length }}{% if current_type %} · {{ current_type }}{% endif %}{% if search %} · "{{ search }}"{% endif %} page {{ page }}
{% endif %}
{% for entry in entries %} {% set mt=entry.memory_type.value %} {% set meta=TYPE_META.get(mt,{"color":"#6B7280","badge":"badge-neutral"}) %}
{{ mt }} {% for tag in entry.tags[:3] %}{{ tag }}{% endfor %} {% if entry.tags|length>3 %}+{{ entry.tags|length-3 }}{% endif %}

{{ entry.summary or "(no summary)" }}

{% else %}

No memories{% if search %} for "{{ search }}"{% elif current_type %} of type {{ current_type }}{% endif %}

{% if search or current_type %}Clear{% endif %}
{% endfor %}
{% if entries|length==per_page or page>1 %}
{% if page>1 %}Prev{% endif %} {% if entries|length==per_page %}Next{% endif %}
{% endif %} {% elif tab == "kg" %} {# Entity type → color/icon map #} {% set ET_META = { "person": {"color":"#F97316","icon":"👤","label":"People"}, "concept": {"color":"#8B5CF6","icon":"💡","label":"Concepts"}, "preference": {"color":"#EC4899","icon":"❤️","label":"Preferences"}, "fact": {"color":"#3B82F6","icon":"📌","label":"Facts"}, "organization": {"color":"#10B981","icon":"🏢","label":"Organizations"}, "asset": {"color":"#F59E0B","icon":"💰","label":"Assets"}, "place": {"color":"#06B6D4","icon":"📍","label":"Places"}, "event": {"color":"#6366F1","icon":"📅","label":"Events"}, } %} {# Stats bar #}
{%- set type_list = kg_nodes|map(attribute='entity_type')|unique|list if kg_nodes else [] -%}
Nodes {{ layer_counts.kg_nodes }}
Relations {{ layer_counts.kg_relations }}
Types {{ type_list|length }}
{%- for etype in type_list -%} {%- set em = ET_META.get(etype, {"color":"#6B7280","icon":"◆","label":etype}) -%} {%- set cnt = kg_nodes|selectattr('entity_type','equalto',etype)|list|length -%}
{{ em.icon }} {{ cnt }} {{ etype }}
{%- endfor -%}
{% if kg_nodes %} {# Group nodes by entity_type #} {% set seen_types = namespace(list=[]) %} {% for node in kg_nodes %} {% if node.entity_type not in seen_types.list %} {% set seen_types.list = seen_types.list + [node.entity_type] %} {% endif %} {% endfor %} {% for etype in seen_types.list %} {%- set em = ET_META.get(etype, {"color":"#6B7280","icon":"◆","label":etype}) -%} {%- set group_nodes = kg_nodes|selectattr('entity_type','equalto',etype)|list -%}
{{ em.icon }} {{ em.label }} {{ group_nodes|length }}
{% for node in group_nodes %}
{{ em.icon }}
{{ node.name }}
{{ (node.confidence*100)|round|int }}% {% if node.updated_at %} {{ node.updated_at.strftime('%m-%d') }} {% endif %}
{% if node.description %}

{{ node.description }}

{% endif %}
{% endfor %}
{% endfor %} {% else %}

No knowledge graph data yet

{% endif %} {% if kg_relations %}
Relations {{ kg_relations|length }} shown
{% for rel in kg_relations %} {%- set from_name = kg_node_names.get(rel.from_node_id, rel.from_node_id[:8] ~ "…") -%} {%- set to_name = kg_node_names.get(rel.to_node_id, rel.to_node_id[:8] ~ "…") -%}
{{ from_name }} {{ rel.relation_type }} {{ to_name }} {% if rel.value %}{{ rel.value[:50] }}{% endif %}
{% endfor %}
{% endif %} {% if kg_nodes|length == per_page or kg_relations|length == per_page or page > 1 %}
{% if page>1 %}Prev{% endif %} {% if kg_nodes|length==per_page or kg_relations|length==per_page %}Next{% endif %}
{% endif %} {% elif tab == "procedural" %}
{%- set ns_ps = namespace(total_s=0, total_ok=0, total_fail=0) -%} {%- for p in procedural -%} {%- set ns_ps.total_s = ns_ps.total_s + (p.steps|length) -%} {%- set ns_ps.total_ok = ns_ps.total_ok + p.success_count -%} {%- set ns_ps.total_fail = ns_ps.total_fail + p.failure_count -%} {%- endfor -%} {% set avg_success = (ns_ps.total_ok / (ns_ps.total_ok + ns_ps.total_fail + 1) * 100)|round|int %} {% for label, val, color in [ ("Procedures", layer_counts.procedural, "#F97316"), ("Showing", procedural|length, "#FB923C"), ("Avg Success", avg_success|string ~ "%", "#FBBF24"), ] %}
{{ val }}
{{ label }}
{% endfor %}
{% for proc in procedural %} {% set total_runs = proc.success_count + proc.failure_count %} {% set success_pct = (proc.success_count / total_runs * 100)|round|int if total_runs > 0 else 0 %} {% set pid = "proc-" ~ loop.index %}
{# Header row #}
🔧
{{ proc.name }} {{ proc.steps|length }} steps {% if total_runs > 0 %} {{ success_pct }}% ok · {{ total_runs }} runs {% endif %}
{% if proc.description %}

{{ proc.description }}

{% endif %} {# Trigger keywords #} {% if proc.trigger_keywords %}
triggers: {% for kw in proc.trigger_keywords[:6] %} {{ kw }} {% endfor %} {% if proc.trigger_keywords|length > 6 %}+{{ proc.trigger_keywords|length - 6 }}{% endif %}
{% endif %}
{% if proc.steps %} {% endif %}
{# Steps accordion #} {% if proc.steps %} {% endif %}
{% else %}

No procedural memories yet

{% endfor %}
{% if procedural|length == per_page or page > 1 %}
{% if page>1 %}Prev{% endif %} {% if procedural|length==per_page %}Next{% endif %}
{% endif %} {% elif tab == "timeline" %}
{% set obs_types = timeline|map(attribute='observation_type')|unique|list if timeline else [] %} {% for label, val, color in [ ("Observations", layer_counts.timeline, "#10B981"), ("Showing", timeline|length, "#34D399"), ("Entities", timeline|map(attribute='entity')|unique|list|length if timeline else 0, "#6EE7B7"), ("Types", obs_types|length, "#A7F3D0"), ] %}
{{ val }}
{{ label }}
{% endfor %}
{% set OBS_COLORS = {"price":"#F59E0B","event":"#8B5CF6","state":"#3B82F6","mention":"#9CA3AF","metric":"#10B981"} %} {% set OBS_ICONS = {"price":"💰","event":"📅","state":"🔄","mention":"💬","metric":"📊"} %} {# Entity type filter chips #} {% if timeline %} {%- set tl_entities = timeline|map(attribute='entity')|unique|list -%} {%- set tl_types = timeline|map(attribute='observation_type')|unique|list -%}
entities: {% for ent in tl_entities[:8] %} {{ ent }} {% endfor %} {% if tl_entities|length > 8 %}+{{ tl_entities|length - 8 }}{% endif %} {% if tl_entities|length > 1 %} all {% endif %}
{% endif %}
{% for obs in timeline %} {% set oc = OBS_COLORS.get(obs.observation_type, "#9CA3AF") %} {% set oico = OBS_ICONS.get(obs.observation_type, "·") %}
{{ oico }}
{{ obs.entity }} {{ obs.observation_type }} {% if obs.confidence < 1.0 %} {{ (obs.confidence*100)|round|int }}% {% endif %}
{# Value: price gets big, others get normal #} {% if obs.observation_type == "price" %}

{{ obs.value }}

{% else %}

{{ obs.value }}

{% endif %}
{{ obs.observed_at.strftime('%Y-%m-%d %H:%M') if obs.observed_at else '—' }} {% if obs.source %}· {{ obs.source }}{% endif %}
{% else %}

No timeline observations yet

{% endfor %}
{% if timeline|length == per_page or page > 1 %}
{% if page>1 %}Prev{% endif %} {% if timeline|length==per_page %}Next{% endif %}
{% endif %} {% elif tab == "dreams" %}
{% set total_consolidated = dreams|sum(attribute='memories_consolidated') if dreams else 0 %} {% set total_kg = dreams|sum(attribute='kg_nodes_added') if dreams else 0 %} {% set total_improvements = dreams|sum(attribute='improvements_proposed') if dreams else 0 %} {% for label, val, color in [ ("Dream Cycles", layer_counts.dreams, "#6366F1"), ("Mem. Consolidated", total_consolidated, "#818CF8"), ("KG Nodes Added", total_kg, "#A5B4FC"), ("Improvements", total_improvements, "#C7D2FE"), ] %}
{{ val }}
{{ label }}
{% endfor %}
{% for dream in dreams %} {%- set did = "dream-" ~ loop.index -%} {%- set is_first = loop.first -%}
🌙
{# Header: date + stats chips #}
{{ dream.started_at.strftime('%Y-%m-%d %H:%M') if dream.started_at else '—' }} {{ dream.duration_seconds }}s
{# Activity pills #}
{% if dream.memories_consolidated > 0 %} {{ dream.memories_consolidated }} consolidated {% endif %} {% if dream.kg_nodes_added > 0 %} +{{ dream.kg_nodes_added }} KG nodes {% endif %} {% if dream.improvements_proposed > 0 %} {% if dream.improvements_json %} {% else %} 💡 {{ dream.improvements_proposed }} improvement{{ "s" if dream.improvements_proposed != 1 else "" }} {% endif %} {% endif %} {% if dream.prefetch_done %} prefetch ✓ {% endif %}
{# Improvements list accordion #} {% if dream.improvements_json %} {% endif %} {# Reflection — first dream fully visible, others in accordion #} {% if dream.reflection %} {% if is_first %}

{{ dream.reflection }}

{% else %}
{% endif %} {% endif %}
{% else %}

No dream cycles yet

{% endfor %}
{% if dreams|length == per_page or page > 1 %}
{% if page>1 %}Prev{% endif %} {% if dreams|length==per_page %}Next{% endif %}
{% endif %} {% endif %}{# end tab #} {% endblock %} {% block extra_scripts %} {% endblock %}