Repository navigation
Skills as a first-class knowledge-page type: hand-authored first version, targeted delta maintenance, cautious automation #3786
Replies: 3 comments 3 replies
|
The hand-authored-first split you propose is the load-bearing design choice, and one constraint from running it is worth building in early: make hand-authored text win conflicts by construction, not by convention. In our setup machine-maintained content lives in a fenced, subordinate block inside the skill file; the server may rewrite its own fence but never the human text, so an errant delta can't silently reword an instruction someone wrote deliberately. Second constraint: maintenance writes follow four-operation discipline (add, update, delete, no-op) rather than append, because append-only delta streams are how a skill body accretes contradictions. And keep an adoption gate between a proposed delta and the live page: a skill doc is effectively editable weights, and closed self-edit loops on instruction text have a measured degradation mode. Pattern write-up: https://github.com/jimy-r/agent-workspace-architecture/blob/main/PATTERNS.md#10-a-skill-is-editable-weights--never-adopt-a-self-edit-without-a-gate |
|
Strong +1 on the direction — treating Skills as a first-class knowledge-page type (or extending knowledge pages to support a Skill mode) brings clear architectural value: human-authored initial baselines, progressive disclosure ( One conceptual distinction worth clarifying regarding Directives: The reason they surfaced as a comparison is largely an artifact of current API mechanics (where Directives happen to be the only entity accepting caller-supplied
Looking forward to seeing P0 ( |
|
I've been thinking about this and it makes sense somehow but not in the form you described. One thing that would make this better would be a new type of Document that is also a Knowledge Page (or part of the tree somehow) the reason is that we can automatically ingest it as usual and via a simple flag on the retain (like knowledge_page_tree_parent_folder_id or something) it becomes part of the tree. then all the updates/delete op are available already. it's automaticlaly available in recall and any other operation. the "prefer hand-authored knowledge page" over other documents is something we can make part of the reflect/refresh mechanism so kwnoledge pages steer in that direction. although this is not that simple as knowledge pages can change. on the skill part, I thought about it and the kwnoeldge pages api is basically already a skill so I don't see any new work here. except if if you want we coild add an "Export as Skill" but then it becomes obsolete soon and I don't see the value again. if you could explain more your use case of "hand-authored" vs derived and what are actual, concrete, content you'd see in one and another that would be really helpful to weigh in this solution |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Problem
Coding agents accumulate procedural knowledge — release steps, workarounds, "how we do X in this repo".
Hindsight today stores declarative knowledge very well (knowledge pages / mental models: "what this repo is"),
and always-on rules (directives). There is no home for "how to do a thing", and no lifecycle for it.
Claude Code / Hermes-style skills are the established shape for this: a short description used for
routing, a markdown body loaded only on demand. I'd like to propose skills as a knowledge-page type in
Hindsight, and I've done some local experimentation on the maintenance half. Sharing the findings first
because one of them changes where the fix belongs.
Why not just use what exists
contentrequired)A skill needs the directive's human authority, the knowledge page's delta maintenance, and a routing
semantic neither has. Conversely: if the body ends up unconditionally injected, a skill is just a long
directive and the concept isn't worth building. That's the load-bearing constraint.
Where whole-document rewrites actually come from
I ran a local experiment maintaining a hand-authored procedural page under
mode: delta(setup and resultsin the appendix). Short version: delta maintenance of hand-authored content works, but only inside a
scope that doesn't drift — feeding it topic-overlapping but semantically drifted memories rewrote the entire
document and erased every hand-written step line.
My first instinct was "delta reuses the reflect prompt, so skills need their own delta prompt." Reading the
code shows that's not where the problem is. A refresh is two stages across two independent LLM calls:
reflect_async(answer_as_document=True)loop, prompted with thebank-level
reflect_missionplus a hardcoded refresh context. Produces a candidate document.(
append_block/replace_block/add_section/ …). Blocks no op mentions are deep-copied verbatim —the module comment says prose drift is "structurally impossible" here.
The delta prompt is already separate and already safe. The drift is produced in stage 1, and stage 2
faithfully merges it. The other failure path is delta not running at all and falling back to full: five
reasons, of which
source_query_changedmatters most for hand-authored content — changingsource_queryat all makes the next refresh regenerate the whole document. Which is unfortunate, because"tighten
source_query" is exactly the fix my experiment landed on. Those two behaviours currently fighteach other.
Consequence: the fix isn't a new delta prompt. It's (a) a per-page synthesis instruction, (b) protecting
hand-authored content from the full-refresh fallbacks, (c) tighter scope.
Proposal
P0 — put description or a summary in the coding-agents page roster (small, standalone, useful now)
The session roster currently renders
- <title> (<id>); the page list parser dropsdescriptioneventhough the API returns it. So the agent has only a title to decide whether a page is worth reading, and
descriptionis reachable only by explicitly callinghindsight_list_knowledge_pages.Rendering
- <title> (<id>) — <description>improves routing for all knowledge pages, and is exactly theprogressive-disclosure shape skills need. This is worth doing regardless of whether the rest of the proposal
lands, and I'm happy to open it as its own PR.
P1 — hand-authored skills
contenton mental-model and knowledge-page create/update, across HTTP andMCP (MCP currently hardcodes the
"Generating content..."placeholder and can't setcontentat all;update_knowledge_pagehas nocontentparameter). Skip async generation whencontentis supplied andreturn a null
operation_id—CreateKnowledgePageResponse.operation_idis already nullable.last_refreshed_at/last_memory_seen_aton hand-authored creates so a fresh skill isn'timmediately stale. Documented consequence: memories predating the skill are never auto-merged.
type:<x>tag convention plusmanaged(the column already exists withexactly the "system-owned vs hand-authored" semantic, but isn't exposed on the API). Zero migration.
A
subtype='skill'column is the alternative — it allows SQL-level isolation, but the CHECK constraint hasalready drifted between Postgres and Oracle (
('structural','emergent','pinned','learned')vs('directive','pinned')), and nothing readssubtypefor behavior or selection today — the only read is abased_oncontext label that defaults to'structural'— so the read path that matters (a WHERE predicatefor isolation) would all be new. My preference is the tag first, the column only if reflect isolation demands a WHERE predicate.
mode=delta,refresh_after_consolidation=false,exclude_mental_models=true—human content is not auto-rewritten unless the user opts in.
SELECT COUNT(*) … WHERE bank_id = $1, so a single skill in a bank makes the agent's forced first step asearch_mental_modelscall — that's the concrete mechanism by which skills would pollute reflect and burntokens. The existing
exclude_mental_modelsflag is all-or-nothing (it skips the count entirely, bank-wide),so it can't express "exclude skills only" while keeping declarative models in the loop — that isolation is
exactly what the proposal wants to add.
The one invariant this breaks: knowledge-page bodies are currently always server-synthesized from
memory. Accepting a hand-authored
contentbreaks that. If maintainers consider the invariant load-bearing,skills can only live as bare mental models — at the cost of the tree,
hindsight fs mount, and the roster.That trade-off is the piece I'd most want a maintainer's read on before writing any code.
P2 — targeted maintenance (the real go/no-go gate)
refresh_instructions, feeding the synthesis leg. Split the two concerns thatsource_querycurrently conflates:source_query= what to retrieve (it drives embedding + BM25, so longinstructions pollute retrieval),
refresh_instructions= how to write. Skill default would be aprocedural-playbook instruction: exact steps, exact commands, known traps, append-only into existing
structure.
managed=falsedocuments,source_query_changedanddelta_ops_all_skippedshould skip-and-flag rather than silently regenerate;full rewrite becomes explicit (
force_full, or gated behind a dry-run diff).locked— block-level (delta ops targeting a locked block are skipped) and document-level (skipautomatic refresh entirely).
semantically drifted memories and verify the hand-written sections survive. Passing only on clean input is
not a pass.
P3 — automatic skill creation (default off, drafts only)
This is the only stage that can make the value floor negative — a wrong skill actively misleads. So:
drafts only, never published without human approval;
N≥2gate (a pattern must appear in ≥2 independentsessions); an anti-pattern blacklist (environment failures, negative claims, already-self-healed errors,
unresolved failures); update-preference order (update the loaded skill → merge into an existing sibling →
only then create); a per-bank draft quota.
Two practical notes: the coding-agents integration already has a turn-cadence mechanism, so "review every N
turns" needs no new machinery. But the transcript reader compresses
tool_useinto a one-line action anddiscards
tool_result— so "did that command actually work" isn't in the distillation input, which isprecisely the behavioural signal this stage depends on. Keeping a result code / first line (not the full
body) would likely be a prerequisite.
Also worth stating explicitly: the coding-agents tool surface deliberately does not expose page CRUD
("agents never author page structure"). Draft distillation would run server-side and keep that invariant
intact — but if maintainers read it differently, that's worth settling before any of P3. And P3 is
separable: stopping at P2 (hand-written + maintained) is a legitimate resting point that strands nothing.
Happy to split this into separate issues and start with P0 if the direction looks right.
Appendix — the local experiment
Setup: one hand-written procedural page (release flow: build → test → ship) with
mode: delta, thenrepeated "retain a related memory → refresh" rounds, diffing the body byte-for-byte each round.
Round 1 — failed. The memories I retained shared keywords with the page topic but had drifted
semantically ("release verification round N: N bug fixes confirmed" — same word "release", different
subject). The refresh rewrote the whole document into a verification-rounds table; every hand-written step
line was gone.
Round 2 — passed, 7/7. With
source_querytightened to explicitly exclude the confusable topic and onlygenuinely on-topic memories retained: the no-op refresh was byte-identical; new facts were appended
surgically; hand-written lines stayed byte-stable across 5 rounds; a manual edit survived the next refresh.
Two side findings from the same run:
dry-run-refreshand the real refresh are two independent LLM calls, sothe preview is directional and not a contract; and a
trigger.tag_groupsscope silently retrieved nothing,because
TagGroupLeaf.matchdefaults toany_strict(which requires non-empty tags) while observations in asharedscope carry none — two colliding defaults rather than a bug, but worth documenting.All reactions