Build nodes from content and meta only - #94
Merged
Conversation
Removes the live-keg copy path and the archive export tool from the agent surface: the `import_from_keg` and `export` MCP tools, the `tap import` command, and `Tap.ImportFromKeg` with its supporting package. `export` was already unreachable. registerArchiveTools was defined but never called from NewServer, so the tool it registered had no way to appear in a tools/list response -- deleting the file removes code that only looked like a feature. Archive support itself is untouched. `tap archive` and the pkg/keg export/import machinery it drives stay exactly as they are; what goes is the node-level cross-keg copy, whose agent-facing story overlapped `create` and whose CLI name collided confusingly with `archive import`. The parity coverage table loses the ImportFromKeg row and the note explaining that collision, since neither has anything left to describe.
Every tool failure now carries a recovery contract: a `code`, an `action` naming the next step, and an `operationPerformed` flag saying whether state changed. Previously only two paths -- auth and precondition-required -- had structured content, so for every other failure an agent got a bare message and had to guess whether to retry, whether the write landed, and what to fix. operationPerformed is three-valued on purpose. false means the request was refused before anything was written, which is knowable for each classified code because all of them are raised during validation or precondition checking. null means the outcome is genuinely unknown, which is the honest answer for an unclassified failure that may have been raised mid-write. Reporting false there would be a guess, and the point of the field is that an agent can trust it instead of reading the node back to find out. Orientation failures get the same treatment, with one wording split that came out of a smoke-test run. A denial on a call that named no flight is now described in terms of what the caller did -- it named a keg, which selects a target and never grants authority -- rather than blaming "the selected flight" the caller never selected. The old wording read as a wrong flight name and sent agents through list_flights looking for a flight that was not the problem. The denial text also deliberately stops telling agents to reorient: a denial is not disorientation, and the next call resolves live authority anyway. Finally, schemaWithFlight now appends a description of the flight parameter at the same point it injects the property, so schema and prose cannot drift. Agents read descriptions, not just schemas, and an injected property that nothing mentions reads as absent -- it had already been reported as a missing feature.
A node is built from exactly two inputs: Body, the markdown whose explicit H1 is the node's only title source, and Meta, the complete metadata document. keg.NodeCreate and keg.CreateOptions drop Title, Lead, Tags, and Attrs, which between them had given a node's title two sources and its metadata three, with no rule saying which won. keg.RejectFrontmatter becomes the single definition of the rule that content must not open with a --- block, so every writer -- the CLI, MCP, the remote keg, and Tapper Hub -- enforces it identically and metadata keeps one home. Callers that used to embed frontmatter in the body now split it into Meta. The MCP surface follows the same shape. create takes content and meta; edit takes optional content and meta pointers, so a call can replace either document alone while one expected_hash still covers both. cat gains a structured read whose fields are exactly what edit accepts, letting a read-modify-write cycle round-trip without parsing output meant for humans -- which is why it returns the two documents separately rather than a composed ---meta---body blob that edit would reject. The standalone meta tool is retired along with Tap.MetaBatch. Reading metadata is cat with meta_only and writing it is edit's meta field, so metadata has one read path and one write path instead of a second tool that could disagree with them. The parity table records that the Meta capability is now covered by two MCP tools rather than one. Keg.Next leaves the interface. Ids are assigned by the create call that allocates them and returned in its result, so there is no reason to reserve one ahead of time, and a reserved-but-unwritten id was a hole a caller had to remember to fill. Two smaller correctness fixes ride along in the metadata parser, both reached by the new single-document path. ParseMeta now names the shape that actually arrived -- "metadata must be a YAML mapping of keys, got a sequence" -- rather than surfacing the decoder's complaint about an internal struct it could not fill, because that message reaches API clients verbatim. NodeMeta.Keys reports the document's top-level keys so a caller can detect two inputs writing the same key before either write happens; Get cannot serve that purpose, since it reports only scalars and silently misses nested keys.
edit took its items under "edits" while create, remove, and node_snapshot all took theirs under "nodes". The four batch mutation tools now agree on one parameter name, so an agent that has learned the shape of one has learned all of them and cannot pick the wrong key by analogy. A client still sending "edits" is rejected rather than silently accepted, which is the same answer the schema already gives for any other unknown key.
Updates the agent-facing integration content and guides for the two-input node model and the renamed MCP edit parameter, and re-renders the Claude and Codex skills from it. Two corrections to the tool inventory came out of smoke-test runs where agents could not reach any KEG through subflight-scoped calls. Both were usage errors the documentation invited: `flight` is described as operational, not discovery-only, with a concrete JSON example carrying the exact canonical name. The inventory had described the parameter conceptually, so agents passed a bare flight name; unqualified names resolve against the active KEG, and under a root with an empty cover there is nothing to resolve against, which surfaces as ORIENTATION_DENIED and reads like a missing feature. One report concluded from this that the server does not forward flight context to operational tools, which is the opposite of what the contract says. `keg` is now stated plainly to never grant authority -- it selects a target within the authority a call already has. Naming an uncovered KEG is ORIENTATION_DENIED because the access control is working, and that denial had been logged as a defect. cliff.toml gains a breaking-changes section. CHANGELOG.md is generated from commit messages, but the template grouped only by type and never read commit.breaking, so a future `feat(keg)!:` with a BREAKING CHANGE: footer would render as an ordinary feature bullet and a consumer scanning the changelog would never learn their client broke. Nothing in this project is a stable API yet, so no commit is marked breaking today and the section stays empty; verified that regenerating with the new template leaves every historical entry byte-identical.
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.
A node is built from exactly two inputs:
content, the markdown whose explicit H1 is its only title source, andmeta, the complete metadata document. Previously the same information was spread acrossTitle,Lead,Body,Tags, andAttrs, which gave a node's title two sources and its metadata three, with no rule saying which won.Five commits, each independently buildable and reviewable in order.
refactor(cli,mcp): remove cross-keg import and archive toolingDrops the
import_from_kegandexportMCP tools, thetap importcommand, andTap.ImportFromKeg.exportwas already unreachable —registerArchiveToolswas defined but never called fromNewServer, so its tool could never appear in atools/listresponse.Archive support itself is untouched:
tap archiveand thepkg/kegmachinery behind it stay. What goes is the node-level cross-keg copy, whose agent-facing story overlappedcreateand whose CLI name collided witharchive import.feat(mcp): return structured error guidance from every failed tool callEvery tool failure now carries a recovery contract — a
code, anactionnaming the next step, and anoperationPerformedflag. Only auth and precondition-required had structured content before, so every other failure left an agent guessing whether to retry and whether the write landed.operationPerformedis three-valued on purpose:falsewhere the request was provably refused before any write,nullwhere the outcome is genuinely unknown. Reportingfalsefor an unclassified mid-write failure would be a guess, and the point of the field is that it can be trusted without reading the node back.Orientation denials get clearer wording too. A denial on a call that named no flight is now described in terms of what the caller actually did — it named a
keg, which selects a target and never grants authority — instead of blaming a "selected flight" the caller never selected.feat(keg): build nodes from content and meta onlyThe core change.
keg.NodeCreateandkeg.CreateOptionstake onlyBodyandMeta.keg.RejectFrontmatterbecomes the single definition of the rule that content must not open with a---block, so the CLI, MCP, the remote keg, and Tapper Hub all enforce it identically.catgains a structured read whose fields are exactly whateditaccepts, so a read-modify-write cycle round-trips without parsing output meant for humans — which is why it returns the two documents separately rather than a composed---meta---bodyblob thateditwould reject.The standalone
metatool retires along withTap.MetaBatch: reading metadata iscatwithmeta_only, writing it isedit'smetafield.Keg.Nextleaves the interface — ids are returned by thecreatethat allocates them, so a reserved-but-unwritten id is no longer a hole a caller has to remember to fill.refactor(mcp): name edit's item list nodesedittook its items undereditswhilecreate,remove, andnode_snapshotusednodes. All four batch mutation tools now agree.docs: record the content/meta node model and flight usageUpdates the integration content and re-renders the Claude and Codex skills. Two tool-inventory corrections came out of smoke-test runs where agents could not reach a KEG through subflight-scoped calls; both were usage errors the docs invited, and one had been logged as a server defect when the access control was working as specified.
Compatibility
Nothing here is marked as a breaking change — no surface in this project is stable yet — but callers are affected: the
createitem shape changes,edit's parameter is renamed, and themeta,import_from_keg, andexporttools are gone. Tapper Hub consumes this API and has a matching branch that is not yet open.Verification
go build ./...andgo test ./...pass at each of the five commits, not just at the tip.