Skip to content

feat(plugin): devagentic-canvas — slash commands + state-aware preamble - #6

Merged
PowerCreek merged 1 commit into
mainfrom
issue-55-canvas-plugin
May 22, 2026
Merged

feat(plugin): devagentic-canvas — slash commands + state-aware preamble#6
PowerCreek merged 1 commit into
mainfrom
issue-55-canvas-plugin

Conversation

@PowerCreek

Copy link
Copy Markdown

Closes devagentic#55. Lane D opener — single-repo PR on hermes-agent (no companion devagentic change; the /v1/canvas/* REST surface already exists on devagentic from prior work).

Summary

New plugin under plugins/devagentic-canvas/ following hermes' existing standalone-plugin pattern (kind=standalone, register() entrypoint, plugin.yaml manifest — mirrors plugins/spotify/ + plugins/disk-cleanup/).

Surface

piece shape
/canvas list list user's canvases (active one flagged)
/canvas open <id> mark canvas as active; state injected into every turn
/canvas close clear the active marker
/canvas show summarize the active canvas
/canvas new <name> create a new canvas
pre_llm_call hook when a canvas is open, fetch state + return {"context": ...} to the host (appended to user message — keeps prompt cache valid)
devagentic-canvas:canvas skill reference doc on canvas authoring

Active-canvas marker

Tracked in $HERMES_HOME/canvas-active (default ~/.hermes/canvas-active) — a single-line file containing the canvas id. Written by /canvas open, cleared by /canvas close. Survives process restart so long-running sessions keep the canvas open across reconnects.

Configuration

Reuses the devagentic-local provider's env (Phase G #50 + C1 #52):

  • DEVAGENTIC_BASE_URL — default http://127.0.0.1:6071/v1
  • DEVAGENTIC_API_KEY — bearer (any value works under DEVAGENTIC_TRUST_HEADER=1)
  • DEVAGENTIC_USER_ID — manual override, else hermes_cli.profiles.get_active_profile_name()
  • HERMES_HOME — location of the canvas-active marker

Acceptance (from devagentic#55)

  • hermes plugin list shows devagentic-canvas — verified by manifest parse test (test_manifest_parses_and_declares_expected_fields).
  • Enabled: /canvas list works, preamble injects when canvas open — verified by test_canvas_list_command + test_preamble_injects_context_when_active.
  • Disabled: plugin is fully inert (no commands registered, no hooks fired) — guaranteed by hermes' standard plugin-disable flow (no per-plugin code needed; the host omits register() entirely when disabled).
  • Plugin README documents enable/disable + the surface.
  • 23 plugin tests pass; existing hermes pytest cases (devagentic-local profile, skills, memory — 37 total from prior PRs) still green.

Design calls

  1. pre_llm_call hook, not a system-prompt prepend. The hermes contract for pre_llm_call appends returned context to the user message specifically to preserve prompt-cache validity (system prompt stays identical across turns). The plugin honors that: the canvas state is ephemeral per-turn context, not a permanent system instruction.
  2. Marker file in HERMES_HOME, not in-process state. A single-line file at ~/.hermes/canvas-active survives process restart and is checkable by any tool (the /canvas show command, future debugging scripts, etc.) without needing the plugin loaded. Atomic write via .tmp + replace.
  3. Plugin never raises out of slash commands or hooks. Every public path catches its own exceptions and returns user-facing error strings (slash commands) or None (hooks). A broken devagentic doesn't brick the session — it just yields a "couldn't reach devagentic" message in the slash output and no preamble injection.
  4. Caps on injected preamble. Both nodes and edges are capped at 12 in the rendered output. A 200-node canvas wouldn't fit cleanly in a single turn anyway; operators tune the constants if their canvases trend large. Documented in _render_canvas_preamble.
  5. No call-site changes in hermes core. The plugin is purely additive — agent/conversation_loop.py already invokes pre_llm_call (existing hermes infrastructure); slash command registration is via the standard register_command API. Disabling the plugin removes the entire surface with zero residual hermes-side changes.

Layout

plugins/devagentic-canvas/
├── plugin.yaml          # manifest (kind: standalone, hooks: pre_llm_call)
├── __init__.py          # register() entrypoint
├── client.py            # HTTP client for /v1/canvas/*
├── commands.py          # /canvas slash command handlers + marker file logic
├── preamble.py          # pre_llm_call hook
├── README.md            # plugin docs
└── skills/
    └── canvas/
        └── SKILL.md     # canvas authoring conventions

Deferred

  • Auto-disable on devagentic unreachable. v0 returns user-facing errors on each /canvas invocation when devagentic is down. A v1 could circuit-break after N consecutive failures.
  • Canvas-state caching. Every turn re-fetches the active canvas. For a slow link, a 30s-TTL cache would amortize. v0 keeps the fetch fresh per-turn so refinements show up immediately.
  • More granular state injection. v0 shows nodes (id + type) and edges (src/dst/kind). A v1 could surface node bodies / canvas-position metadata when relevant; today's render is intentionally compact.
  • CLI subcommand for hermes plugin enable/disable. The README references hermes plugin enable|disable — that's the existing hermes plugin CLI surface; no per-plugin work needed here.

…le (#55)

New plugin under plugins/devagentic-canvas/ following hermes'
existing plugin pattern (kind=standalone, register() entrypoint,
plugin.yaml manifest). Three pieces:

  * /canvas slash commands (list / open <id> / close / show /
    new <name>) over devagentic's /v1/canvas/* REST surface.
  * pre_llm_call hook that injects the active canvas's state as
    ephemeral context (appended to the user message — preserves
    prompt cache).
  * Plugin-scoped skill at skills/canvas/SKILL.md documenting
    when to surface a canvas-shaped layout.

Active canvas tracked via ~/.hermes/canvas-active marker file
(written by /canvas open, cleared by /canvas close). Survives
process restart so long-lived sessions keep the canvas open
across reconnects.

Auth + base URL reuse the devagentic-local provider's env
(DEVAGENTIC_BASE_URL, DEVAGENTIC_API_KEY, DEVAGENTIC_USER_ID /
hermes_cli.profiles.get_active_profile_name()). The plugin
never raises out of a slash command or hook — devagentic being
down surfaces as user-facing error strings, not crashes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@PowerCreek
PowerCreek merged commit c6c2f08 into main May 22, 2026
@PowerCreek
PowerCreek deleted the issue-55-canvas-plugin branch May 22, 2026 07:07
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.

1 participant