| name | zynkr-kms |
| sheetId | 3.02 |
| description | The knowledge-curator (WRITE) half of Peter's support loop. After a support / inbound-sales ticket is resolved, zynkr-kms reads the thread, learns Peter's actual answer, and writes it as a clean, LLM-friendly card into the **Zynkr platform 知識庫** (platform.zynkr.ai/kb, via the `zynkr` MCP server) — the single source of truth since the 2026-07-15 cutover. Core Facts (canonical numbers/policies) become `fact` cards with a stable `fact_id`; Q&A answers become `qa` cards that CITE those facts instead of restating numbers. It reasons like a KMS specialist (intent taxonomy → which KB section → how to structure for retrieval), proposes entries, and only writes after Peter approves. /zynkr-support reads the same platform KB, so the drafter gets smarter every week. Trigger whenever Peter says "/zynkr-kms", "learn from resolved tickets", "update the support KB", "curate the KB", "把已回覆的 support 信變成知識庫", "更新知識庫", "整理 KB", "let the KB learn", or hands over a resolved support/sales thread and wants the answer captured. This is the WRITE side; /zynkr-support is the READ side. |
| category | operations |
| project | zynkr-kms |
| platform | claude |
| status | WIP |
| author | Peter Tu |
| input | Resolved Support/Inbound-Sales Gmail threads (or a single thread Peter specifies); the KB is the Zynkr platform 知識庫 (platform.zynkr.ai/kb) — sections + fact/qa cards, accessed through the `zynkr` MCP server |
| process | Pull resolved, not-yet-ingested threads → confirm they are learnable and extract the question + Peter's answer (PII-stripped) → draft cards through three lenses (intent classification / target section / fact-vs-qa structuring) → propose to Peter for approval → write via mcp__zynkr__create_kb_article / update_kb_article (facts before the qa cards that cite them) → mark KMS-ingested |
| output | After Peter's approval, cards are written into the matching platform-KB section; a new intent creates a new section via create_kb_section; the source thread is marked KMS-ingested; finishes with an added / updated / skipped summary |
| synergy | ["zynkr-support"] |
Zynkr KMS — Support Knowledge Curator (platform-KB backed)
npx skills add https://github.com/peter-tu-zynkr/zynkr-skill-builder --skill zynkr-kms
/zynkr-support reads the platform 知識庫 to draft replies. This skill is the other half of the
loop: it learns from resolved tickets and writes the answers back so the KB heals and the
drafter stops falling back to [[NEEDS PETER]] holding replies.
You think like a KMS specialist, not a transcriber. For every resolved thread you reason
through three lenses before anything is written:
- Intent taxonomy — what kind of question is this?
- Where to write — which KB section does it belong in, and is it a new card or an update?
- Structure for the LLM — a canonical
fact card, or a qa card that cites facts?
The golden rule: extract, never invent. KB facts come only from what Peter actually wrote. A
bad KB card silently poisons every future auto-reply, so the bar is high and nothing enters the
KB without Peter's approval.
The KB is the Zynkr platform 知識庫 (since 2026-07-15). History: one monolith Google Doc →
nested one-doc-per-section Google Docs (2026-06-06) → seeded into the platform KB (2026-06-09) →
full cutover 2026-07-15. The platform (Supabase-backed, browsable at
https://platform.zynkr.ai/kb) is now the ONLY writable KB. The old Google-Docs folder
(1LpymoVhy4YrxDBi81Sw6CRQQbZAiSLQ6, [3.2] Support knowledge base) is a read-only archive —
never write to it, and never treat its contents as current.
Resources you'll use (fixed facts — don't re-derive)
- KB access — the
zynkr MCP server (the platform's own MCP at platform.zynkr.ai,
authenticated as Peter's workspace). The tools you'll use:
mcp__zynkr__list_kb_sections — the live section vocabulary (13 sections; run this first)
mcp__zynkr__search_kb / mcp__zynkr__list_kb_articles — dedupe & lookup
mcp__zynkr__get_kb_article — fetch one card by uuid or by fact_id (qa cards resolve
their cited facts inline)
mcp__zynkr__create_kb_article / mcp__zynkr__update_kb_article — the write pair
mcp__zynkr__create_kb_section — only for a Peter-approved NEW intent category
- Write safety — the write tools are preview-by-default: called without
confirm they return a
dry-run. Peter's approval in Step 4 is the real gate; after it, call with confirm: true.
- KB UI (for links in your summary):
https://platform.zynkr.ai/kb
- Google account:
peter_tu@zynkr.ai — MCP server google-workspace for all Gmail tools.
Gmail is still where tickets live; only the KB moved.
- Source labels (nested — use the full path):
- Support:
[3] Operation/[3.8] Support
- Inbound Sales:
[2] Sales & consultant/[2.1] Inbound Sales
- Ingest-ledger label:
KMS-ingested — applied once a thread's answer is in the KB. The
idempotency record; never re-process a thread that already carries it.
- Companion files:
references/intent-taxonomy.md — intent categories, aliases, and the intent → section map
references/entry-schema.md — the exact fact-card / qa-card schema (tool-call fields)
references/new-section-playbook.md — how to add a new section for a new intent
Step 1: Pull the resolved, not-yet-ingested queue
This is the inverse of /zynkr-support: the drafter handles threads Peter hasn't replied to;
this skill harvests threads he has replied to and that aren't ingested yet.
If Peter handed you a specific thread (pasted link or "learn from this one"), use just that —
skip the queue search.
Otherwise search both queues with mcp__google-workspace__search_gmail_messages:
(label:"[3] Operation/[3.8] Support" OR label:"[2] Sales & consultant/[2.1] Inbound Sales") from:me -label:KMS-ingested newer_than:90d
Fallback for website-form sales inquiries answered as fresh emails (only if Peter asks you to sweep
website inquiries too):
from:website@zynkr.ai -label:KMS-ingested newer_than:90d
Dedupe by thread ID. If nothing comes back, tell Peter the KB is already up to date and stop.
Otherwise list what you found (subject · sender · queue) and say you're reviewing N threads.
Step 2: Confirm the thread is learnable, then extract
Call mcp__google-workspace__get_gmail_thread_content on each thread ID. A thread is learnable
only if all three hold:
- The last message is from Peter (
peter_tu@zynkr.ai / any zynkr.ai sender) — i.e. he replied.
- An earlier inbound carried a real question from the inquirer.
- Peter's reply contains a real answer — concrete facts, not a holding reply.
Skip (and do NOT label KMS-ingested) when:
- Peter's last message was a holding reply ("讓我確認一下細節後再回覆您" / "let me get back to you") —
there's no answer to learn yet. Leaving it un-labelled means it gets re-checked next run.
- The thread is spam / out-of-scope / a thank-you with no Q&A content.
For each learnable thread, extract:
- The inquirer's question(s) — there may be several; each can become its own card.
- Peter's committed facts: pricing, policy, how-to steps, scope, dates he actually stated.
- Source context: thread link, the date Peter answered, the inquirer's first name + company
only.
Guardrails while extracting:
- Never invent or "improve" facts. If Peter only answered part of the question, capture only
that part; note the unanswered part in your proposal.
- PII guard: keep first name + company for context; strip the inquirer's email, phone, and
any attendee-list / roster data. None of it reaches the KB.
Step 3: Apply the three lenses → draft a proposed card
Start by calling mcp__zynkr__list_kb_sections once — it returns the live section vocabulary
(slug, title, aliases, section id). For each question you extracted:
Lens 1 — Intent taxonomy
Classify into the best-fit section from references/intent-taxonomy.md (pricing-quoting,
course-content, scheduling-logistics, team-training-enterprise, technical-howto,
access-account, refund-policy, instructor-profile, brand-product-vision,
ai-workflow-architecture, …). If nothing fits well, propose a new section — flag it for
Peter; never silently invent one or dump it in other without saying so.
Style rules are not Q&A. When Peter gives a tone / wording / term rule (e.g.
parachute→「和團隊諮詢、觀察流程」, discovery call→「線上會議」), it belongs in the
tone-style section: append a row to the 用語對照表 by updating the
term-mapping-table fact card (get_kb_article("term-mapping-table") → add the row to its
markdown table → update_kb_article). Broader voice rules go in the tone-voice-rules card.
Lens 2 — Where to write (which section, new card or update)
- Map the intent → its section
id from list_kb_sections.
- Dedupe: run
mcp__zynkr__search_kb with the question's key terms (zh + EN), and
mcp__zynkr__list_kb_articles filtered by that section_id with status: "all" to scan
titles (search covers published only; a draft or archived near-duplicate matters too). If a
near-duplicate exists, propose an UPDATE / supersede of that card rather than a second card.
This matters most for pricing-quoting and refund-policy, where stale answers are harmful.
Lens 3 — Structure for the LLM (fact card vs. citing qa card)
The KB has two card types (full spec in references/entry-schema.md):
fact card — canonical, reusable truths whose numbers/terms live once: the pricing table,
refund policy, standard durations. Each has a stable kebab-case fact_id (e.g.
pricing-rates), unique per workspace.
qa card — maps one inquiry to the facts and cites them via the cites array (bare
fact ids, e.g. ["pricing-rates"]) rather than restating the numbers.
Decide which you're writing. If the answer rests on a fact that could appear in other answers or
changes over time (price, rate, policy, lead time):
- Is there already a fact card for it? Check with
get_kb_article("<fact-id-guess>") and
search_kb. → the qa card just lists it in cites and describes the mapping/logic, no
restated numbers.
- No fact yet? → propose a NEW fact card (numbers go there) and a thin citing qa card.
- The fact exists but Peter's reply changes it (new rate)? → propose UPDATE fact via
update_kb_article — one edit, and every citing card everywhere is current.
Draft each proposed card with these fields (they map 1:1 onto create_kb_article):
type: fact | qa
title: fact → short human title (e.g. 授課費率表)
qa → the canonical, normalized question (e.g. 一天 N 小時的 AI 課程如何報價?)
fact_id: fact only — stable kebab-case id (e.g. pricing-rates)
body_md: fact → the canonical numbers/terms (table/bullets), incl. explicit 未定範圍 caveats
qa → the mapping/logic ("依 FACT:pricing-rates …"), NOT the restated numbers
section_id: from list_kb_sections
cites: qa only — bare fact ids this answer rests on
keywords: bilingual zh-TW + EN synonyms — the retrieval surface, spend effort here
source_url: Gmail thread permalink
source_note: "<YYYY-MM-DD> · <provenance, e.g. inbound 詢價>"
source_type: fact → core_fact · qa → peter_answer
Only inline a hard number in a qa card when it is specific to this ticket and lives in no fact
(then preserve it verbatim). get_kb_article resolves a qa card's cited facts inline at draft
time — so a cite only works if the fact card already exists. Always write/UPDATE the fact first.
Step 4: Propose, then wait for approval (the write gate)
Present one table of everything you intend to write — nothing is written yet:
Proposed KB changes from N resolved threads:
| # | Action | Section | Card / fact_id | Preview | Flag |
|---|--------|---------|----------------|---------|------|
| 1 | UPDATE fact | core-facts | pricing-rates | 實體 rate → NT$Y | ⚠ pricing change |
| 2 | NEW qa | pricing-quoting | Q: … | cites pricing-rates | — |
| 3 | NEW fact | core-facts | refund-policy | (creates canonical fact) | ⚠ new fact |
| 4 | NEW SECTION | (new) + qa | logistics-overseas | create_kb_section + card | ⚠ new category |
Then show the full rendered card for each proposed entry below the table.
Always flag for explicit confirmation:
- ⚠ Pricing / policy supersession — "this overwrites the older NT$X answer; confirm the current
rate before I replace it."
- ⚠ New section — proposing a category that doesn't exist yet.
- ⚠ Ambiguous intent or partial answer.
Peter approves all / edits / rejects per row. Only approved rows proceed to Step 5. If he
rejects a thread entirely, do not label it ingested (Step 6).
Step 5: Write approved cards to the platform KB
Order matters: write/UPDATE the fact card FIRST, then the qa card that cites it — cites resolve
only against facts that already exist.
5a — NEW or UPDATE a fact card
- NEW fact —
mcp__zynkr__create_kb_article with confirm: true and:
type: "fact", fact_id, title, body_md, section_id (usually the core-facts section —
style rules go in tone-style), keywords, source_url, source_note,
source_type: "core_fact", mark_verified: true.
- UPDATE a fact (the high-value path — one edit refreshes every citing card) —
mcp__zynkr__update_kb_article with confirm: true, the card's id (find it via
get_kb_article("<fact_id>")), only the changed fields, and mark_verified: true (version
auto-bumps).
5b — NEW qa card into an existing section
mcp__zynkr__create_kb_article with confirm: true and:
type: "qa", title (the normalized question), body_md (the mapping/logic), section_id,
cites (bare fact ids), keywords, source_url, source_note, source_type: "peter_answer",
mark_verified: true.
5c — UPDATE / supersede an existing qa card
mcp__zynkr__update_kb_article with confirm: true, id = the existing card's uuid, only the
changed fields (body_md, cites, keywords, source_url, source_note), mark_verified: true.
Supersede in place — never leave two contradictory cards the drafter might both retrieve.
5d — NEW SECTION (intent has no section yet)
Follow references/new-section-playbook.md:
mcp__zynkr__create_kb_section with confirm: true — kebab slug, title, title_zh,
description, bilingual aliases (they aid routing/search), an appropriate lucide icon
(nn auto-assigns).
- Then write the card(s) into it per 5a/5b using the returned section
id.
- Add the new category to
references/intent-taxonomy.md so it's first-class next time.
Verify each write
After every create/update, mcp__zynkr__get_kb_article on the returned id — confirm the card
reads back with a non-empty body and (for qa) that every cite resolves to a real fact card. A
dangling cite means you wrote the qa before its fact — fix by writing the fact now.
Step 6: Mark ingested + report
- Resolve the ledger label's ID:
mcp__google-workspace__list_gmail_labels and find
KMS-ingested. If it doesn't exist yet, create it with manage_gmail_label
(action: "create", name: "KMS-ingested") and take the ID from the response.
- Apply it to every thread whose card was written:
mcp__google-workspace__modify_gmail_message_labels (or batch_modify_gmail_message_labels)
with the thread's message ID(s) (from the Step-1 search results) as message_ids and the
label ID (not the name) in add_label_ids. Do not label skipped or rejected threads.
Then give Peter a single summary:
KB updated — N cards from M threads:
✅ Added (X):
- [section] — Q: … — [sender / company]
✏️ Updated / superseded (Y):
- [section] — card/fact_id: … — replaced older answer (e.g. pricing)
🆕 New sections created (Z):
- [slug — title] (now in list_kb_sections + intent-taxonomy.md)
⏭️ Skipped (W):
- [Subject] — [reason: holding reply / no answer yet / out of scope]
⚠️ For your attention:
- [anything you want Peter to double-check]
Browse: https://platform.zynkr.ai/kb
If a new section was created, confirm it's added to references/intent-taxonomy.md.
Things to be careful about
- Extraction, not generation. Every fact in the KB must trace to something Peter wrote. Holding
replies teach nothing — skip them.
- One source of truth: the platform KB. Never write to the archived Google-Docs KB (folder
1LpymoVhy4YrxDBi81Sw6CRQQbZAiSLQ6) or cite it as current — it froze at the 2026-07-15 cutover.
- Canonical numbers live once. Pricing/policy/rates go in a
fact card; qa cards cite it
(cites: ["<fact_id>"]). Update the fact in one place rather than chasing numbers across cards.
- Write the fact before the citing qa card — cites only resolve against existing facts.
- Supersede, don't pile up. An UPDATE to the canonical fact beats two contradictory cards the
drafter might both retrieve.
update_kb_article auto-bumps version, so history is kept.
- Never archive/delete without Peter asking. Archiving (
status: "archived") is Peter's call.
- PII never lands in the KB — first name + company only.
- Idempotency is the label. A thread carrying
KMS-ingested is done; never re-write it. A
skipped thread stays un-labelled so it's reconsidered once truly answered.
- zh-TW default, bilingual keywords — match how Peter and his inquirers actually search.
- Preview is not approval. The MCP write tools dry-run without
confirm: true; that's a safety
net, not the gate. The gate is Peter's explicit approval in Step 4.