Knowledge Curator
Decides which discovered URLs are worth syncing into an agent's
knowledge corpus. Companion to the deterministic knowledge-sync
skill (#144).
The split:
knowledge-syncowns the mechanic — fetch, parse, write marker-delimited sections. Deterministic Python.knowledge-curator(this skill) owns the policy — judge each candidate URL against the agent's purpose, decide in-use vs. rejected, persist the verdict. LLM-orchestrated.
Activation
This skill activates when:
- User invokes
/aida knowledge curate <agent> - User invokes
/aida knowledge review <agent> - Routed from the
aidaskill for these operations
Operations
| Operation | Mode | Description |
| --------- | ----------- | ---------------------------------------------------- |
| curate | LLM-decides | Walk pending URLs; verdict each (in-use | rejected) |
| review | Interactive | Walk recent decisions; let human confirm or override |
Critical invariants (read before any write)
Atomic decisions file pair. decisions.json and decisions.md
must stay in lockstep. Both are written together by
scripts/shared/decisions_log.py::write_decisions(...). There is no
other supported write path.
This skill MUST follow these rules:
- Never write
decisions.jsondirectly (no Edit / Write tool on that file). Read withdecisions_log.read_decisions(...); write withdecisions_log.write_decisions(...). The atomic write regeneratesdecisions.mdfrom the JSON in the same call. - Never hand-edit
decisions.md. It carries a generated-file banner at line 1. Any change you want to land must go throughdecisions.jsonand re-runwrite_decisions. - Never re-decide
locked: trueentries. A human has confirmed them; the curator skips. Only/aida knowledge reviewcan flip a locked decision. - Never decide already-decided entries. The curator only
verdicts
status: pendingentries. Existingin-useorrejectedentries stay as they are unlessreviewoperates on them.
Curate workflow (/aida knowledge curate <agent>)
The user wants the LLM to judge each pending URL discovered for the target agent. Walk this sequence carefully:
Step 1 — Load the curator's reasoning context
You need enough information to answer: "does this URL add something the agent doesn't already have?"
Read into context:
- The agent's purpose —
agents/<agent>/<agent>.md. The frontmatterdescriptionis the load-bearing signal. The markdown body has supporting expertise / judgment notes. - The agent's knowledge index —
agents/<agent>/knowledge/index.md. Tells you what files exist and what each covers. - The heading structure of every existing knowledge file —
walk
agents/<agent>/knowledge/*.md(excludingindex.mdandexternal-references.md), reading H1/H2/H3 headings into a compact outline. You do not need to load full file bodies. Headings alone show coverage scope; bodies bloat context. Add a targeted read only if a candidate URL appears to overlap a knowledge file and you need to disambiguate. - The existing decisions —
agents/<agent>/knowledge/decisions.jsonloaded viadecisions_log.read_decisions(...). Use the prior rejections and approvals for consistency: do not contradict a prior locked rejection, and prefer reasoning consistent with prior verdicts.
Step 2 — Walk pending entries
For each Decision(status="pending") in the existing decisions:
- Skip if
locked: true. (Curator never re-decides locked entries — those are human-confirmed.) - Fetch a content sample. Use
http_source.HttpFetcher().fetch(url)and take the first ~500 chars ofoutcome.content. Don't load the whole page — sample is enough to judge topic + relevance. If fetch fails (source-missing,fetch-error,too-large), record that as the verdict reason and reject the URL. - Reason about the URL. Ask yourself:
- Is the topic on-domain for this agent's purpose?
- Does the agent's existing knowledge already cover this topic? (Check headings.)
- Does the content sample look authoritative and stable enough to vendor?
- Decide. Either
status="in-use"orstatus="rejected". - Write the verdict by constructing a new
Decision(...)and callingupsert_decision(...)thenwrite_decisions(...). Required fields:decided_at:decisions_log.now_iso()decided_by:"llm"reason: one to three sentences explaining the verdictinforms(only whenin-use): list of knowledge file basenames this URL should refresh
Batch all updates and call write_decisions once at the end —
the JSON+MD regeneration is the same cost regardless of batch
size, and one atomic write is simpler to reason about than N.
Step 3 — Report
Tell the user what you decided. Group by verdict:
Curated 12 pending URLs for agent 'claude-code-expert':
✓ 5 in-use (informs: skills.md, design-patterns.md)
✗ 7 rejected (out-of-scope / redundant)
decisions.json + decisions.md updated.
Run /aida knowledge review claude-code-expert to confirm or override.
If any URLs failed to fetch, surface those separately so the user
can edit sources.yml to remove or fix the root that produced them.
Review workflow (/aida knowledge review <agent>)
The user wants to confirm, override, or lock the LLM curator's recent verdicts. Walk this sequence:
Step 1 — Load decisions
Read agents/<agent>/knowledge/decisions.json via
decisions_log.read_decisions(...).
Step 2 — Show the user
Present the recent LLM verdicts (those with decided_by: "llm",
locked: false) grouped by status. For each, show:
- URL
- Decided-at timestamp
- Reason (one to three sentences)
- The
informs:list ifin-use
Step 3 — Ask the user
For each verdict, the user can:
- Confirm (no change; optionally set
locked: trueso the curator never re-decides it) - Override the verdict (flip in-use ↔ rejected; capture a new
reason from the user, set
decided_by: "human") - Edit metadata (update
informs:for in-use entries; updatereason) - Skip (move on without touching)
Prefer running this interactively — AskUserQuestion for each
verdict — when the count is small. If there are many decisions
(>10), offer a "review all in batch" flow that walks them in a
single pass and accepts free-text per-entry input.
Step 4 — Persist
Construct the new decisions list, call write_decisions(...).
Report a short summary:
Reviewed 12 decisions:
3 confirmed and locked
2 overridden (now in-use)
7 unchanged
decisions.json + decisions.md updated.
Path Resolution
Base Directory: Provided when skill loads via
<command-message> tags.
Shared module imports (run scripts under the AIDA venv):
~/.aida/venv/bin/python3 -c "
import sys; sys.path.insert(0, '{base_directory}/../../scripts')
from shared.decisions_log import (
Decision, read_decisions, write_decisions, upsert_decision, now_iso,
)
from shared.http_source import HttpFetcher
# ... your workflow here
"
The skill itself has no script entry point — the LLM workflow drives the deterministic primitives directly.
Out of scope (deferred to Slice 2 of #144)
- Sync integration —
/aida knowledge syncwill be extended to mergedecisions.jsonin-useentries withsources.yml /aida knowledge audit— drift report (last-reviewed dates, new pages found upstream since last walk)/aida knowledge promote <agent> <url>— manual in-use without the curator/aida knowledge regenerate-md— repair ifdecisions.mdis hand-modified- Rich
decisions.mdformatting (Slice 1 ships a plain dump) conflict-suppressedsync status whensources.ymlanddecisions.jsondisagree