feat(agents): the desk remembers — agents check precedent before acting - #1
Merged
Conversation
A resolver had no memory: every ticket started from zero even when the same fault had been solved last month. Three read-only tools give it one. - search_tickets ranks past tickets on relevance — title, description and the resolution someone actually recorded — preferring tickets that reached an outcome, filterable by category or to resolved-only. - read_ticket opens one in full: the request, the replies sent, the tools the agent used, the resolution note. - requester_history lists what else this person has filed, so the third replacement dock in two months reads as a hardware fault, not a new request. Precedent without the privacy leak: another requester's name and email are withheld unless the past ticket belongs to the same person, so nothing can be quoted back to the wrong requester. An MCP caller, having no ticket in context, sees every identity withheld. Ranking is scored in memory over a bounded candidate window rather than in SQL: SQLite has no relevance scoring and Servo ships without an FTS extension so self-hosting stays a one-liner — and the scoring becomes unit-testable. The MCP server's own unranked search_tickets is deleted in favour of the registry tool, so external clients get the same ranked, redaction-aware results the agents get. The mock provider reads the tool list it is handed and opens its script with search_tickets when granted, so the offline demo shows precedent-checking without an API key. All three are LOW risk and need no approval; policy rows backfill on upgrade, so the default resolver gains them without a reseed. Specialists an admin has edited are never rewritten — they opt in from Agents -> Tools, as documented. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016L84GW5HDSnb5DH93F8era
This was referenced Aug 14, 2026
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.
What was ported
A resolver had no memory: every ticket started from zero even when the same fault had been solved last month. Three read-only tools give it one, in
src/lib/ai/tools/history.ts, with the pure ranking/redaction core insrc/lib/ai/ticket-history.ts:search_tickets— ranks past tickets on relevance across title, description and the resolution someone actually recorded, preferring tickets that reached an outcome. Filterable bycategoryor to resolved-only.read_ticket— one past ticket in full: the request, the replies sent, which tools the agent used, the resolution note.requester_history— what else this person has filed, so the third replacement dock in two months reads as a hardware fault, not a new request.All three are risk LOW, no approval — they only read.
Where the idea came from
Paperclip's MCP server and its
packages/skills-catalogprogressive-disclosure pattern: a cheap catalogue you search first, with the expensive full read behind a second call. Claude Code's search-then-read tool pairing is the other half. No upstream code was copied — the ranking (stemming, per-field weighting, settled-ticket bonus) and the redaction rule were written against Servo's own schema, soTHIRD-PARTY.mdneeds no entry.Why this, and why now
It is the ROADMAP's "knowledge for agents" goal at a fraction of the cost: no embedding model, no vector store, no new dependency, and it works offline on the SQLite that already ships with the app.
Design decisions
mayRevealRequester()reveals a name or email only when the past ticket belongs to the same requester as the one being worked; otherwise the hit readsanother requester (withheld). An MCP caller has no ticket in context, so it sees every identity withheld. Precedent is useful; other people's identities are not, and an agent must not quote one person's details back to another.search_tickets.src/lib/mcp.tscarried its own unranked title/description LIKE search. Deleted, so the registry tool is served instead — external clients now get the same ranked, redaction-aware results the agents get.ensureToolPolicies()backfills the three policy rows, so the default resolver gains the tools without a reseed. Specialists an admin has edited are never rewritten, so their allowlists are extended from Agents → Tools — documented in the user guide. The bundledagents/*.mdcarry the tools for fresh installs.MockProviderreads the tool list it is handed and opens its script withsearch_ticketswhen the tool is granted, so the offline demo shows precedent-checking without an API key.How it was validated
Nothing here is claimed on inspection.
npm ci·npm run typecheckclean ·npm run buildsucceeds.npm test: 93 passing — 41 pre-existing (all still green) + 52 new acrosstests/ticket-history.test.ts(ranking, stemming, resolution extraction, redaction, formatting) andtests/history-tools.test.ts(the tool contract against a stubbed Prisma client: query shape, limits, redaction, and "return the error as text, never throw").npm run setupon a fresh database: 21 tool policies, 4 profiles seeded with the new tools. Re-run on a populated database: 21 policies backfilled, 0 profiles overwritten — the upgrade path.search_tickets, found#1001, read back "Reissued the VPN certificate and pinned the client to the EU gateway" — and neither Ravi's name nor his email appeared anywhere in the result.search_tickets,read_ticketandrequester_historyare served.One real bug surfaced during testing and was fixed:
"resetting"stemmed toresett, matching neitherresetnor anything useful. The stemmer now undoes the consonant English doubles before-ing/-ed.What was rejected
packages/adapters/*(claude-local, codex-local, cursor-cloud, gemini-local, hermes…) — they adapt coding-agent CLIs and assume Paperclip's mutable server+UI dual registry and its agent-hire model. Servo's BYOK layer already covers Anthropic- and OpenAI-compatible endpoints from Settings, which is the part that pays.packages/plugins/*— worker isolation, a manifest format and a plugin SDK are a large surface already served in Servo by custom HTTP tools plus the MCP server. Infrastructure, not breadth.packages/db— its own schema and migration story; Servo is Prisma + SQLite with the string unions insrc/lib/types.tsas the source of truth.docs/PORTING-LEDGER.mdis created in this PR and records all of the above, plus the candidates for the next run (agent skills as versionable files;fetch_urlbehind the egress allowlist; knowledge-gap mining on top of desk memory).🤖 Generated with Claude Code
https://claude.ai/code/session_016L84GW5HDSnb5DH93F8era
Generated by Claude Code