feat: SAGE Memory Companion plugin + Playground tile#432
Open
l33tdawg wants to merge 1 commit into
Open
Conversation
Adds an additive plugin under `deeptutor/plugins/sage/` that gives
DeepTutor an external long-term memory companion via SAGE
(github.com/l33tdawg/sage). Targets cross-tutor knowledge sharing —
orthogonal to the per-learner `MemoryService`/`MemoryConsolidator`
flow, which this PR does not touch.
Hooks:
- Pre-run: injects SAGE recall into UnifiedContext.memory_context for
deep_solve and deep_research capabilities (appended, never replacing
existing per-learner memory).
- Post-run: subscribes to CAPABILITY_COMPLETE EventBus and stores
pedagogy-only observations under deeptutor-pedagogy /
deeptutor-{subject} / deeptutor-tutor-{learner_id}.
Other surface:
- Adds `deeptutor/plugins/loader.py` — referenced by AGENTS.md,
capability_registry.py, and plugins_api.py but not previously
implemented; both call sites already wrap the import in try/except,
so this is purely additive.
- Adds GET/POST /api/v1/plugins/sage/{status,toggle} for the UI.
- Adds a SAGE Memory Companion tile in the web Playground showing
Connected / Disabled / Disconnected / Not configured states with a
link to the SAGE dashboard and an enable/disable toggle.
Opt-in: SAGE_ENABLED=true. SAGE_URL and SAGE_AGENT_KEY_FILE optional.
Graceful degradation: when SAGE_ENABLED is unset OR the SDK is not
installed OR the node is unreachable, all hooks are no-ops and the
Playground tile renders a neutral status — host framework behavior is
unchanged.
Tests: 7 plugin tests + 4 router tests, all passing.
See INTEGRATION_PLAN.md for full motivation, architecture diff, and
links to prior SAGE host integrations (RAPTOR, pentagi, Aether,
Level Up).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SAGE Memory Companion — DeepTutor Integration Plan
Motivation
DeepTutor's per-learner memory layer (
SUMMARY.md+PROFILE.md,MemoryService,MemoryConsolidator) is excellent at capturing what one student knows and prefers. It is also intentionally local — there is no story for cross-tutor knowledge sharing today. As DeepTutor scales into classrooms, deployments, and individual tutors who each run their own instance, every tutor re-discovers the same pedagogical lessons in isolation:These are institutional insights — they should be shared across deployments without leaking individual user data. That's the gap SAGE fills: a governed, BFT-consensus memory layer with confidence scoring, decay, and per-agent identity.
This PR ships an additive plugin that gives DeepTutor an external long-term memory companion. It is orthogonal to
MemoryServiceand does not modify a single line of the existing per-learner memory code.Architecture diff
What's added (Python plugin in
deeptutor/plugins/, plus the new web surface)Edits to pre-existing files (kept tiny on purpose, ~7 lines total):
deeptutor/api/main.py— importplugins_sageand register it under the/api/v1/pluginsprefix (2 lines).web/app/(workspace)/playground/page.tsx— importSagePluginTileandrender it once, just below the page header (5 lines).
What's untouched (zero surgery)
deeptutor/services/memory/deeptutor/agents/solve/main_solver.pyUnifiedContext.memory_context; we append, never replace.deeptutor/runtime/orchestrator.pyCAPABILITY_COMPLETE— we just subscribe.deeptutor/runtime/registry/capability_registry.pydeeptutor.plugins.loader.discover_plugins; we just provide that module.deeptutor/api/routers/plugins_api.pydiscover_plugins(); the SAGE plugin shows up in/plugins/listautomatically.BaseCapabilityprotocolpyproject.tomldependenciessage-agent-sdkis not added to any[project.optional-dependencies]— it is loaded lazily and only required when the user opts in.The diff outside
plugins/is exactly 0 lines.Web Playground Surface
The Next.js Playground page (
web/app/(workspace)/playground/page.tsx)already fetches
/api/v1/plugins/listto enumerate tools + capabilities,but plugins themselves were never rendered visually. We added a single
status tile so SAGE has a discoverable presence in the UI:
web/components/plugins/SagePluginTile.tsx— fetches/api/v1/plugins/sage/status, derives one of four states (Connected,Disabled, Disconnected, Not configured) and renders a compact card with a
status badge, the SAGE node URL, the agent_id when known, an "Open SAGE
Dashboard" link (only when Connected, points at
${SAGE_URL}/ui/), and abest-effort Enable/Disable toggle. Errors degrade to a muted card — the
tile must never throw and break the rest of the Playground.
deeptutor/api/routers/plugins_sage.py— the backend probe.GET /sage/statusreturns{enabled, reachable, agent_id, node_url, sdk_installed, plugin_loaded, detail}.POST /sage/toggleflips thein-process
SAGE_ENABLEDenv var and askscapability.install_hooks/uninstall_hooksto react. The toggle is not persisted — restart withthe env var set in your shell for a permanent change. The README notes
this caveat.
t(...)but no zh-CN entries areshipped in this PR.
react-i18nextfalls back to the English key text(the project sets
keySeparator: falseand uses English keys as ids),matching the rest of Playground's untranslated strings.
Runtime flow
Opt-in story
A single env var controls everything:
SAGE_ENABLED=true, the plugin loads but installs no hooks. Itscapability still appears in
deeptutor plugin list, but reportsenabled=falseand does nothing.SAGE_ENABLED=truebut the SAGE node is unreachable /sage-agent-sdkisn't installed, every recall/remember returns an empty result and logs
at
INFOonce. DeepTutor turns continue normally.the underlying capability still runs unmodified.
exception so a SAGE outage cannot break the chat turn.
Graceful degradation matrix
SAGE_ENABLEDunset / not truthySAGE_ENABLED=true, SDK not installedSAGE_ENABLED=true, SAGE node down[], remember warns once and returns{}. Capability turn unaffected.run()proceeds with empty SAGE block.What gets stored in SAGE
Per-turn observation (one per domain — pedagogy, subject, learner-tutor):
Prior art (same shape, already shipped)
core/sage/core/sage_client.py+ multi-pass hooksintegrations/sage/integrations/levelup/sage_bridge.pyintegrations/levelup/All four ship as additive plugins with the same env-driven opt-in story
and graceful no-op behaviour. This PR is the fifth.
Tests
The suite mocks
sage_sdkviasys.modules, so it runs without the SDKpackage installed and without a live SAGE node. Coverage:
discover_plugins().load_plugin_capability().SAGE_ENABLEDunset, the plugin is a strict no-op.SAGE_ENABLED=true, recall is invoked pre-deep_solveandmemory_contextis enriched.SAGE_ENABLED=true,CAPABILITY_COMPLETEtriggers apropose()call across all expected domain tags, and the agent output never leaks
into stored content.
run()reports status correctly in both states.What's intentionally NOT in this PR
To keep the surface tight, this PR does not:
sage-agent-sdkto anyrequirements.txt/pyproject.tomlextra.Users opting in install it themselves:
pip install sage-agent-sdk.MemoryServiceorMemoryConsolidator.sage_memorystatus probe.web/.Each of those is a separate, opt-in follow-up if maintainers want it.