Skip to content

kb-add silently falls back to default_kb when no destination is named #802

Description

@williamthorsen

Problem

kb-add resolves its writable knowledge base via a precedence chain: explicit --kb <name>, then .kb/ ancestor discovery, then the registry's default_kb. When --kb is omitted and no .kb/ is discoverable, it silently writes to default_kb. This is the same "succeeds-too-easily" failure family as #800: removing destination friction pulls the agent toward under-specifying where a note lands, so a note can end up in the default store when a specific KB was intended.

Context

  • .kb/ ancestor discovery is legitimate context resolution (it finds the KB you are physically working in) and must be preserved. This is why the fix cannot be a copy of default_kb override: agents ignore explicit KB destinations and capture to default_kb instead #800.
  • default_kb override: agents ignore explicit KB destinations and capture to default_kb instead #800 fixed the analogous bug in capture-event by making --store always required, with --store @default as the deliberate sentinel for the registry default. capture-event has no discovery tier, so its "always require a name" approach does not transfer directly.
  • Footprint: the fix lives in the shared writable-KB resolver, whose other consumers are kb-curate and kb-edit. The new precedence therefore applies uniformly across all three tools — refusing a silent default_kb fall-through and reaching the registry default only via --kb @default are not kb-add-specific. This entails routing the tightened resolver outcomes through the kb-curate and kb-edit CLIs and bringing kb-curate's documentation in line with the new precedence alongside kb-add's.
  • Out of scope: the separate kb-add bug in which --folder paths are written to the KB root instead of the content/ subdirectory, captured in event 01KV9NG9Z75XPNX0MAZEJG5Z4Q.

Proposed solution

Refuse the bare fall-through to default_kb, and reach the registry default only through an explicit @default sentinel that mirrors #800, while keeping .kb/ discovery as the in-context default.

The resulting precedence is: an explicit --kb @default (the only path to the registry default), then a concrete --kb <name>, then .kb/ discovery, then a refusal when no --kb is given and no .kb/ is found. The shared writable-KB resolver gains distinct failure outcomes for "no destination could be determined" and "@default requested but no default configured," so the skill can route each to its own recovery. The refusal that fires when a destination cannot be determined is self-documenting: it names the registered knowledge bases and points to --kb @default. The @default sentinel value is shared with capture-event as a single cross-tool constant so the two tools cannot drift.

Acceptance criteria

Must have

  • When --kb is omitted and no .kb/ is discoverable, kb-add refuses to write rather than falling back to default_kb.
  • The registry default_kb is reachable only via an explicit --kb @default sentinel, which overrides .kb/ discovery like a concrete --kb <name>.
  • .kb/ discovery remains the default destination when --kb is omitted and a .kb/ is found.
  • --kb default still resolves a knowledge base literally named "default"; only @default triggers the sentinel.
  • --kb @default with no configured default_kb fails with a distinct, clearly worded error.
  • The refusal for an undeterminable destination names the registered knowledge bases and points to --kb @default.
  • The @default sentinel value is defined once and shared between kb-add and capture-event.
  • The skill's documentation (the --kb argument, the KB-selection description, and the result-handling guidance) reflects the new precedence and drops the "falls back to default_kb" behavior.
  • New and changed behavior is covered by tests.

Metadata

Metadata

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions