Ivy Tendril Documentation Audit
Tendril's docs cover a real product in real depth, but the navigation is riddled with 404s, model identifiers don't survive cross-checking, the machine-readable indexes (llms.txt, llms-full.txt) serve a broken SPA shell instead of content, and the core "Promptwares" concept page is a stub.
1. llms.txt and llms-full.txt return the React SPA shell instead of machine-readable content (critical)
Location: https://tendril.ivy.app/llms.txt and https://tendril.ivy.app/llms-full.txt
Problem: llms-full.txt returns the literal string Loading... (the SPA shell never resolves to text). llms.txt returns the app shell, which then throws a runtime error rendered as the page body: Error: Unable to preload CSS for https://tendril.ivy.app/assets/MarkdownWidget-Bq0v7jer.css with a full JS stack trace (at Lazy, at Suspense, at AI (...index-BGmzYLKb.js...)). The intended llms.txt content is never delivered to a non-browser client.
Consequence: AI coding agents (Claude Code, Cursor, Copilot) that fetch llms.txt to index docs receive either Loading... or a JavaScript error stack. Tendril's entire pitch is "orchestrating coding agents," yet its own docs are unreadable to the agents it targets. Search/RAG ingestion pipelines that special-case llms.txt get nothing usable.
The fix: Serve llms.txt and llms-full.txt as static plain-text responses from the edge/CDN, bypassing the SPA. They must be a literal Content-Type: text/plain index of URLs and a concatenated Markdown bundle respectively — not a route into the React app.
2. Documented pages 404 with "Apps app not found" across whole sections, plus broken footer links (critical)
Location: Multiple. Verified 404s include:
/coding-agents/opencode/configuration/apps,/configuration/dashboard,/configuration/review,/configuration/drafts,/configuration/jobs,/configuration/icebox,/configuration/pull-requests,/configuration/recommendations,/configuration/trash/advanced/cli/integrations/openclaw/model-providers/berget-ai,/model-providers/z-ai,/model-providers/opper-ai,/model-providers/openrouter
Footer/cross-reference rot in addition:
- Evroc's "Next →" points at
/model-providers/z-ai(404) - Vercel's "Next →" points at a page literally named "Overview" that doesn't exist in the sidebar at all
- REST API's "← Previous" points at "Other," which also doesn't exist in the sidebar
- MCP page is missing its "Next" link entirely
Problem: Each broken URL renders the same body: Warning. Ouch! :| Apologies, the app you were looking for was not found. The pages are linked from the sidebar and from cross-references in working pages — e.g. /coding-agents/opencode is linked by every model-provider page ("Select OpenCode as your coding agent during onboarding"), and /advanced/cli is referenced by Getting Help ("See the CLI reference for details") and by ~10 commands in Troubleshooting (tendril plan doctor --fix, tendril db-migrate, tendril db-reset --force, tendril db-version, tendril plan add-repo, tendril plan cleanup, etc.).
Consequence: A developer following the sidebar to "Configuration > Apps" hits a dead page; following Getting Help to the CLI reference hits a dead page; following the prominently-advertised OpenCode agent hits a dead page; clicking "Next →" between provider pages lands on a 404 or on a phantom "Overview" page. The docs site advertises capabilities it does not document, including the CLI surface that troubleshooting depends on.
The fix: Either write the missing pages (especially /advanced/cli and /coding-agents/opencode — both are referenced as load-bearing canonical references) or remove the orphan sidebar/footer entries. The /configuration/* 404s appear to be a routing bug: those pages exist at /apps/* — fix the sidebar links or add redirects. Run a footer-link audit so "Next/Previous" never points at a page that isn't in the sidebar.
3. Codex and Copilot pages list model identifiers that the docs themselves never validate (critical)
Location: /coding-agents/codex and /coding-agents/copilot
Problem: The Codex Profiles table lists models gpt-5.4, gpt-5.4-mini, and gpt-5.3-codex. The Copilot Profiles table lists gpt-5.4 in all three rows, with only the "Effort" column changing. Neither page provides an "Available Models" section (as the Gemini page does), and there is no link to an upstream OpenAI/Copilot catalog showing these identifiers are real. The Copilot table also lists three rows for one model, even though the docs explicitly claim "Tendril maps effort levels to Copilot models" (plural).
Consequence: A user configuring codingAgent: codex and trying to override the model per profile (which the docs explicitly permit) has no in-doc way to verify which strings are accepted; if any of these are stale or placeholder, the agent fails at startup. Copilot users see three rows that look like three models but are really one model with three effort flags — so the docs' "maps effort levels to Copilot models" claim is misleading.
The fix: Add an "Available Models" section to each page (matching the Gemini page format), citing the upstream provider catalog. For Copilot, restructure the Profiles table to show that only the effort flag changes, not the model — or remove the Model column entirely and document the effort knob.
4. BasicAuth env-var section is a security-relevant tangle, and the inbox API silently runs open by default (critical)
Location: /configuration/setup (BasicAuth section) and /integrations/jam-dev
Problem: Setup says: "BasicAuth__JwtSecret — same base64 as BasicAuth__HashSecret if your host shows three secrets and you want to fill all three. Otherwise optional: it is a second name for the same value, not a second secret." Two paragraphs later: "You can use TENDRIL_AUTH_PASSWORD, TENDRIL_AUTH_USERNAME, and TENDRIL_AUTH_HASH_SECRET instead of the BasicAuth__* names if you prefer." Three different naming schemes (BasicAuth__*, TENDRIL_AUTH_*, and the bare api.apiKey for REST) are described in adjacent paragraphs with no single table mapping them. Meanwhile the Jam.dev integration page says: "Without an API key configured, the endpoint accepts unauthenticated requests."
Consequence: Operators wiring up production auth (1) can't tell whether JwtSecret is a real second secret or an alias — and "set the same value as the hashing secret" is genuinely bad guidance if JwtSecret is ever used as a JWT signing key, since hash secrets and signing secrets should not share material; (2) don't know which of the three naming schemes wins if more than one is set; (3) get no precedence rules. Combined with /api/inbox accepting unauthenticated POSTs when no key is set, a Tendril instance exposed beyond localhost without API key configuration is an open plan-creation endpoint.
The fix: Replace the prose with a single table: variable name | scope (UI / API / both) | required? | how to generate | precedence. Confirm whether JwtSecret is a true second secret (in which case sharing it with HashSecret is wrong) or pure alias (in which case remove the duplicate name). Add a "production checklist" — refuse to bind to non-localhost without auth configured, or at minimum print a loud warning on startup.
5. The Promptwares page — the canonical reference for Tendril's core abstraction — is a stub (significant)
Location: /concepts/promptwares
Problem: Promptwares are described on the introduction page as the central mechanism ("Tendril moves your Plan through a defined lifecycle using Promptwares: isolated, single-purpose agents"). Yet the page that defines them opens with a sentence fragment ("Promptwares are the self-improving agents behind each plan stage: each with its own prompt, tools, memory, and hooks."), lists three folder children in one line, gives a one-line "Core Jobs" table, and ends "Hooks (PowerShell)" with three bullets of two-word descriptions ("Setup, clone, env."; "Cleanup, post-process."; "Errors, notify, telemetry."). The promised sections (Custom Instructions, Execution Flow) are barely a paragraph each.
Consequence: Anyone trying to author a custom promptware — the whole point of the abstraction — gets no executable detail: no input/output contract for hooks, no example pre_execute.ps1, no Memory schema, no list of built-in tools beyond the allowedTools example. The release notes' references to "firmware" (also reused, confusingly, as a header name in the REST page) get no home page.
The fix: Expand each section to match the depth of /configuration/setup: full hook signatures with arguments and exit-code semantics, a worked example of a custom promptware folder, the full default allowedTools list, and an explicit definition of "Firmware" with its relationship to Program.md and customInstructions.
6. Two pages disagree on the canonical Job status enum (significant)
Location: /concepts/lifecycle vs /apps/jobs
Problem: /concepts/lifecycle enumerates Job statuses as Pending, Running, Completed, Failed, Timeout, Queued, Stopped, Blocked (8 states). /apps/jobs lists Running, Completed, Failed, Pending, … and trails off with an ellipsis. Neither page links to the other and neither claims authority.
Consequence: A user (or agent) building automation on top of the REST/MCP surface cannot tell which status values are valid. Filtering by state=Timeout or state=Queued may silently return nothing if those aren't real states; Stopped and Blocked aren't even mentioned in the Jobs app where you'd presumably observe them.
The fix: Pick one canonical list, publish it in a single "Job status reference" block, and link both pages to it. If the enum really has 8 states, the Jobs UI page should enumerate all 8; if some are internal-only, mark them as such.
7. Verification names disagree between Concepts and Setup (significant)
Location: /concepts/lifecycle vs /configuration/setup
Problem: Lifecycle describes Verification steps as Build, Format, Tests (plural). Setup's "Verifications" table lists the canonical built-ins as Build, Format, Test (singular), Lint, CheckResult. Lifecycle doesn't mention Lint or CheckResult at all, yet the Tutorial's example config requires CheckResult as a verification.
Consequence: Users following Lifecycle to scaffold a verifications: block use the name Tests and get an unknown-verification error. They also miss Lint and CheckResult entirely. For agents parsing the docs to suggest YAML, this is exactly the silent contradiction that produces broken configs.
The fix: Standardize on the Setup page's enum (Build, Format, Test, Lint, CheckResult) everywhere. Update Lifecycle to reference Setup's list rather than restating it.
8. MCP documents 4 recommendation states; REST documents none, and has no Jobs endpoints either (significant)
Location: /advanced/mcp vs /advanced/rest
Problem: The MCP page documents recommendation state values as Pending, Accepted, AcceptedWithNotes, Declined, and says tendril_plan_rec_accept "Sets state to Accepted or AcceptedWithNotes if notes provided." The REST API page documents Plans, Update Field, Set Verification, and Inbox — but no recommendations endpoints at all, and no Jobs endpoints (the page openly says "Job status is reported via the CLI (not the REST API)"). The MCP page's "Parity" footer explicitly promises that "MCP tools, REST API, and CLI all operate on the same plan data and share the same validation logic."
Consequence: The parity promise is broken in two directions: a REST caller cannot list or accept recommendations at all, and cannot poll job status programmatically over HTTP — both are MCP/CLI-only. Any automation built against REST has to shell out to tendril job status or run an MCP client, neither of which the REST page mentions.
The fix: Either expose recommendations and job-status endpoints over REST to honor the parity claim, or replace the parity promise with an explicit "Surface coverage" table showing which operations are MCP-only, CLI-only, or REST-available.
9. Release notes are 12 versions behind the shipped product (significant)
Location: /release-notes/release-notes (and the site banner at the top of every page)
Problem: The site banner shows v1.0.51. The newest release note entry is 1.0.39 (2026-05-28). That's 12 patch versions of shipped product with no public changelog entry. The same 1.0.39 entry adds OpenCode as a "supported coding agent" — but /coding-agents/opencode 404s (see Issue 2). Release notes 1.0.35 also added OpenCode and a tendril update command; the latter isn't documented anywhere outside this changelog.
Consequence: Users on v1.0.51 cannot find out what changed in the last 12 builds. Features advertised in release notes (tendril update, OpenCode provider, tunnel support, Test Agent button) have no canonical reference page. For an "open-source local-first" product, missing changelog entries undermine the trust contract that updates won't break configs.
The fix: Backfill release notes from 1.0.40 through 1.0.51. Wire the changelog generation into the release pipeline so this can't drift again. Each release-note entry should link to the doc page for any new CLI subcommand or provider it mentions.
10. Tutorial uses HTTPS for localhost; Jam.dev integration uses HTTP (significant)
Location: /getting-started/tutorial vs /integrations/jam-dev (and /advanced/rest)
Problem: Tutorial: "This launches the web server at https://localhost:5010." REST API: "All endpoints are available at your Tendril server URL (default https://localhost:5010)." Jam.dev webhook example: "Configure jam.dev to POST to: http://localhost:5010/api/inbox." HTTP vs HTTPS contradiction on the same default port.
Consequence: A user configuring jam.dev to POST to the documented URL gets a connection error (wrong scheme) or a TLS handshake failure. Worse, agents/scripts that follow the REST API page's HTTPS but the integration page's HTTP example will pick whichever they saw last and silently fail.
The fix: Pick the scheme Tendril actually serves on by default, document it once, and propagate. If Tendril serves both, document the redirect/upgrade behavior explicitly. The Jam.dev example must match what tendril actually binds to.
11. Gemini "default" model contradicts the balanced-profile mapping (significant)
Location: /coding-agents/gemini
Problem: "Available Models" lists gemini-2.5-pro — Full reasoning, 1M context (default). The Profiles table maps balanced to gemini-2.5-flash and deep to gemini-2.5-pro. So the "default model" annotation on Pro and the standard/balanced profile point at different models. The page also lists gemini-3-pro-preview and gemini-3-flash-preview with no availability/region notes, even though it shows them as values you can drop into a config override.
Consequence: A user reading "gemini-2.5-pro (default)" assumes their balanced runs go to Pro; in reality balanced fires Flash. Token-budget planning and cost estimates are off accordingly. A user who copies gemini-3-pro-preview into config based on the docs' suggestion may discover at runtime that the preview model isn't available in their region or for their key.
The fix: Remove the parenthetical "(default)" from gemini-2.5-pro, or clarify it means "the model chosen if you set no profile" (and verify which model that actually is). Add availability notes (region, allowlist, preview status) next to the Gemini 3 preview entries.
12. Project paths flip between Windows and Unix conventions with no cross-platform guidance (minor)
Location: Tutorial (D:\Repos\MyProject), Setup (D:\Repos\MyProject), Projects page (~/git/global-engine), REST API (D:\\Sessions\\Session1 with escaped backslashes inside a JSON body)
Problem: Four pages use three different path conventions: Windows literal, Unix tilde-relative, and JSON-escaped Windows. No page explains how Tendril resolves ~, whether forward slashes are accepted on Windows, or how to write paths inside JSON bodies on different OSes.
Consequence: A macOS user copying the Setup example pastes D:\Repos\MyProject and gets a path-not-found. A Windows user posting to /api/inbox may forget to double-escape backslashes and get a JSON parse error.
The fix: Add a short "Paths" subsection in Setup covering tilde expansion, OS-specific separators, and JSON escaping. Make examples consistent within each page (Tutorial should pick one OS, not both).
13. Three different navigation paths described for the same "Coding Agent" setting (minor)
Location: /model-providers/scaleway, /model-providers/cloudflare, /model-providers/nvidia, /model-providers/vercel, /model-providers/evroc say Settings > Configuration > Coding Agent. /coding-agents/claude-code, /coding-agents/codex, /coding-agents/copilot, /coding-agents/gemini say Settings > Coding Agent. /configuration/setup describes the Settings app as having a "General" section that sets the default coding agent.
Problem: Three different breadcrumbs for the same UI control.
Consequence: Users can't find the setting on first try; doc-following agents generate broken UI instructions.
The fix: Take a screenshot of the actual nav path and standardize on it across all three doc surfaces.
14. Icebox page is the only one written in marketing-flavored fluff (minor)
Location: /apps/icebox
Problem: "Maintaining an effective pipeline means not polluting the active Draft environment." "Unfreezes the context scope. The Plan inherits an identical metadata structure it formally retained, dumping it instantly into the active Draft ecosystem ready to invoke ExecutePlan bindings." Every neighbouring app page (Drafts, Review, Jobs) is plain and behavioral.
Consequence: Users can't tell what Icebox does mechanically — is it a state change, a folder move, a tag? The phrase "active Draft ecosystem" tells them nothing.
The fix: Rewrite to match the tone of Drafts/Review/Jobs — describe the actual state transition, the on-disk effect (TENDRIL_HOME/...), and the UI mechanics in 2–3 sentences. The Icebox state is already defined on /concepts/plans; cross-link to it.
15. "Firmware" appears as an HTTP header name with no definition (minor)
Location: /advanced/rest, Job Status section
Problem: The REST page says: "The <job-id> is provided as the TendrilJobId firmware header in the agent's prompt." The word "firmware" appears nowhere else in the REST docs; on the Promptwares page it's a prompt-template concept, not a header. A reader cannot tell whether "firmware header" means an HTTP header named TendrilJobId, a prompt-template slot named firmware, or both.
Consequence: A developer writing an HTTP client to inject the job id has no way to know what wire format the field takes. An agent parsing the docs for header names will not recognize "firmware" as a header type.
The fix: Replace with concrete wording: "Tendril injects TendrilJobId: <id> as a custom header in agent prompts (see Promptwares > Firmware)." Define "Firmware" once in the Promptwares page and link from here.
16. GitHub README and docs site disagree on what Tendril is (minor)
Location: /getting-started/introduction vs the GitHub README
Problem: Docs: "Tendril is an open source, local-first desktop application." GitHub README: "a web application built on Ivy Framework." Installation page directs users to dotnet tool install -g Ivy.Tendril (a CLI tool), which then tendril "launches the web server at https://localhost:5010" (a local web app).
Consequence: Users (and AI agents trying to understand the product surface) get three labels — desktop, web, CLI — without a unifying explanation. The release notes' references to "Photino GUI updater" and "desktop notifications" suggest there's also a packaged desktop shell. Users don't know which install method gives them which experience.
The fix: One paragraph in the Introduction: "Tendril is a .NET tool that runs a local web server you access in the browser; a packaged desktop wrapper is also available." Align the GitHub README to match.
17. Vercel page references a model identifier that appears nowhere else (minor)
Location: /model-providers/vercel
Problem: The Vercel config example uses anthropic/claude-sonnet-4.6 as a provider model key. Tendril's own Claude Code page lists only profile-level names (opus, sonnet, haiku) with no versioned identifiers, and no other page anywhere in the docs uses the 4.6 version string.
Consequence: A user copying the Vercel example into opencode.json has no way to verify whether claude-sonnet-4.6 is real or stale. If the released version is different, the routing rule silently no-ops.
The fix: Either pin example identifiers to a "verified-as-of" date with a link to the upstream model catalog, or use a clearly-marked placeholder (<provider/model-id>) instead of a specific string.
18. Evroc undercuts its own "Recommended Model" with a parenthetical (minor)
Location: /model-providers/evroc
Problem: Under "Recommended Model" the page reads: "moonshotai/Kimi-K2.5 (look for models with the Code tag)." A definitive recommendation that immediately tells the reader to go browse for something else.
Consequence: A user following the recommendation copies moonshotai/Kimi-K2.5 and may discover it isn't the right model for code; a user heeding the parenthetical doesn't know which model to pick. Both paths require a second lookup.
The fix: Either commit to a specific model name and remove the parenthetical, or remove the model name and tell users to filter the model catalog by the Code tag. Don't do both.
What they do well
- The plan-lifecycle model (
Plans,Promptwares,Jobs) is cleanly conceptualized and the per-app pages (Drafts/Review/Jobs/PRs) are tight and behavioural. - REST and MCP surfaces are documented side-by-side with matching tool/endpoint tables, even though the actual parity claim doesn't quite hold.
- The Troubleshooting page is genuinely diagnostic (symptom→fix table), not the usual "have you tried restarting" content.
Top 3 recommendations
- Fix the routing/SPA layer first.
llms.txt/llms-full.txtmust serve real text; the/configuration/*and other 404s must either redirect to the real/apps/*URLs or have their sidebar entries removed; footer "Next/Previous" links must point at pages that actually exist. This is one bug class blocking both agents and humans. - Write the missing
/advanced/cliand/coding-agents/opencodepages, and flesh out/concepts/promptwares. All three are load-bearing references that other pages already point at, and Promptwares is the central abstraction the whole product is sold on. - Tighten the security defaults and the parity claim. Replace the BasicAuth prose with a single env-var table, decide whether
/api/inboxshould ever accept anonymous POSTs, and either deliver REST parity for recommendations and jobs or stop promising it.