Skip to content

Extend decision/glossary pinning to all prompt-building adapters - #582

Open
QuanCheng-QC wants to merge 3 commits into
developfrom
bugfix/decision-pinning-coverage
Open

Extend decision/glossary pinning to all prompt-building adapters#582
QuanCheng-QC wants to merge 3 commits into
developfrom
bugfix/decision-pinning-coverage

Conversation

@QuanCheng-QC

Copy link
Copy Markdown
Collaborator

Summary

Decision pinning (#580) gave the Claude adapter a per-channel decision log, but every other adapter — and several Claude configurations — still lacked the shared, user-confirmed context. Two agents in the same channel (e.g. a QA agent and an RD agent on different adapter types) could therefore hold different readings of the same field, with test cases and implementation drifting apart.

This PR makes the pinned knowledge a cross-adapter mechanism and adds a second pinned entry — a glossary — as the single source of truth for field/term definitions.

Changes

  • Hoist the three-state knowledge-entry fetch from ClaudeAdapter into BaseAdapter, generalized to any pinned title with a per-channel id cache (method names, cache fields, and the three-state contract are unchanged).
  • Glossary pinning: channel-specific Glossary for channel <name>, falling back to a workspace-wide Workspace glossary. Injected data-fenced like the decision log, folded into the persistent-process fingerprint, so a glossary edit respawns Claude CLI sessions with --resume to re-pin it. The workspace-wide fallback is read-only for channel agents — a channel-local clarification must not silently rewrite definitions other channels rely on.
  • Adapter coverage: codex, llm-direct (kimi), amp, copilot, hermes, and opencode prefetch the pinned entries per message and pass them to their system-prompt builders (skills/curl phrasing). Gemini and Goose pin read-only (no knowledge tool access; the write protocol is replaced with a restate-in-reply instruction).
  • MCP allowlist fix: the decision protocol instructs workspace_list/read/write_knowledge by name but none were allowed — in tool_mode: mcp the log silently stopped updating. Plan mode allows the read/list tools only. The disable is also propagated to the MCP server (--disable-knowledge) so the tools are not even registered — allowlist omission is not a boundary under --dangerously-skip-permissions.
  • Silent failures made visible: a disabled knowledge module now posts a once-per-channel status saying pinning is inactive, instead of only logging.

Review-driven hardening (rounds 2–3)

  • Workspace-fallback hits are never cached under the channel key — a channel glossary created later wins on the next turn (fallback users pay one extra list per message).
  • Cached pinned entries revalidate their title on read: a renamed (archived) entry invalidates the cache and re-resolves to the fallback or a replacement canonical entry.
  • A transient knowledge read no longer wipes the last successful pin for prompt-per-turn adapters (last-known-good is kept per entry).
  • Glossary results carry their scope (channel / workspace) so prompts can phrase the fallback as read-only.

Known limitations (accepted)

  • Aider, Cline, and Mini remain unpinned by design: aider is message-file driven, cline deliberately keeps its own system prompt (-s would replace it), mini deliberately has no workspace context. openclaw, cursor, and nanoclaw have no per-message system-prompt channel either.
  • Concurrent writes to the same glossary/decision entry are last-write-wins — the knowledge API has no conditional update; this is a pre-existing property shared with the decision log.
  • Adapters that resume CLI-side sessions (amp, copilot, opencode) re-pin only when their CLI session restarts; only the Claude adapter has the fingerprint-respawn machinery.

Testing

  • Full suite: 806 tests pass (39 new/updated across pinned-context.test.js and claude-decision-pinning.test.js), covering: glossary channel→workspace precedence and re-resolution, rename invalidation, fallback non-caching, last-known-good on transient failure, read-only prompts (Gemini/Goose/workspace scope), MCP allowlist + --disable-knowledge propagation, and the combined-fingerprint respawn paths.
  • npm run lint is non-functional repo-wide (no ESLint config/dependency exists); node --check passes on all touched files.

Decision pinning (added for the Claude adapter in c3e63c5) left the other
adapters and several configurations without the shared, user-confirmed
context, so two agents in the same channel could hold different readings of
the same field. Close those gaps:

- Hoist the three-state knowledge-entry fetch from ClaudeAdapter into
  BaseAdapter, generalized to any pinned title with a per-channel id cache.
- Add a second pinned entry: a glossary (channel-specific "Glossary for
  channel <name>", falling back to a workspace-wide "Workspace glossary")
  that defines shared fields/terms. It is injected data-fenced like the
  decision log, and folded into the persistent-process fingerprint so a
  glossary edit respawns Claude CLI sessions with resume to re-pin it.
- Wire pinning into the other prompt-building adapters: codex, llm-direct
  (kimi), amp, copilot, hermes, and opencode prefetch the pinned entries per
  message and pass them to their system-prompt builders with skills-mode
  (curl) phrasing. Gemini pins read-only: it has no knowledge tool access,
  so the write protocol is replaced with a restate-in-reply instruction.
- Fix the MCP-mode allowlist: the decision protocol instructs
  workspace_list/read/write_knowledge by name but none were allowed, so in
  tool_mode mcp the log silently stopped updating. Plan mode allows the
  read/list tools only.
- Make a disabled knowledge module visible: post a once-per-channel status
  saying pinning is inactive instead of only logging.

Known limitations, deliberately out of scope: openclaw, cursor, and
nanoclaw have no per-message system-prompt channel and stay unpinned;
adapters that resume CLI-side sessions (amp, copilot, opencode) re-pin only
when their CLI session restarts, since only the Claude adapter has the
fingerprint-respawn machinery.
Address five review findings on the pinning-coverage commit.

The workspace-glossary fallback, once matched, was cached under the channel
key; the cached-id fast path then skipped the precedence check forever, so
a channel glossary created later stayed invisible until restart. Only
highest-precedence matches are cached now — fallback users pay one extra
list per message and pick up a new channel glossary on the next turn.

The knowledge disable was never handed to the MCP server: the CLI parsed
only --disable-files/--disable-browser, so the server registered the
knowledge tools regardless, and in execute mode
--dangerously-skip-permissions makes allowlist omission a suggestion, not
a boundary. The adapter now passes --disable-knowledge and the CLI maps it
into the server's disabledModules.

Goose builds a real per-turn --system prompt but was left unpinned; it now
prefetches and injects both sections read-only (like Gemini, it has no
workspace API commands to update entries with). Aider, Cline, and Mini
remain unpinned by design — aider is message-file driven, cline
deliberately keeps its own system prompt, mini deliberately has no
workspace context — and are recorded here as known limitations.

A transient knowledge read no longer wipes the last successful pin for
prompt-per-turn adapters: _prefetchPinnedContext keeps the previous good
result for whichever entry errored, and a thrown fetch keeps the previous
context whole.

The glossary result now carries its scope, and the prompt makes the
workspace-wide fallback read-only for channel agents — a channel-local
clarification must not silently rewrite definitions every other channel
relies on. Concurrent read-modify-write on the same entry remains
last-write-wins, a knowledge-API property shared with the decision log.
The cached-id fast path checked only status, assuming the entry still owns
its canonical title. Renaming a channel glossary or decision log (e.g. for
archival) therefore kept the old entry pinned indefinitely, masking the
workspace fallback or a replacement canonical entry. When GET returns a
title that differs from the canonical one, invalidate the cache and re-run
list-and-match. A GET response without a title (older backends) is never
treated as a rename.
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openagents-workspace Ready Ready Preview Aug 1, 2026 12:43pm

Request Review

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