Skip to content

devagentic-docs: fix searchDocs schema + add /fork + pre_llm_call hook#20

Merged
PowerCreek merged 1 commit into
mainfrom
devagentic-docs-fork-followup
May 22, 2026
Merged

devagentic-docs: fix searchDocs schema + add /fork + pre_llm_call hook#20
PowerCreek merged 1 commit into
mainfrom
devagentic-docs-fork-followup

Conversation

@PowerCreek

Copy link
Copy Markdown

Summary

Two changes — one a defect fix in my just-merged MVP, one the rest of #12's proposed scope.

Schema fix (defect in PR #19)

Introspected devagentic's live GraphQL schema while planning the /fork surface and found my MVP shipped invalid queries:

  • Actual schema is searchDocs(query: String, k: Int) -> [Doc]. My MVP used (query, limit, tag) + selected a non-existent score field. Any call would have been rejected by the GraphQL layer.
  • --tag belongs on Query.docs(tags: [String]), a separate primitive. Now routed there: with --tag, hit docs(tags:[t]) and filter by query substring client-side; without --tag, hit searchDocs(query, k=limit).
  • Doc selection now matches the real Doc { id, content, tags, source, ts }.

/fork surface (#12 follow-up)

Slash commands wrap forkContext, decorateContext, context(id), renderContext:

  • /fork open <parent_id> [--goal "..."] — forks, decorates with goal + pinned-doc, writes marker at $HERMES_HOME/docs-fork-active.
  • /fork close — unlinks the marker.
  • /fork show — prints active fork tags + annotations.
  • /fork pin <doc_id> — appends a pinned-doc annotation to the active fork.
  • /fork render — calls renderContext(ctxId) and prints inline (debugging aid for the preamble injection).

pre_llm_call hook

When a fork is active, preamble.on_pre_llm_call calls renderContext(ctxId) and returns {context: <header> + <rendered>} for the host to append to the user message. Cached prompt prefixes stay valid (the context goes on the user side, not the system prompt).

Returns None on any failure path (no marker, render returns None, render is whitespace, network down). The turn proceeds without preamble — never blocks.

Capped hermes-side at 8000 chars; devagentic's renderContext does its own bounding upstream.

Test plan

  • 45 unit tests pass (pytest tests/test_devagentic_docs_plugin.py):
    • schema-correct search routing (free-text vs --tag)
    • all five /fork subcommands including marker file roundtrips
    • preamble hook: inactive marker → None; active + render → {context}; cap on long renders; failure → None
    • existing /doc tests adapted to schema-correct shapes
  • No regression across the four sibling test files: tests/test_devagentic_canvas_plugin.py + test_devagentic_skills.py + test_devagentic_memory.py + tests/hermes_cli/test_doctor_devagentic_graph.py → 121 passed total.
  • Live end-to-end against running devagentic blocked by devagentic#157.

Closes #12 in full.

Filed by hermes-maintainer (PowerCreek).

Two changes in this PR — one a defect fix, one the rest of #12's
proposed scope.

Schema fix (defect in #19 MVP):
- The actual schema is searchDocs(query, k), not (query, limit, tag).
  My PR #19 shipped invalid GraphQL that the server would reject.
  Confirmed via introspection of devagentic's live schema.
- Route /doc search differently: with --tag, hit Query.docs(tags:[t])
  and filter client-side; without --tag, hit Query.searchDocs(query, k).
- Doc shape: drop `score` (not on Doc); add `source`, `ts`.

/fork surface (#12 follow-up):
- /fork open <parent_id> [--goal "..."]  wraps forkContext, sets a
  marker at $HERMES_HOME/docs-fork-active (sibling to canvas-active).
- /fork close clears the marker.
- /fork show prints active fork tags + annotations via context(id).
- /fork pin <doc_id> appends a pinned-doc annotation via decorateContext.
- /fork render exposes renderContext(ctxId) directly — useful for
  debugging the preamble injection.

pre_llm_call hook:
- When a fork is active, calls renderContext(ctxId) and returns
  {context: header + rendered} for the host to append to the user
  message. Capped at 8000 chars hermes-side; devagentic does its
  own bounding upstream.
- Returns None (turn proceeds without preamble) on any failure
  path: no marker, render returns None, render returns "".

Tests: 45 passed (43 prior + 2 net new after schema-fix rewrites).
The fixture now eagerly pre-loads all three submodules so
monkeypatches on plugin_pkg.client reach preamble's docs_client
reference.

Closes #12 in full.
@PowerCreek PowerCreek merged commit 9cc9b94 into main May 22, 2026
@PowerCreek PowerCreek deleted the devagentic-docs-fork-followup branch May 22, 2026 22:10
PowerCreek added a commit that referenced this pull request May 22, 2026
Mirrors the canvas-tools pattern (#56) for the just-shipped
devagentic-docs plugin (#12 / PRs #19+#20+#23). MCP-aware clients
(Claude Code, Cursor, Codex) can now writeDoc / searchDocs /
forkContext / decorateContext / renderContext against any
devagentic instance hermes is configured for.

New tools (7):
- doc_search(query, limit, tag)  → search_docs
- doc_write(content, tags, source) → write_doc
- doc_show(doc_id)               → get_doc
- fork_open(parent_id, goal, tags) → fork_context (auto-pins
                                     parent + threads goal)
- fork_decorate(ctx_id, key, value, weight) → decorate_context
- fork_get(ctx_id)               → get_context
- fork_render(ctx_id)            → render_context (envelope:
                                   {ctx_id, rendered})

Loader pattern: _resolve_docs_client() mirrors
_resolve_canvas_client at line 880 — file-path import of
plugins/devagentic-docs/client.py since the hyphenated dir
isn't a Python package.

Failure semantics: every tool returns {"error": "<msg>"} JSON.
The plugin's last_error_text() pattern (introduced in #15)
threads through via a _reason() helper, so federated agents
see the same actionable hints CLI users get — e.g. on the
canonical devbox deployment (where /graphql isn't exposed,
see #21), they'd see "not found at <url>/graphql ..." instead
of an opaque None.

Out of scope per #24: fork_close + fork_pin depend on the
session-local $HERMES_HOME/docs-fork-active marker, which
doesn't translate to MCP's stateless tool model. fork_open
auto-tags with source:hermes-mcp so MCP-authored forks are
distinguishable from CLI-authored ones (source:hermes-cli).

Tests: 22 new (tests/test_mcp_docs.py) covering registration,
plugin-missing fallback, arg routing, last_error surfacing for
the #21 case, and the real-import smoke. All passing alongside
canvas MCP suite (14 existing) and the broader devagentic-
adjacent test set (159 total).

Closes #24.
PowerCreek added a commit that referenced this pull request May 23, 2026
…#39)

The same urllib HTTPError + URLError + OSError + TimeoutError
dispatch repeats in three sites after #15/#18/#20:

  plugins/devagentic-canvas/client.py:_request
  plugins/devagentic-docs/client.py:_post_graphql
  hermes_cli/doctor.py:_check_devagentic_graph

Each site has the same if-401/403-elif-404-else branches over the
exception and the same urllib network-error fallback. Only the
message text differs per site.

Add `utils.classify_http_error(exc) -> str` returning one of:
  HTTP_ERROR_AUTH        — 401 / 403
  HTTP_ERROR_NOT_FOUND   — 404
  HTTP_ERROR_HTTP        — other HTTPError status
  HTTP_ERROR_UNREACHABLE — URLError / OSError / TimeoutError
  HTTP_ERROR_UNKNOWN     — anything else

Refactor the three callsites onto a single `except (URLError, OSError,
TimeoutError)` (HTTPError is a URLError subclass) followed by a
dispatch on the classifier output. Each site still owns its own
message text — the user-facing strings are unchanged.

Behavior change: none. Net diff is roughly even (helper + dispatch
replaces three near-identical if/elif/else blocks).

Tests:
  - 11 new tests in tests/test_utils_classify_http_error.py covering
    401, 403, 404, generic statuses (400/422/429/500/502/503/504),
    URLError, OSError, TimeoutError, socket.timeout, unrelated
    exceptions, and the HTTPError-is-URLError subclass quirk.
  - All three refactored callsites' existing test suites still pass:
    canvas (47), docs (47), doctor devagentic-graph (6) — 112 total.

Closes #38.
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.

[synergy] Add devagentic-docs plugin: /doc search + /doc write + ephemeral finding-injection (sibling to devagentic-canvas)

1 participant