Skip to content

feat(reflect): annotate the graph with work-memory verdicts and surface them in explain/query (#1441)#1542

Closed
TPAteeq wants to merge 4 commits into
Graphify-Labs:v8from
TPAteeq:feat-reflect-annotate-graph
Closed

feat(reflect): annotate the graph with work-memory verdicts and surface them in explain/query (#1441)#1542
TPAteeq wants to merge 4 commits into
Graphify-Labs:v8from
TPAteeq:feat-reflect-annotate-graph

Conversation

@TPAteeq

@TPAteeq TPAteeq commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #1441. The work-memory loop re-ingests the raw Q&A memory docs into the graph,
but the distilled verdicts graphify reflect computes (preferred / tentative / contested /
dead_end, time-decay scored) only land in reflections/LESSONS.md — a flat file, overwritten
each run, that nothing reads back. This makes those verdicts first-class graph data and
surfaces them where you actually look.

Write side — reflect --annotate-graph

Stamps each cited source node's verdict onto the graph (matched by node id or cited label):

attribute value
learning_status preferred / tentative / contested / dead_end
learning_score signed, time-decayed score (omitted for dead-end-only nodes)
learning_uses distinct useful results (omitted when 0)

Persistence is by recompute, not storage. annotate_graph clears every learning_* then
re-stamps from the aggregate: deterministic and idempotent (a re-run is byte-identical, a no-op
rewrites graph.json byte-for-byte matching export.to_json), touches only learning_*, never
changes node count or real fields. The durable truth stays graphify-out/memory/*.md → a full
rebuild that drops the attributes is healed by the next reflect. The git
post-commit/post-checkout hooks pass annotate=True after each rebuild so the graph self-heals;
learning_* is excluded from both watch compare functions so an annotation never forces a
needless rebuild.

Read side — the verdicts are actually used (four surfaces)

  • graphify explain <node>Lesson: preferred source (start here) — 2× useful, score=2.0
  • graphify query + MCP query_graphNODE Transformer [src=… learning=preferred]
  • GRAPH_REPORT.md → a "Work-memory lessons" section listing preferred sources / known dead ends
  • graph.html → annotated nodes get a status-colored ring (green = preferred, red = dead end;
    the community color stays the fill) and a Lesson: field in the node info panel

The status is one of four fixed strings but is sanitize_label'd on every output path (MCP F-010
and HTML), and on the query path appended inside the existing NODE line so the token-budget
truncation accounting is unchanged. Un-annotated graphs are unaffected: no Lesson: line, no
learning= suffix, no report section, no node ring (graph.html gains only a few constant
template lines; the rendered graph is visually identical).

Tests

  • annotate_graph: by-id/by-label matching, all four verdicts, idempotency, self-healing,
    byte-identical no-op, non-dict graph → 0, label-less node not matched by a "None" citation,
    end-to-end via reflect(annotate=True) on a real build_from_json graph.
  • Both watch compares ignore learning_* (the --no-cluster one without mutating originals).
  • Consumers: explain shows/omits the Lesson: line; _subgraph_to_text (query CLI + MCP)
    tags/omits learning=; the GRAPH_REPORT section is present/absent; the HTML ring is present/absent.
  • Full suite: 2550 passed, 2 skipped. skillgen --check, ruff clean.

Follow-ups (separate)

  • A skill-runbook note that graph nodes carry learning_status (batched to amortize the skillgen
    14-copy regen).

Out of scope by design: no new edges / "Lessons" node — node attributes only (no topology change);
corrections stay question-level in LESSONS.md.

🤖 Generated with Claude Code

TPAteeq and others added 4 commits June 29, 2026 16:46
…ce them

The work-memory loop (Graphify-Labs#1441) ingests the raw Q&A memory docs into the graph, but
the distilled verdicts graphify reflect computes (preferred / tentative / contested
/ dead_end, time-decay scored) only ever land in reflections/LESSONS.md -- a flat
file, overwritten each run, that nothing reads back.

Write side: graphify reflect --annotate-graph stamps each cited source node's
verdict onto the graph as reserved learning_status / learning_score / learning_uses
attributes (matched by node id or cited label). Persistence is by recompute, not
storage: annotate_graph clears all learning_* then re-stamps from the aggregate, so
it is deterministic and idempotent (a re-run is byte-identical; a no-op annotation
rewrites graph.json byte-for-byte, matching to_json's format), touches only
learning_* keys, and never changes node count or any real field. The durable source
of truth stays graphify-out/memory/*.md, so a full rebuild that drops the attributes
is healed by the next reflect. The git post-commit/post-checkout hooks pass
annotate=True after each rebuild so the graph self-heals; learning_* is excluded
from both watch graph-compare functions so an annotation never forces a needless
rebuild.

Read side (so the verdicts are used, not write-only): graphify explain prints a
Lesson: line for an annotated node, and the shared query renderer (graphify query
CLI + the MCP query_graph tool) tags each node line with learning=<status>. The
status is one of four fixed strings but is sanitized like every other field on the
MCP output path (F-010), and appended inside the existing NODE line so the
token-budget truncation accounting is unchanged.

Missing id/label is never coerced to the string "None"; a non-dict graph.json
returns 0 per the best-effort contract.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface the learning_status verdicts (graphify reflect --annotate-graph) in the
human-facing report, grouped by status (preferred / tentative / contested / dead
end) with the per-node useful counts, so the lessons show up in the broad overview
and not only when you explain or query a specific node. The section is omitted
entirely on an un-annotated graph, so existing reports are unchanged. Deterministic
ordering (score desc, then label), each group capped at 8 with a "+N more" suffix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Color the interactive graph.html by learning_status (graphify reflect
--annotate-graph): an annotated node gets a status-colored border ring -- green
preferred, light-green tentative, amber contested, red dead end -- while the
community color stays the node fill, so the two signals read independently. The
verdict is also shown as a "Lesson" field in the node info panel. Only the four
known statuses produce a ring, and the value is sanitized before reaching the HTML;
un-annotated graphs render byte-identically (no ring, no Lesson field).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve CHANGELOG.md: keep the reflect --annotate-graph entry under ## Unreleased,
above the new ## 0.9.2 release section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@safishamsi

Copy link
Copy Markdown
Collaborator

Thanks for this, and for pushing the work-memory loop forward — surfacing the distilled verdicts where you actually look (explain/query/report/html) is exactly the right instinct, and it shipped on v8 (5779767).

We landed it with one architectural change from this PR: instead of stamping learning_* onto the nodes in graph.json, the verdicts are written to a separate .graphify_learning.json sidecar that the read surfaces merge in at read time. Same four surfaces you targeted, same reuse of the reflect aggregate. The sidecar keeps graph.json purely structural (so it doesn't churn on every reflect and the attributes don't flow into GraphML exports), and it let us add two things cheaply: each verdict now carries its source-question provenance, and a file fingerprint so a verdict on code that's changed since gets flagged 'code changed — re-verify' rather than shown as still-authoritative. Dead-ends stayed question-scoped (report-only) rather than a node attribute.

So I'm closing this PR as implemented-differently rather than merged — the idea and the read-surface design are yours and credited in the commit. Really appreciate it; please keep them coming.

safishamsi added a commit that referenced this pull request Jun 30, 2026
…aph sidecar (#1441)

Projects the verdicts `graphify reflect` already distills (preferred /
tentative / contested, exponential time-decayed) into a derived
experiential layer the read surfaces consume, so accumulated agent
experience actually shows up where you look — without polluting the
structural graph.

Design (grounded in agent-memory + provenance literature; a redesign of
the #1542 approach):
- SIDECAR, not graph.json stamping. `reflect` writes `.graphify_learning.json`
  next to graph.json (an additional output, so the git hooks produce it
  automatically). graph.json stays purely structural; nothing leaks into
  GraphML; no graph.json churn. Mirrors the named-graph / event-sourcing
  separation of durable truth from a derived layer.
- Reuses the existing reflect aggregate (its `_decay` is the
  recency-weighted exponential model; `_finalize_sources` the
  classification) — no new scoring.
- PROVENANCE: each verdict carries the source questions/dates that produced
  it (cap 5, most-recent first).
- STALENESS: each verdict stores the node's file fingerprint; on read, a
  changed source file flags the verdict stale ("code changed since —
  re-verify") rather than presenting a confident lesson on rewritten code.
- CONTESTED surfaced distinctly (useful N / dead-end M), not averaged away.
- DEAD-ENDS stay QUERY-SCOPED — never a node-level status; they appear only
  in the report as question -> nodes.
- Read surfaces (explain / query+MCP / GRAPH_REPORT / graph.html) merge the
  overlay at read time, sanitized; un-annotated graphs are byte-identical.

Deferred (logged): letting verdicts influence query/seed traversal — the
recommender feedback-loop / Matthew-effect risk means that needs
propensity correction + exploration, not naive biasing.

Builds on the idea in #1441/#1542 (thanks @TPAteeq).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@safishamsi safishamsi closed this Jun 30, 2026
safishamsi added a commit that referenced this pull request Jun 30, 2026
README: document that `reflect --graph` writes the .graphify_learning.json
overlay and that explain/query surface a Lesson hint (with the
code-changed staleness flag).

CHANGELOG: add an Unreleased section for the post-0.9.2 work — the
work-memory overlay (#1441/#1542), this.field.method() injected-field
resolution (#1316), TS wildcard path aliases (#1544), JS namespace
re-exports (#1552), and the ObjC dot-syntax/@selector edges (#1475/#1543).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants