Hi all ![]()
While building an Anna App, I went through most of the Developer Hub docs page by page and cross-checked every concrete API claim against what actually ships. Below are the issues I could confirm against the published packages.
## 1. Confirmed factual errors
These are cases where the documented behaviour is contradicted by the shipped CLI, SDK, schema, or official scaffold templates.
### 1.1 Plugin protocol: the run method is `invoke`, not `call`
- **Docs (Concepts / Architecture):** “Implements `describe` … and **`call`** (run a tool)”; “Runtime sends **`call`** as a single JSON-RPC line.”
- **Actual:** The JSON-RPC method is **`invoke`**. There is no `call` method anywhere. All three official templates handle `case “invoke”` (`templates/executa/{python,node,go}`), and the host issues it via `runner` (`invoke()` → `call(“invoke”, { tool, arguments })`). The protocol method set is `describe` / `invoke` / `health` / `initialize`.
- **Fix:** Replace `call` with `invoke` in both pages. A plugin that implements `call` will never be reached.
### 1.2 v2 `initialize` handshake uses snake_case on the wire, not camelCase
- **Docs (Lifecycle & Capability Negotiation):** request uses `clientInfo`, `capabilities`; response uses `serverInfo`; sampling caps shown as `capabilities.sampling.{modalities,maxTokensPerCall,maxCallsPerInvoke}` plus `fileTransport: true`.
- **Actual:** The host sends `HOST_INITIALIZE_PARAMS` with **snake_case** keys: `client_info`, `client_capabilities`, and the only camelCase key is `protocolVersion`. The capability set is `sampling / agent / storage / image / image.edit / upload` (each `{}`). There is **no** `fileTransport`, and the sampling sub-fields shown in the docs are not present. Official templates respond with `server_info` (snake_case).
- **Why it matters:** A plugin reading `params.clientInfo` / `params.capabilities` per the docs gets `undefined`.
- **Fix:** Use `client_info` / `client_capabilities` in the request example, the capability key set above, and `server_info` in the response example.
### 1.3 `health` status: templates return `“ok”`, which is not in the documented enum
- **Docs (Protocol / Lifecycle):** “`status` must be one of `ready`, `error`, `initializing`.”
- **Actual:** All three official standalone templates return `{“status”:“ok”}` (`python`, `node`, `go`). Several Quickstart pages also use `“ready”`. So the official artifacts disagree with each other and with this enum.
- **Fix:** Either add `ok` to the enum (and clarify it’s the recommended value), or fix the templates. Right now a plugin copied from a template returns a status the spec says is invalid.
### 1.4 The manifest `name` “required plugin identifier” model is outdated
- **Docs (Protocol / Common Pitfalls):** `name` is “Required: yes / the plugin identifier”; Pitfall asks you to make `tool_id` == describe `name` == archive `manifest.json` `name` all equal.
- **Actual:** Identity is the **server-minted `tool_id`**. The current standalone scaffold **omits the top-level `name`** and uses `display_name` only; the templates carry an explicit comment: *“The manifest no longer declares a top-level name; the plugin’s identity is the server-assigned tool_id … not a self-declared manifest name.”* The Binary Distribution and Publishing pages confirm the Agent **no longer reads a self-reported name** and “mismatches no longer matter.”
- **Nuance (so the fix is correct):** the app-bundled *minimal* scaffold still sets `name: “_TOOL_ID_”`, so a literal `name` isn’t *rejected* — it’s just no longer the identifier. The wrong claim is “name = the required identifier.”
- **Fix:** State that identity = minted `tool_id`; `name`/`display_name` do not participate in any join. Remove the “make all three names equal” guidance.
-–
## 2. CLI commands / package names that don’t exist as documented
These will fail the moment a reader copy-pastes them.
### 2.1 `executa dev` sampling flags are wrong
- **Docs (Sampling):** `anna-app executa dev ./my-plugin --sampling mock --sampling-unsupported-format`
- **Actual flags:** `–dir `, `–no-sampling`, `–mock-sampling `, `–app-slug`, `–sampling-account`. There is **no** `–sampling ` and **no** `–sampling-unsupported-format`; the target is `–dir`, not a positional arg.
- **Fix:** `anna-app executa dev --dir ./my-plugin --mock-sampling ./sampling-fixture.jsonl`. To exercise the unsupported-format path, put a downgraded response in the fixture (the python template ships `sampling-fixture.jsonl` as a reference).
### 2.2 `anna-app dev --llm ` does not exist
- **Docs (Local Dev with --llm):** `–llm real`, `–llm mock --mock-llm …`, `–llm off`.
- **Actual:** The LLM flags are `–no-llm`, `–mock-llm `, `–llm-account`, `–llm-app-slug`. There is no bare `–llm ` (the two `–llm…` options are `–llm-account` / `–llm-app-slug`).
- **Mapping:** `–llm real` → default (no flag); `–llm mock --mock-llm X` → just `–mock-llm X`; `–llm off` → `–no-llm`.
### 2.3 `–slug` default is not `manifest.tags[0]`
- **Docs (Local Development):** `–slug` “Default: from manifest `tags[0]`.”
- **Actual:** `deriveSlug` = `manifest.slug ?? manifest.name`. Unrelated to `tags`. (Concrete counter-example: an app with `slug: “avalon-v2”` and `tags[0] = “game”` would be mis-derived as `game` per the docs.)
- **Fix:** “Default: `manifest.slug`, falling back to `manifest.name`.”
### 2.4 “No `llm.complete` in dev — returns `not_implemented`” is outdated
- **Docs (Local Development):** “No `llm.complete` — the dispatcher returns `not_implemented` … use a dev.mocks entry.”
- **Actual:** dev has a full LLM bridge with three modes (off / mock / real). `–no-llm` returns `llm_disabled` (not `not_implemented`); `–mock-llm` serves a fixture; default goes real. The newer --llm/Agent pages already describe this.
- **Fix:** Replace with “use `–mock-llm `, or `–llm-account` for real.”
### 2.5 “Publishing happens entirely in the Console — there is no CLI” is outdated
- **Docs (Publishing an App):** “There is no zip upload, no CLI, and no separate submission portal.”
- **Actual:** CLI 0.1.28 has the full developer-side lifecycle: `apps push`, `apps cut `, `apps release `, `apps publish`, `apps submit-review`, `apps archive`, `apps unpublish`, `apps status` / `versions` / `grants`, plus the bundle upload pipeline (`bundle/init|file|finalize`). Only admin approve/reject is server-side.
- **Fix:** “Publishing can be done via the Developer Console **or** the `anna-app` CLI (`apps push/cut/release/publish/submit-review/archive`); only admin review (approve/reject) is server-side.”
### 2.6 Wrong package name in the test import
- **Docs (Recording & Replay):** `import { mountBundle } from “@anna/cli/test”;`
- **Actual:** the package is `@anna-ai/cli` (sub-export `@anna-ai/cli/test`). `@anna/cli/test` does not resolve — `-ai` is missing.
### 2.7 `@anna-ai/app-ui-sdk` is not a real package
- **Docs (App UI Host API):** `import { hostBridge } from “@anna-ai/app-ui-sdk”`
- **Actual:** there is no `app-ui-sdk`. The real SDK is `@anna-ai/app-runtime`, exporting `AnnaAppRuntime` (imported via the runtime URL `/static/anna-apps/_sdk/latest/index.js`). The `hostBridge.invoke(…)` shape here also disagrees with the `AnnaAppRuntime.connect()` shape used on the SDK page.
- **Fix:** standardize on `@anna-ai/app-runtime` + `AnnaAppRuntime`.
-–
## 3. Schema / SDK reference errors
### 3.1 `dispatcher_version` is `0.10.0`, not `0.4.0`
- **Docs (Host API vs Executa Tool):** “schema pinned at `dispatcher_version=0.4.0`.”
- **Actual:** `@anna-ai/app-schema@0.10.0` ships `dispatcher_version.txt = 0.10.0` (it tracks the app-schema version).
- **Fix:** Update to `0.10.0`, or drop the hard-coded number and say it tracks app-schema.
### 3.2 `host_api.storage` allowed values are `get`/`set`, not `read`/`write`
- **Docs (App UI Manifest):** host_api table lists `storage | read, write`.
- **Actual:** the storage namespace methods are `get` / `set` / `delete` / `list` (`host_api/methods.json`; the SDK is `anna.storage.get/set`). `read` / `write` are methods of the **`fs`** namespace. The docs appear to have copied the **permission verbs** (`storage.read` / `storage.write`, which belong to the root `permissions` field) into the **method-name** column.
- **Why it matters:** declaring `ui.host_api.storage: [“read”,“write”]` makes `anna.storage.get/set` fail the ACL with `permission_denied`.
- **Fix:** `ui.host_api.storage` values are `[“get”,“set”]` (and `delete`/`list`).
### 3.3 The Host API namespace tables omit `agent`
- **Docs (App UI Manifest + App UI Host API):** the namespace tables list `tools / chat / artifact / llm / fs / storage / prefs / window` — no `agent`.
- **Actual:** `host_api/methods.json` has a full `agent` namespace with **7 `session.*` methods** (`session.create/run/cancel/delete/history/list/refresh`). (Note: `complete` is `llm.complete`, not `agent.complete`.) This is how a UI app drives multi-turn AI runs from the iframe, so it really should be in the general tables. There’s a dedicated App-Side LLM & Agent page that documents `agent`, but the two general Host API tables don’t cross-reference it.
- **Fix:** add an `agent` row (with `session.*` methods + the matching `host_api.agent` ACL) to both tables, or at least a pointer to the dedicated page. (`image` and `upload` namespaces are also absent from the table.)
### 3.4 SSE `event` kind table doesn’t match `AnnaAppEvent.json`
- **Docs (App UI Windows):** lists `entry_payload` as an event kind and is missing several real ones.
- **Actual:** `AnnaAppEvent.json` (schema 0.10.0) defines exactly **11 kinds**: `artifact_appended`, `chat_message_from_app`, `close_view`, `geometry_changed`, `open_view`, `ping`, `rpc.stream`, `runtime_state_synced`, `status_changed`, `title_changed`, `window_focus_changed`.
- `entry_payload` is **not** a kind (it’s carried inside another kind’s payload; the SDK also exposes an `on(“entry_payload”)` callback, but that’s an SDK-layer name, not a wire kind).
- The table is **missing** `artifact_appended`, `chat_message_from_app`, `ping`, `rpc.stream`.
- **Fix:** align the table with the 11-kind enum; drop `entry_payload` from the wire-kind list.
### 3.5 SSE envelope field is `by_client_id`, not `client_id`
- **Docs (App UI Windows):** envelope example uses `“client_id”` (originating client, for echo suppression).
- **Actual:** `AnnaAppEvent.json` defines `by_client_id` (string|null).
### 3.6 iframe Agent session frames are OpenAI-style chunks, not `token`/`complete`/`delta`
- **Docs (Agent Sessions / App-Side LLM & Agent):** iframe streaming shown as `frame.event === “delta”` / `frame.text` (and older `token` / `complete`), described as “same frame shapes” as the plugin side.
- **Actual:** `@anna-ai/app-runtime@0.8.0` **does not normalize frames** — the SDK source contains no `token` / `complete` / `choices` / `delta` literals; it forwards the host frames verbatim. The real iframe frames are **OpenAI-style chunks**: text is in `choices[0].delta.content`, completion/usage in `choices[0].delta.task_complete.token_usage`, ending with `event_type: ‘stream_end’` → `[DONE]`.
- **Fix:** show iframe consumers reading `frame.choices?.[0]?.delta?.content`, or explicitly state the SDK does not normalize and the raw frames are OpenAI chunks. (This is an iframe-vs-iframe comparison, confirmed against both the shipped SDK and a running app — not a cross-surface mix-up.)
-–
## 4. Local-validation divergences (docs describe server rules; the CLI validator differs)
Flagging these because they bite developers running `anna-app validate` locally — the doc’s stated rule and the CLI’s actual regex/constant disagree, so “valid per the docs” can fail locally (or vice-versa).
### 4.1 slug regex — CLI rejects trailing hyphen, docs allow it
- **Docs (Listing Fields):** `^[a-z0-9][a-z0-9-]{2,79}$` (allows a trailing `-`).
- **CLI:** `SLUG_RE = /^[a-z0-9][a-z0-9-]{1,78}[a-z0-9]$/` — must end in `[a-z0-9]`, no trailing hyphen. So `my-app-` passes the documented regex but is rejected by `anna-app validate`.
- **Fix:** align the doc regex to the CLI form (or clarify which the server enforces).
### 4.2 storage error codes — docs disagree with the CLI `StorageBridge` constants
- **CLI `StorageBridge` defines:** `INVALID_REQUEST = -32020`, `NOT_GRANTED = -32021`, `NOT_FOUND = -32022`, `PRECONDITION_FAILED = -32023`, `UPSTREAM = -32026`.
- **Docs:** put `INVALID_REQUEST` at `-32028`, label `-32026` as `RATE_LIMITED`, and put `UPSTREAM_ERROR` at `-32029`. (`-32021/-32022/-32023` do match.)
- The image (`-32101…-32105`) and upload (`-32201…`) code tables, by contrast, match the CLI exactly — which suggests the storage table is genuinely off rather than a convention I’m misreading.
- **Fix:** reconcile the storage table with the shipped constants (or document the server-vs-client split if they really differ).
-–
## 5. Internal contradictions within the doc set
Independently verifiable just by reading the docs against each other (no baseline needed).
### 5.1 Binary Distribution page contradicts itself on `name`
The IMPORTANT box says the Agent “no longer reads a self-reported manifest name … mismatches no longer matter,” but the same page’s field table says `name` “must equal the `tool_id` you minted **and the name your binary returns from describe**.” Pick one (the IMPORTANT box matches the current identity model).
### 5.2 `host_api.storage` — Manifest page vs Host API page
The App UI Manifest page lists storage as `read, write`; the Host API page correctly lists `get/set/delete/list` and separates the permission verbs. The Host API page is the correct one (see §3.2).
### 5.3 Sampling error codes — Sampling page vs Common Pitfalls
- The Sampling page’s code table and the Common Pitfalls page assign **different codes to the same scenario**: e.g. “manifest missing `llm.sample`” is `-32008` on the Sampling page but `-32001` in Pitfall 8; “cumulative tokens exceeded” is `-32007` vs `-32005` in Pitfall 10.
- I can’t tell which set is correct (the sampling code values aren’t observable from the client baseline), but the two pages **contradict each other** and at least one is wrong. Pitfall 8 is also internally inconsistent: it pairs `-32001` with the message `“not_negotiated”`.
- **Fix:** make the two pages agree, and reconcile against the real Nexus code table.
Happy to file these individually as issues if that’s more useful — just let me know the preferred format. Thanks for the docs overall; most of the API surface was accurate and easy to follow. ![]()