2026 03 08_sector_g_mod_agent_plan - mark-ik/graphshell GitHub Wiki
Doc role: Implementation plan for the mod, agent, and theme registry sector
Status: โ
Complete / Implemented at runtime level โ ModRegistry, AgentRegistry, ThemeRegistry all struct-complete. Follow-ons: WASM mod host, mod-provided agent extensions, OS-theme startup detection.
Date: 2026-03-08
Parent: 2026-03-08_registry_development_plan.md
Registries covered: ModRegistry, AgentRegistry, ThemeRegistry
Specs: mod_registry_spec.md, agent_registry_spec.md, theme_registry_spec.md
ModRegistry remains the most complete registry in the system โ native mod discovery, dependency
ordering, lifecycle integration, runtime surface extension, and unload rollback are all functional.
Sector G runtime work has now landed the two previously missing registries (AgentRegistry,
ThemeRegistry) and normalized registry authority so GUI/runtime code consume one shared
RegistryRuntime instance.
ModRegistry โ native/runtime extension + unload complete; WASM host still open
AgentRegistry โ implemented; supervised background capability declaration lives in runtime
ThemeRegistry โ implemented; runtime-owned token sets + activation; residual migration remains
| Registry | Struct | Completeness | Key gaps |
|---|---|---|---|
ModRegistry |
โ (atomic) | Mostly complete | No WASM mod host/intent bridge yet; no reducer-carried ModDeactivated event |
AgentRegistry |
โ | Implemented | Built-in runtime agents only; no mod-provided agent extension path yet |
ThemeRegistry |
โ | Implemented | Startup OS-theme detection and mod-provided theme activation remain partial |
The mod_registry_spec.md's manifest-gate policy: mods must declare all capabilities in their
manifest before activation. WASM mods are isolated; they cannot access host memory directly.
pub trait WasmModHost {
fn load_wasm(&mut self, path: &Path, manifest: &ModManifest)
-> Result<WasmModInstance, ModLoadError>;
fn call_export(&self, instance: &WasmModInstance, name: &str, args: &[WasmVal])
-> Result<WasmVal, WasmCallError>;
}WASM mods communicate with the host via an IntentBridge: they receive serialised events and
return serialised intents. They never hold references to host state.
Done gates:
-
WasmModHosttrait defined. -
wasmtime(orwasmer) crate added toCargo.tomlbehind awasm-modsfeature flag. -
ModRegistry::load_wasm_mod()loads a.wasmfile, validates manifest, activates. -
ControlPanelsupervises the WASM host as a worker (WASM calls are async). - Unit test: WASM mod emits a
GraphIntent::Noopvia intent bridge.
The mod_registry_spec.md's containment policy: mods extend registry surfaces through
declared extension points, not by replacing registry internals.
Mods can register:
- Protocol scheme handlers (
ProtocolRegistry::register_scheme_handler()โ Sector A A1.3). - Viewer implementations (
ViewerRegistry::register_viewer()). - Search providers (
IndexRegistry::register_provider()โ Sector F F3.1). - Action handlers (
ActionRegistry::register_action()โ Sector B B3.x). - Lens descriptors (
LensRegistry::register_lens()โ Sector A A4.1). - Canvas profiles (
CanvasRegistry::register_profile()โ Sector D D3.1). - Workflow descriptors (
WorkflowRegistry::register_workflow()โ Sector E E2.1).
The ModRegistry tracks which extensions each mod has registered; unload_mod() removes them.
Done gates:
-
ModExtensionRecordtracks all registry extensions made by a mod. -
unload_mod()calls the appropriateunregister_*on each extension type. - Test: load mod โ extensions present; unload mod โ extensions removed; fallback restored.
Currently ModRegistry supports load-all at startup. Unloading a single mod at runtime is
needed for mod developer iteration.
pub fn unload_mod(&mut self, id: &ModId) -> Result<(), ModUnloadError>Unload:
- Calls
unload()on the mod instance (if defined in the manifest). - Removes all
ModExtensionRecordentries. - Emits
GraphIntent::ModDeactivated { mod_id }. - Emits
SignalKind::Lifecycle(ModUnloaded)viaSignalRoutingLayer.
Done gates:
-
unload_mod()implemented with full extension cleanup. -
GraphIntent::ModDeactivateddefined and handled in reducer. - Test: unload a mod that registered a scheme handler โ scheme handler removed.
Unlocks: PresentationDomainRegistry (Sector D); WCAG-compliant colour resolution;
dark mode; mod-provided themes.
The theme_registry_spec.md adopts OSGi R8, OpenTelemetry Semantic Conventions, and
WCAG 2.2 Level AA. Theme tokens govern all visual output.
pub struct ThemeTokenSet {
// Colours (all WCAG AA contrast-compliant)
pub surface_background: Color,
pub surface_foreground: Color,
pub node_default_fill: Color,
pub node_selected_fill: Color,
pub node_hot_fill: Color,
pub node_cold_fill: Color,
pub edge_default_stroke: Color,
pub edge_selected_stroke: Color,
pub label_primary: Color,
pub label_secondary: Color,
pub focus_ring: Color,
pub selection_highlight: Color,
// Typography
pub label_font_family: String,
pub label_font_size_default: f32,
pub label_font_size_small: f32,
// Motion
pub transition_duration_ms: u32,
pub animation_easing: EasingCurve,
}
pub const THEME_ID_DARK: ThemeId = ThemeId("theme:dark");
pub const THEME_ID_LIGHT: ThemeId = ThemeId("theme:light");
pub const THEME_ID_HIGH_CONTRAST: ThemeId = ThemeId("theme:high_contrast");
pub struct ThemeRegistry {
themes: HashMap<ThemeId, ThemeTokenSet>,
active: ThemeId,
}Done gates:
-
ThemeRegistrystruct inshell/desktop/runtime/registries/theme.rs. -
DARK,LIGHT,HIGH_CONTRASTbuilt-in themes registered. -
HIGH_CONTRASTtheme verified WCAG AA (7:1 contrast ratio minimum). - Added to
RegistryRuntime. -
DIAG_THEMEchannel (Info severity).
All egui::Color32::* hardcoded values in render/panels.rs and render/mod.rs are
replaced with calls to the active theme token set:
let tokens = registries.theme.active_tokens();
let node_fill = tokens.node_default_fill;Done gates:
- No hardcoded
Color32::from_rgb(...)literals remain inrender/. -
PresentationDomainRegistry::resolve_presentation_profile()reads fromThemeRegistry. - Visual regression check: dark theme produces identical colours to before.
Detect OS light/dark preference at startup and set the active theme accordingly.
pub fn detect_system_theme() -> ThemeId {
// use winit / platform API to detect dark/light
}User preference (manual override) takes precedence over system detection. Override persists
to user preferences via GraphIntent::SetTheme.
Done gates:
-
detect_system_theme()implemented for Windows (primary target; others as best-effort). -
GraphIntent::SetTheme { theme_id }defined and handled in reducer. - Theme persists across restart.
Via ModRegistry extension mechanism (Phase G1.2), mods can register additional theme token
sets. Custom themes are validated for WCAG AA compliance on registration.
Done gates:
-
ThemeRegistry::register_theme()validates WCAG AA contrast ratios. - Non-compliant theme registration emits
DIAG_THEMEatWarnseverity. - Test: register custom theme; activate it; render uses custom tokens.
Unlocks: Declarative background agents; intelligence features (PLANNING_REGISTER ยง1D Sector E: verse-intelligence, intelligence-memory); future AI-augmented graph features.
The agent_registry_spec.md's supervised-agent policy: agents are explicit background
capabilities, not hidden threads. Agent work enters app state only through supervised intents.
The key distinction from ControlPanel workers: workers are platform-level infrastructure
(memory monitor, prefetch, sync). Agents are application-level capabilities declared by mods
or the built-in intelligence layer. An agent is closer to a named background task with a
declared capability surface.
pub trait Agent: Send {
fn id(&self) -> AgentId;
fn display_name(&self) -> &str;
fn declared_capabilities(&self) -> Vec<AgentCapability>;
/// Called to start the agent. Returns an async task handle.
fn spawn(self: Box<Self>, context: AgentContext) -> AgentHandle;
}
pub struct AgentContext {
pub intent_tx: mpsc::Sender<QueuedIntent>,
pub signal_rx: broadcast::Receiver<SignalEnvelope>,
pub cancel: CancellationToken,
}
pub struct AgentDescriptor {
pub id: AgentId,
pub display_name: String,
pub capabilities: Vec<AgentCapability>,
pub schedule: AgentSchedule, // OnDemand | Periodic(Duration) | Triggered(SignalKind)
}
pub struct AgentRegistry {
descriptors: HashMap<AgentId, AgentDescriptor>,
handles: HashMap<AgentId, AgentHandle>,
}Built-in agents (stubs for now):
-
agent:graph_summariserโ periodically generates summaries for recently visited nodes. -
agent:tag_suggesterโ suggests UDC tags for untagged nodes using heuristics.
Done gates:
-
AgentRegistrystruct inshell/desktop/runtime/registries/agent.rs. -
Agenttrait andAgentContextdefined. -
GRAPH_SUMMARISERandTAG_SUGGESTERstub agents registered. - Added to
RegistryRuntime. -
DIAG_AGENTchannel (Info severity).
The boundary-ingress policy: agents communicate with the app state through the intent queue,
not direct state access. ControlPanel supervises agent tasks as first-class workers.
impl ControlPanel {
pub fn spawn_agent(&mut self, agent: Box<dyn Agent>, registries: &RegistryRuntime) {
let context = AgentContext {
intent_tx: self.intent_tx.clone(),
signal_rx: registries.signal_routing.subscribe_all(),
cancel: self.cancel.clone(),
};
let handle = agent.spawn(context);
self.workers.spawn(handle.task);
}
}Done gates:
-
ControlPanel::spawn_agent()implemented. - Agent tasks are included in
shutdown()JoinSet drain. - Test: stub agent spawns, receives cancel, shuts down cleanly.
The TAG_SUGGESTER agent provides a concrete implementation of the agent model:
- Subscribe to
SignalKind::Navigation(NodeActivated)signals. - For each newly activated node, analyse the URL and title.
- Query
KnowledgeRegistry::validate_tag()for candidate tags. - Emit
GraphIntent::SuggestNodeTags { node_key, suggestions }(new intent, non-destructive).
SuggestNodeTags is a display-only intent: it adds suggestions to the node's UI without
committing them to the graph model. The user must explicitly confirm to create the tags.
Done gates:
-
GraphIntent::SuggestNodeTagsdefined and handled (display-side only). -
TagSuggesterAgentimplementation inapp/agents/tag_suggester.rs. - Agent subscribes to navigation signal and emits suggestion intents.
- Test: agent suggests UDC tags for a URL with a known-category hostname.
- WASM mod loader functional with intent bridge; isolated from host state.
- Mods can register and unregister scheme handlers, viewers, search providers, and lenses.
-
ThemeRegistryowns all visual tokens; no hardcoded colours inrender/. - Dark/light/high-contrast themes built in; system theme detection works.
- Custom mod themes validated for WCAG AA on registration.
-
AgentRegistryis a real registry; agents supervised byControlPanel. -
TAG_SUGGESTERagent provides end-to-end agent model example. - All three registries are in
RegistryRuntimewith diagnostics coverage.
Residual follow-ons that remain explicit rather than hidden:
-
WasmModHost/ intent-bridge support is still absent;ModType::Wasmis a manifest/model marker only today. - Runtime
unload_mod()now removes extension records and restores fallback mappings, but the reducer-carriedGraphIntent::ModDeactivatedpath from the original plan has still not landed. - Theme activation is runtime-owned, but startup OS-theme detection and mod-provided theme activation are still follow-on work.
- Theme token migration is substantial but not absolute;
render/still contains some hardcoded color literals outside the command/radial paths already moved to runtime theme tokens.
- mod_registry_spec.md
- agent_registry_spec.md
- theme_registry_spec.md
- 2026-03-08_sector_d_canvas_surface_plan.md โ ThemeRegistry consumer
- 2026-03-08_sector_h_signal_infrastructure_plan.md โ Agent signal subscription
- SYSTEM_REGISTER.md โ ControlPanel worker supervision
- 2026-03-08_registry_development_plan.md โ master index