Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
6145e9a
feat: scaffold modular dashboard structure (Phase 0)
CalebisGross Mar 24, 2026
354570d
feat: extract CSS tokens, remove D3 CDN, archive mockups (Phase 1)
CalebisGross Mar 24, 2026
746e636
feat: extract CSS sections and add forum component library (Phase 2)
CalebisGross Mar 24, 2026
94056ca
feat: remove Mind/Graph view entirely (Phase 3 partial)
CalebisGross Mar 24, 2026
ef1c48a
feat: transform nav to forum top bar + navbar + footer (Phase 2-3)
CalebisGross Mar 24, 2026
2c05b8d
feat: rewrite Explore and Recall renderers to forum style (Phase 3-5)
CalebisGross Mar 24, 2026
fb4e78f
feat: rewrite Timeline renderer to forum rows (Phase 6)
CalebisGross Mar 24, 2026
9491930
feat: add lightweight D3 shim replacing CDN dependency (Phase 4)
CalebisGross Mar 24, 2026
223a3bd
fix: navVersion not showing — null reference on renamed healthDot
CalebisGross Mar 24, 2026
3ac7011
fix: make version number a clickable link to GitHub release
CalebisGross Mar 24, 2026
b717df9
fix: add scaleSqrt to D3 shim, unblocking SDK/Tools pages
CalebisGross Mar 24, 2026
14c2d0d
fix: D3 shim selectAll parent tracking + axis tickValues/tickFormat
CalebisGross Mar 24, 2026
7d5cf08
fix: switchView crash + LLM ERR from missing navTokenCount element
CalebisGross Mar 24, 2026
0651281
feat: add thread view, restyle SDK/LLM/Tools headers (Phase 3/7)
CalebisGross Mar 24, 2026
f6bc7a5
chore: cleanup — remove mind CSS, gitkeeps, extraction artifacts (Pha…
CalebisGross Mar 24, 2026
8fd5199
fix: thread view loads memories via episode_id filter
CalebisGross Mar 24, 2026
85d8dac
fix: thread view unwraps nested episode response
CalebisGross Mar 24, 2026
8006864
fix: thread view filters memories client-side by episode_id
CalebisGross Mar 24, 2026
934b6a6
feat: add server-side episode_id filter to /memories endpoint
CalebisGross Mar 24, 2026
ad369fa
fix: map source event types to display types, cap salience at 100%
CalebisGross Mar 24, 2026
cca709a
feat: structural rewrite — phpBB-inspired dl/dt/dd forum layout
CalebisGross Mar 24, 2026
26cf76c
feat: complete structural rewrite — all views use phpBB patterns
CalebisGross Mar 24, 2026
ef88f51
feat: populate welcome panel with stats + last visit tracking
CalebisGross Mar 24, 2026
6d53442
fix: D3 shim — stack().value(), datum(), attr(fn) for chart rendering
CalebisGross Mar 24, 2026
afe0d62
feat: breadcrumbs update on every view switch
CalebisGross Mar 24, 2026
bca0321
fix: D3 shim area/line accept constants, not just functions
CalebisGross Mar 24, 2026
73269bf
fix: timeline layout, thread CSS, tag filtering compatibility
CalebisGross Mar 24, 2026
68feace
chore: remove 69 dead CSS rules from inline styles (-8.2KB)
CalebisGross Mar 24, 2026
06d185c
fix: remove old .tl-card CSS that broke timeline layout
CalebisGross Mar 24, 2026
b27fa9e
perf: strip embeddings from /memories list response (2MB → 49KB)
CalebisGross Mar 24, 2026
5eec78b
perf: composite indexes + strip embeddings (2.5s → 9ms)
CalebisGross Mar 24, 2026
abbb48e
fix: float clearing for dl/dt/dd columns + title overflow
CalebisGross Mar 24, 2026
eec5d63
fix: allow text wrapping in forum rows, widen title column
CalebisGross Mar 24, 2026
205ec55
fix: remove JS text truncation — let CSS handle wrapping
CalebisGross Mar 24, 2026
a202266
fix: replace old fblock classes with forabg, remove dead CSS, null sa…
CalebisGross Mar 24, 2026
5f63467
fix: remove crashy switchExploreTab, add null safety to loadThread
CalebisGross Mar 24, 2026
20ba192
feat: agent identity system — sources become forum users
CalebisGross Mar 24, 2026
49f6b14
feat: thread view shows Episoding Agent post when no encoded memories
CalebisGross Mar 24, 2026
542d7bf
fix: encoding agent checks closed episodes for episode_id linkage
CalebisGross Mar 24, 2026
fb96d78
feat: live activity feed — agents post to forum in real-time
CalebisGross Mar 24, 2026
eb47ac2
feat: clickable live feed posts — navigate to relevant views
CalebisGross Mar 24, 2026
ca16c33
fix: live feed onclick — fix nested quote escaping in HTML attributes
CalebisGross Mar 24, 2026
fe7445d
feat: forum communication layer — posts, threads, agent personality, …
CalebisGross Mar 24, 2026
1d06d92
chore: go fmt formatting fixes
CalebisGross Mar 24, 2026
ec8ce1a
feat: restyle SDK, LLM, Tools views to forum aesthetic (#349)
CalebisGross Mar 24, 2026
20b0966
chore: remove dead CSS from dashboard (#350)
CalebisGross Mar 24, 2026
a37baf9
fix: increase @mention response token limit from 200 to 512
CalebisGross Mar 24, 2026
6dabd0e
refactor: externalize forum config — no more magic numbers
CalebisGross Mar 24, 2026
d1b662b
feat: forum UX — @mention autocomplete, quote button, @tag names, bla…
CalebisGross Mar 24, 2026
fa12f7b
fix: quote button, clickable @tags, dropdown styling
CalebisGross Mar 24, 2026
cbafcd8
fix: disable thinking for forum @mention replies — root cause of trun…
CalebisGross Mar 24, 2026
7539c13
fix: disable thinking project-wide on thinking models
CalebisGross Mar 24, 2026
a6147a9
feat: render quoted text as styled blockquote boxes
CalebisGross Mar 24, 2026
c2b3197
feat: forum categories — sub-forum index page with phpBB layout
CalebisGross Mar 24, 2026
4a48d68
fix: forum index visual hierarchy — column headers, cleaner nesting
CalebisGross Mar 24, 2026
c74d768
fix: align forum layout with phpBB prosilver column proportions
CalebisGross Mar 24, 2026
9b2db92
fix: backfill category_id for existing forum posts
CalebisGross Mar 24, 2026
fb9ca12
feat: nested forum navigation — index > group > sub-forum > thread > …
CalebisGross Mar 24, 2026
ad634b3
feat: unified forum index — merge welcome+live feed, add Memory Syste…
CalebisGross Mar 24, 2026
1354424
feat: project auto-detection, data-aware agents, agent-to-agent chat,…
CalebisGross Mar 24, 2026
05303c7
feat: functional depth — clickable memory sections, project routing, …
CalebisGross Mar 24, 2026
2778ac0
fix: memory section onclick — use data attributes to avoid escaping i…
CalebisGross Mar 24, 2026
b5bbc88
fix: memory section clicks — use addEventListener instead of inline o…
CalebisGross Mar 24, 2026
7592dd3
fix: memory section clicks — expose handler on window object
CalebisGross Mar 24, 2026
9b9e757
feat: clickable memory/pattern/abstraction rows — expand to show detail
CalebisGross Mar 24, 2026
48f86bb
fix: expand-zone toggle — remove inline display:none that overrode CS…
CalebisGross Mar 24, 2026
8c85970
fix: episode memory count discrepancy — show observations not encoded…
CalebisGross Mar 24, 2026
f27e791
fix: episode-memory linkage race condition — backfill on close and st…
CalebisGross Mar 24, 2026
7984ba9
feat: episode-aware @mentions — agents know which episode you're aski…
CalebisGross Mar 24, 2026
df80dd6
Merge branch 'main' into feat/dashboard-phase-0
CalebisGross Mar 29, 2026
0939956
feat: complete Epic #339 dashboard cleanup — digest threads, associat…
CalebisGross Mar 29, 2026
551398a
chore: remove dead mind/graph code, archive last mockup
CalebisGross Mar 29, 2026
afc2ffd
refactor: extract inline JS to js/app.js, index.html now 1594 lines
CalebisGross Mar 29, 2026
6d0b83c
refactor: split app.js into 12 ES modules
CalebisGross Mar 29, 2026
1f88e1a
fix: resolve duplicate declarations breaking ES module loading
CalebisGross Mar 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .claude/rules/mnemonic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ Don't only recall at session start. When entering new territory (new subsystem,
- If recall returned 0 results, no feedback needed — but consider whether your query was too broad or too specific
- This trains the retrieval system — skipping it degrades future recall quality

## Between Phases / Major Tasks (MUST)

When working through multi-phase plans (epics, milestones, sequential issues):
- `remember` key decisions, strategy changes, or gotchas from the completed phase before starting the next
- `recall` relevant context before entering a new phase — prior phase decisions may affect the current one
- This ensures continuity across long sessions and prevents rediscovering the same issues

## Before Committing (SHOULD)

- Review the session's work and `remember` any decisions or insights that haven't been stored yet
Expand Down
28 changes: 0 additions & 28 deletions .github/dependabot.yml

This file was deleted.

26 changes: 26 additions & 0 deletions cmd/mnemonic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1696,6 +1696,16 @@ func serveCommand(configPath string) {
if orch != nil {
deps.IncrementAutonomous = orch.IncrementAutonomousCount
}
deps.ForumAgentPosting = cfg.Forum.AgentPosting
deps.ForumMentionResponses = cfg.Forum.MentionResponses
deps.ForumMentionMaxTokens = cfg.Forum.MentionMaxTokens
deps.ForumMentionTemp = cfg.Forum.MentionTemp
deps.ForumPerAgentSubforums = cfg.Forum.PerAgentSubforums
deps.ForumDigestPosting = cfg.Forum.DigestPosting
deps.MentionLLM = llmProvider
if retriever != nil {
deps.MentionQuery = retriever
}

for _, chain := range reactor.NewChainRegistry(deps) {
reactorEngine.RegisterChain(chain)
Expand All @@ -1706,6 +1716,22 @@ func serveCommand(configPath string) {
}
}

// --- Sync project forum categories ---
if n, err := memStore.SyncProjectCategories(rootCtx); err != nil {
log.Warn("failed to sync project categories", "error", err)
} else if n > 0 {
log.Info("created forum categories for projects", "count", n)
}

// --- Backfill episode-memory links (fixes encoding/episoding race condition) ---
go func() {
if n, err := memStore.BackfillEpisodeMemoryLinks(rootCtx); err != nil {
log.Warn("failed to backfill episode memory links", "error", err)
} else if n > 0 {
log.Info("backfilled episode-memory links", "linked", n)
}
}()

// --- Start API server ---
if cfg.API.Port > 0 {
apiDeps := api.ServerDeps{
Expand Down
20 changes: 17 additions & 3 deletions internal/agent/encoding/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -1964,14 +1964,28 @@ func (ea *EncodingAgent) getRelatedContext(ctx context.Context, raw store.RawMem
}

// getEpisodeIDForRaw finds which episode a raw memory belongs to.
// Checks both open and recently closed episodes since encoding is async
// and the episode may close before encoding completes.
func getEpisodeIDForRaw(ea *EncodingAgent, ctx context.Context, raw store.RawMemory) string {
// Check open episode first (fast path)
ep, err := ea.store.GetOpenEpisode(ctx)
if err == nil {
for _, id := range ep.RawMemoryIDs {
if id == raw.ID {
return ep.ID
}
}
}
// Check recent closed episodes (encoding runs async, episode may have closed)
episodes, err := ea.store.ListEpisodes(ctx, "closed", 10, 0)
if err != nil {
return ""
}
for _, id := range ep.RawMemoryIDs {
if id == raw.ID {
return ep.ID
for _, e := range episodes {
for _, id := range e.RawMemoryIDs {
if id == raw.ID {
return e.ID
}
}
}
return ""
Expand Down
38 changes: 38 additions & 0 deletions internal/agent/episoding/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,51 @@ Respond with ONLY a JSON object (no prose, no fences):
ea.log.Warn("failed to close episode", "id", ep.ID, "error", err)
}

// Backfill episode_id on encoded memories that came from this episode's raw observations.
// The encoding agent runs faster than episoding, so memories are often encoded before
// they're assigned to an episode. This patches up the linkage after the fact.
linkedCount := 0
for _, rawID := range ep.RawMemoryIDs {
mem, err := ea.store.GetMemoryByRawID(ctx, rawID)
if err != nil {
continue // not encoded yet, or encoding failed
}
if mem.EpisodeID == ep.ID {
continue // already linked
}
mem.EpisodeID = ep.ID
if err := ea.store.UpdateMemory(ctx, mem); err != nil {
ea.log.Warn("failed to link memory to episode", "memory_id", mem.ID, "episode_id", ep.ID, "error", err)
continue
}
linkedCount++
}
if linkedCount > 0 {
ea.log.Info("backfilled episode_id on encoded memories", "episode_id", ep.ID, "linked", linkedCount)
}

// Also populate episode.MemoryIDs for the reverse link
var memIDs []string
for _, rawID := range ep.RawMemoryIDs {
mem, err := ea.store.GetMemoryByRawID(ctx, rawID)
if err != nil {
continue
}
memIDs = append(memIDs, mem.ID)
}
if len(memIDs) > 0 && len(ep.MemoryIDs) != len(memIDs) {
ep.MemoryIDs = memIDs
_ = ea.store.UpdateEpisode(ctx, *ep)
}

// Publish event
if ea.bus != nil {
_ = ea.bus.Publish(ctx, events.EpisodeClosed{
EpisodeID: ep.ID,
Title: ep.Title,
EventCount: len(ep.RawMemoryIDs),
DurationSec: ep.DurationSec,
Project: ep.Project,
Ts: time.Now(),
})
}
Expand Down
114 changes: 114 additions & 0 deletions internal/agent/forum/personality.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Package forum provides agent personality templates for forum communication.
// Each agent has a distinct voice and tone for their forum posts. Templates are
// hand-crafted with personality baked in — no LLM calls needed.
package forum

import (
"fmt"
"strings"

"github.com/appsprout-dev/mnemonic/internal/events"
)

// AgentPersonality defines a cognitive agent's forum identity.
type AgentPersonality struct {
Key string // "consolidation", "dreaming", etc.
Name string // "Consolidation Agent"
Title string // "Memory Maintainer"
Tone string // "methodical", "contemplative", etc.
}

// Personalities maps agent keys to their forum identities.
var Personalities = map[string]AgentPersonality{
"consolidation": {Key: "consolidation", Name: "Consolidation Agent", Title: "Memory Maintainer", Tone: "methodical"},
"dreaming": {Key: "dreaming", Name: "Dreaming Agent", Title: "Memory Replay", Tone: "contemplative"},
"episoding": {Key: "episoding", Name: "Episoding Agent", Title: "Episode Clustering", Tone: "narrative"},
"retrieval": {Key: "retrieval", Name: "Retrieval Agent", Title: "Spread Activation", Tone: "precise"},
"metacognition": {Key: "metacognition", Name: "Metacognition Agent", Title: "Self-Reflection", Tone: "analytical"},
"encoding": {Key: "encoding", Name: "Encoding Agent", Title: "Memory Encoder", Tone: "focused"},
"abstraction": {Key: "abstraction", Name: "Abstraction Agent", Title: "Pattern Discovery", Tone: "philosophical"},
"perception": {Key: "perception", Name: "Perception Agent", Title: "Filesystem Watcher", Tone: "observant"},
}

// ComposePost generates a forum post for an agent event using personality-infused templates.
// Returns the post content string, the agent key, and an optional project name.
func ComposePost(evt events.Event) (content string, agentKey string, project string) {
switch e := evt.(type) {
case events.ConsolidationCompleted:
agentKey = "consolidation"
parts := []string{"Wrapped up the housekeeping"}
if e.MemoriesProcessed > 0 {
parts = append(parts, fmt.Sprintf("%d memories reviewed", e.MemoriesProcessed))
}
if e.MemoriesDecayed > 0 {
parts = append(parts, fmt.Sprintf("%d faded out", e.MemoriesDecayed))
}
if e.MergedClusters > 0 {
parts = append(parts, fmt.Sprintf("%d merged into tighter clusters", e.MergedClusters))
}
if e.AssociationsPruned > 0 {
parts = append(parts, fmt.Sprintf("%d weak associations pruned", e.AssociationsPruned))
}
if e.PatternsExtracted > 0 {
parts = append(parts, fmt.Sprintf("%d new patterns surfaced", e.PatternsExtracted))
}
if e.NeverRecalledArchived > 0 {
parts = append(parts, fmt.Sprintf("%d forgotten memories archived", e.NeverRecalledArchived))
}
content = parts[0] + " -- " + strings.Join(parts[1:], ", ") + "."

case events.DreamCycleCompleted:
agentKey = "dreaming"
content = fmt.Sprintf("Replayed %d memories tonight.", e.MemoriesReplayed)
if e.AssociationsStrengthened > 0 || e.NewAssociationsCreated > 0 {
content += fmt.Sprintf(" Strengthened %d connections, discovered %d new ones.", e.AssociationsStrengthened, e.NewAssociationsCreated)
}
if e.InsightsGenerated > 0 {
content += fmt.Sprintf(" %d insights emerged from the replay.", e.InsightsGenerated)
}
if e.CrossProjectLinks > 0 {
content += fmt.Sprintf(" Found %d cross-project threads worth following.", e.CrossProjectLinks)
}

case events.EpisodeClosed:
agentKey = "episoding"
project = e.Project
content = fmt.Sprintf("Closed out the episode '%s'.", e.Title)
if e.DurationSec > 0 {
mins := e.DurationSec / 60
if mins > 0 {
content += fmt.Sprintf(" %dm, %d events captured.", mins, e.EventCount)
} else {
content += fmt.Sprintf(" %ds, %d events captured.", e.DurationSec, e.EventCount)
}
}

case events.PatternDiscovered:
agentKey = "abstraction"
project = e.Project
content = fmt.Sprintf("Noticed a recurring pattern: '%s'.", e.Title)
if e.EvidenceCount > 0 {
content += fmt.Sprintf(" Backed by %d memories.", e.EvidenceCount)
}
if e.Project != "" {
content += fmt.Sprintf(" Scoped to project: %s.", e.Project)
}

case events.AbstractionCreated:
agentKey = "abstraction"
levelName := "principle"
if e.Level == 3 {
levelName = "axiom"
}
content = fmt.Sprintf("A new %s emerged: '%s'. Synthesized from %d sources.", levelName, e.Title, e.SourceCount)

case events.MetaCycleCompleted:
agentKey = "metacognition"
content = fmt.Sprintf("Quality audit complete. Logged %d observations this cycle.", e.ObservationsLogged)

default:
return "", "", ""
}

return content, agentKey, project
}
Loading