Files
WeKnora/cli/AGENTS.md
T
nullkey 35c79281c8 feat(cli): doc view + unlink (fill v0.3 design-gap audit)
Final design-pass audit on the v0.3 surface flagged two real gaps.

(A) doc view <id> was missing. Every other resource subtree exposes
a view verb (kb view, session view) for inspecting a single record,
but doc — which has the richest metadata of the three (title, file
name, type, size, parse_status, embedding_model, processed_at,
error_message) — had no single-doc surface. Users wanting one
doc's metadata had to `doc list | grep`.

Implementation mirrors kb view: narrow ViewService(GetKnowledge)
interface, --json envelope path, human KEY: VALUE renderer. Optional
fields are omitted rather than rendered as "-" so the panel is
dense. Tested: human renderer, title fallback when FileName empty,
omit-empty contract, JSON envelope shape, 404 classification.

(B) link had no counterpart. Once .weknora/project.yaml is written,
the only way to clear it was `rm` by hand. Both vercel and netlify
ship `unlink` as a top-level verb; not having one was a
discoverability gap. Top-level rather than `link --clear` follows
the verb-noun convention of the rest of the surface — the verb
stands alone and the operation isn't parameterised.

unlink walks up from cwd via projectlink.Discover (the same
parent-chain logic Factory.ResolveKB uses on the read side), so a
user in a subdirectory of a linked project can unlink without
cd-ing up. Errors with input.invalid_argument when no link is
found anywhere in the chain. Idempotent under racy concurrent
removal: os.ErrNotExist on os.Remove falls through to a Success
envelope since the post-condition holds either way.

projectlink package gained Remove() alongside Save / Load /
Discover so unlink doesn't reimplement the idempotent-remove
pattern inline.

Top-level registration in cmd/root.go, alongside link.
cli/AGENTS.md verb canon line adds unlink to the locally-introduced
list. cli/CHANGELOG.md gains an Added entry for each.

5 unit tests for view + 4 for unlink (cwd / walk-up / no-link
error / JSON envelope). Full suite green.

Intentionally deferred:
- session edit (rename a session): sessions auto-name from the
  first prompt; polish rather than a gap.
- link --clear as an alternative to unlink: top-level unlink is
  the documented form; aliases would just multiply the surface.
2026-05-14 10:57:17 +08:00

11 KiB

Agent Integration Guide for weknora CLI

Scope. This file is an operational reference for LLM agents (Claude Code, Cursor, Codex, Aider, Gemini Coder, etc.) that invoke weknora on a user's behalf. It documents the wire shape, exit code, and behavioral conventions an agent integration relies on.

This is not a contributor guide. If you are an AI coding agent editing weknora's source, see the repo root README.md (and, if added later, a separate contributor AGENTS.md at the repo root).

weknora is designed to be agent-friendly: error messages, output format, and flag design follow conventions agents can rely on. Wire-contract breaking changes are flagged in their PR description and the corresponding weknora --version bump — agents should pin a known-good version and re-validate against --help output on upgrade.

The model: gh CLI as the human-side north star, lark-cli (larksuite) as the agent-affordance reference. The "Output contract" and "Behavioral rules" sections below are the self-contained specification of that decision; everything an integrator needs is in this document.


Output contract

Streams

  • stdout is the data channel: JSON envelope (with --json) or human-formatted output.
  • stderr is logs / progress / warnings / agent guidance footnotes. Never parse stderr for data.

A non-empty stderr does not mean failure — read the exit code instead.

JSON envelope

When --json is set, stdout contains exactly one envelope:

{
  "ok": true,                 // false on failure; check this first
  "data": { /* command-specific payload */ },
  "error": { "code": "...", "message": "...", "hint": "..." },  // iff ok=false
  "_meta": { "request_id": "...", "kb_id": "..." },             // optional
  "risk": { "level": "high-risk-write", "action": "..." },      // write commands
  "dry_run": false                                              // true on --dry-run
}

This snippet is illustrative. Fields are added (never renamed or repurposed) within a minor version, and agents must not error on unknown keys. The authoritative envelope shape lives in cli/internal/format/envelope.go.

Error codes (closed registry)

error.code is a namespace.snake_case string from a closed registry in cli/internal/cmdutil/errors.go AllCodes(). An acceptance test enforces that every code referenced in cli/cmd/ is registered.

Categories: auth.* / resource.* / input.* / server.* / network.* / local.* / mcp.*.

error.hint provides a deterministic next-step hint agents can follow without natural-language parsing.

Exit codes

Code Meaning Agent action
0 Success Continue
1 Typed error (see envelope.error.code) Read code, decide retry/abort
2 Flag/argument validation error Re-check weknora <command> --help
10 Confirmation required for high-risk write Ask the human, retry with -y only after explicit approval
130 Cancelled (SIGINT / Ctrl-C) Stop, do not retry

The exit-10 protocol mirrors lark-cli's (source) "high-risk write requires confirmation" model. Never bypass exit 10 by auto-passing -y without explicit user permission.


Command surface

Discover the command tree the same way human users do:

weknora --help                       # top-level
weknora kb --help                    # subtree
weknora kb delete --help             # single command flags

The command tree follows <noun> <verb> (gh style). Verbs are:

Verb Semantics Example
list Multi-resource read kb list
view Single-resource read kb view <id>
create Create resource kb create --name X
edit Partial update (only sent fields change) kb edit <id> --description X
delete Destructive remove (KB itself) kb delete <id> -y
empty Bulk-delete contents, preserve container kb empty <id> -y
upload Bulk write content doc upload <file>
download Stream resource to disk doc download <id> -O file
pin / unpin Toggle "pinned" state (idempotent) kb pin <id>
use Switch active selection context use <name>
add / remove Manage local config entries context add staging --host ...

auth subtree: login / logout / list / status / refresh. Mirrors gh's auth login / logout / status / switch / list-style surface; weknora uses context use instead of auth switch because contexts carry host + tenant on top of credentials. auth refresh exchanges the stored refresh token for a new access + refresh pair (OAuth refresh-token grant); it errors with input.invalid_argument on API-key contexts which have no refresh semantic. Transparent 401 → refresh → retry is wired into the SDK transport (cli/internal/cmdutil/authretry.go) with singleflight de-dup, so most callers never need to invoke auth refresh explicitly.

search subtree: verb-noun (gh search code/repos/issues/... shape) — search chunks "<q>" --kb X for hybrid retrieval, search kb "<q>" / search docs "<q>" --kb X / search sessions "<q>" for client-side substring filtering on the listing endpoints.

session subtree: list / view / delete for chat session management. Sessions are the durable wrapper around chat invocations.

Top-level RAG / connectivity verbs: chat, search, api, link, auth, context, session, doctor, version.

doctor is a deliberate divergence from gh / lark (neither ships a health-check command); the precedent is flutter doctor / brew doctor. Kept because RAG deployments routinely break on misconfigured embeddings, storage backends, and credentials, and a structured 4-status envelope (ok/warn/fail/skip) is the cleanest agent-readable surface for that.


Behavioral rules

These mirror lark-cli's per-command Tips. Per-command guidance also appears in each command's --help output (under "AI agents:").

  1. Pass -y/--yes on kb delete / doc delete / auth logout when running headless. Without it, you will get exit 10. Never auto-add -y without the user's explicit go-ahead — the exit-10 protocol is the one explicit guard against unintended writes.
  2. Prefer typed commands over weknora api for known endpoints. Fallback to weknora api only when no typed command covers the call.
  3. For chat, prefer --no-stream --json in agent contexts. Streaming tokens to stdout makes JSON envelope parsing impossible.
  4. Honor --dry-run — when the user passes it, don't follow up with the real command unless explicitly asked. The dry-run envelope is the answer.
  5. link writes to the user's working directory — only run it when the user invoked it, not as a side effect of unrelated automation.

(Additional safety guidance — e.g. "do not switch context unless the user asked" — is documented in the affected command's own --help.)


Auto-detection of agent environments

weknora checks these environment variables (case-sensitive):

Env var Detected agent name
CLAUDECODE claude-code
CURSOR_AGENT cursor

When any is set, weknora --help appends the command's agent_help annotation. No behavior change — this is help-text rendering only.

To suppress detection (e.g. running weknora interactively from inside Claude Code without the agent footer): WEKNORA_NO_AGENT_AUTODETECT=1.

The omnibus --agent mode-switch flag that briefly existed in early v0.2 was removed: gh / kubectl / aws / docker / flyctl all decline this kind of flag, since per-command --json + TTY auto-detect cover the same ground without an extra global switch. Stripe's DetectAIAgent (the inspiration) only tags User-Agent for telemetry, never flips behavior; weknora now follows that narrower scope.


Architecture decisions

A handful of decisions are referenced inline in the source as ADR-N. They live here, alongside the contract they shape.

ADR-3 — gh CLI as the primary mainstream north star. When weknora's v0.0/v0.1 surface was audited against gh / kubectl / cargo / npm / git / docker / flyctl / vercel / supabase / brew, gh emerged as the closest fit for an opinionated noun-verb tool with a stable JSON envelope and an agent-aware error model. Documented deviations:

  • link (project-binding) borrows from vercel link / netlify link rather than gh's per-host config model — a <cwd>/.weknora/project.yaml walk-up matches how RAG users scope work to a specific knowledge base.
  • chat / search are domain-specific verbs gh has no analog for.
  • context use (kubectl idiom) instead of gh's auth switch — weknora's context bundles host + tenant + credential, which is more than gh's per-host account model.
  • doctor (flutter / brew idiom) instead of gh's status (which is an activity feed, different concept) — RAG deployments routinely break on misconfigured embeddings / storage / credentials, so a structured 4-status diagnostic is the agent-readable surface for that.

Verb canon (gh-canonical): list / view / create / edit / delete / upload / download / pin / unpin / use. Locally introduced for resource semantics gh lacks: empty (bulk-delete contents preserving the container), refresh (token), add / remove (context CRUD), link / unlink (project bind / unbind).

ADR-4 — Factory closures + narrow Service interfaces. cmdutil.Factory exposes four lazy closures (Config / Client / Prompter / Secrets) that commands may invoke, but each subcommand declares its own narrow Service interface for the SDK calls it actually makes. The production *sdk.Client satisfies these implicitly via duck typing; tests inject fakes. Splitting the boundaries this way means a subcommand's test can stand up an httptest.Server (or a hand-rolled struct) without standing up the full SDK, and the dependency graph of any one command is visible in one file.


Known limitations

The following classes of failure currently surface as error.code = "network.error" with context deadline exceeded rather than a precise typed code. A future release will introduce a precondition.* namespace (server returns HTTP 412 with a typed remediation body before opening the SSE / streaming response):

  • weknora chat when no chat model is configured for the active tenant
  • weknora search chunks when no retriever / vector store is configured
  • weknora doc upload when no storage engine is selected for the KB

Workaround until then: if a chat / search / upload call times out without producing a first-byte response, check the server's tenant configuration (LLM / vector store / storage engine) before retrying. A planned weknora doctor --server-config will probe these directly.


Reporting issues

If the CLI's behavior contradicts this document, that is a bug. File at https://github.com/Tencent/WeKnora/issues with:

  • The exact command line
  • weknora --version output
  • The envelope you got vs the envelope this document promises