# From the user's own files to a live, answering agent

The end-to-end path when someone says "train this on my documents and put a
chat on my website". Every step below has a tool; none of it needs the hub UI
except buying an allowance pack and reading an agent's API key.

Do the steps in order. The one rule that matters: **never build on content you
have not verified is actually in there.** An agent published over a failed
ingest answers confidently from nothing.

## 1. Agree the file list, and what it will cost

Ingest spends real money against a monthly allowance. Before uploading
anything:

- `awesomate_knowledge_status` — plan, consent, and the month-to-date usage
  against the included quota. If `upgrade_required`, relay it and stop.
- List the candidate files for the user and get an explicit yes on the set.
  Say roughly what it will consume: documents bill as pages (~1 page per
  100 KB), audio and video as media-hours.
- If the allowance is exhausted, a call returns `pack_required`: **nothing was
  ingested and nothing was bought**. State the price (1 credit = $100) and let
  them buy it in the hub. Never imply you purchased anything.

Media is far more expensive than documents. A folder of PDFs is cheap; three
hours of video is not. Say so before, not after.

## 2. Upload, one file at a time

`awesomate_knowledge_upload { path, title? }` — a LOCAL path on their machine.
The MCP server reads the file off disk and streams it to the hub, so the file
contents never pass through the conversation and a 100 MB PDF costs no context.

- One call per file. Report progress as you go; never loop silently through
  twenty files.
- 100 MB per file. Bigger media goes through the hub at Knowledge → Sources.
- A folder path is rejected on purpose — list it, agree the files, then upload.
- Public URLs and whole sitemaps stay on `awesomate_knowledge_sources`
  `{action:'add'}`. Use that for their website; use upload for their disk.
- **Files already in their File Manager** (the n8n file system at hub
  Files — `public/`, `private/`, `temp/`, where their workflows write
  outputs): `awesomate_knowledge_upload {fromFilesPath: 'public/reports/x.md'}`
  for one file, or a folder rule with `awesomate_knowledge_sources
  {action:'sync_rule_create', pathPrefix, visibility, collectionIds, sheetKind?}` so
  every file a workflow drops there is ingested automatically (uploads sync
  at once, everything else on a 15-minute sweep). Spreadsheets in the folder
  are added only when the rule has `sheetKind` (the same choice as below);
  without it each one is skipped and `sync_state` says why. A `public/` folder being
  web-served does NOT make its knowledge sources public — say which
  visibility the rule should use, default private.
- **What parses:** pdf, md, txt (documents); csv, tsv, xls, xlsx, xlsm, json (spreadsheets);
  audio, video, images.
- **A spreadsheet needs `kind`**, and the tool refuses one without it before
  uploading anything: `kind:'sheet'` makes each row a searchable, citable
  record (price lists, fee tables, timetables, anything a customer asks
  about); `kind:'data-import'` builds a dataset for totals and trends (job or
  invoice history), read with `awesomate_knowledge_data`. Ask the user when it
  is not obvious; never guess. A spreadsheet in the File Manager takes the
  same `kind`: `awesomate_knowledge_upload {fromFilesPath, kind}`.
- **Titles.** An uploaded source is titled from its filename, slugified:
  spaces, punctuation and non-ASCII letters become `_`
  ("Kōwhai Dance — Questions parents ask us" lands as
  `K_whai_Dance_Questions_parents_ask_us`), even when you pass `title`. Fix it
  with `awesomate_knowledge_sources {action:'rename', sourceId, title}` once the
  job has succeeded. The library shows the new title at once; search hits and
  citations can keep the old one for a while, so check with a search before
  telling the user citations have changed. Word/PowerPoint/RTF/EPUB/HTML files are refused with a
  workaround — export to PDF or save as .md/.txt; a web page goes in by URL.

## 3. Wait for ingestion, and check it actually succeeded

`awesomate_knowledge_sources {action:'jobs'}` until the job reports
`succeeded`. Transcription of audio/video takes minutes, not seconds.

**The JOBS list is the authority on whether an upload worked — not
`failed_sources`, and not the source list.** A job that fails during document
processing never creates a source row, so `{action:'summary'}` still reports
`failed_sources: 0` and the library total is unchanged. Measured live on
2026-09-01: an upload failed with a platform error while summary read
`total: 75, failed_sources: 0`. On current platforms the summary now carries
`failed_jobs_7d` + `last_failed_job_at` for exactly this reason — a nonzero
value means uploads failed that no other count shows; the field is absent on
older platforms, so its absence proves nothing.

So: read the job's `status` AND its `error`, and relay the error text to the
user. Real ones seen in the wild, all platform-side rather than anything wrong
with their file:

- `new row violates row-level security policy for table "documents"` — a
  platform bug in markdown/text ingestion, fixed 2026-09-02. A failed job wrote
  nothing, so once support confirms the fix is deployed the same file can
  simply be re-uploaded.
- `transcription_not_configured: DEEPGRAM_API_KEY is not set` — audio/video
  cannot be transcribed on that deployment yet.

None of these are fixable by the user. Say plainly that the upload reached
Awesomate and failed on our side, and raise it with support (awesomate-support
skill) rather than blindly re-uploading and spending allowance again.

Then `{action:'summary'}` for the whole-library counts once the job has
actually succeeded.

## 4. Probe the content before building anything on it

`awesomate_knowledge_ask` with five real questions the user cares about, and
**one question you know the content cannot answer**. A base that answers the
fifth is not grounded, and that is the single most valuable thing to catch
before a customer sees it. Show the citations.

## 5. Draft the agent

`awesomate_knowledge_agents {action:'create', goal, audience, collectionIds}` —
describe what the agent is for and the platform drafts instructions, scope,
tone and test questions from the account's own content. It saves as a
**private draft**; nothing is live and nothing is lost.

**Always scope a drafted agent to the right collection, and check its kinds.**
The draft's scope is the platform's guess, and it is often too wide: it can
come back limited to no collection at all, so it answers from everything in
the library (seen live: an internal staff agent for one business, drafted
unscoped on an account with one collection per business, answered with
another business's door PIN and Wi-Fi password). When the account has
collections, ask which one(s) the agent is for and pass `collectionIds` with
`create` (or set them with `update` straight after). Also check `kinds`: PDFs
are stored as `book`, FAQ sets as `faq`, so a draft limited to `document`
skips them. The `create` result carries a `warning` when the new agent reads
no collection while the account has some, or when its kinds leave out
content the library holds; fix it before testing or publishing.

**`audience` is the retrieval ceiling, and it is enforced.** `public` for
anything customers or a website will talk to: the agent then answers from
sources marked public ONLY, whatever key runs it. `internal` for the team
(internal + public sources). `private` (the default) for the owner's own
tools. A source's own visibility is set at upload (`visibility` on
`awesomate_knowledge_upload` / `sources add`) or in the hub Library; a public
agent over a library with nothing marked public answers nothing — say so
before building, and get the owner to mark the customer-facing sources public
first. Widening an agent later is a publish, so choose now.

Then `{action:'test', agentId, message}` — free and unmetered, and the right
way to check behaviour. Test the awkward cases: something out of scope, a
pricing question, a complaint, an ambiguous question. Confirm the refusal
wording is what the business would actually want to say.

**The draft test is not what the public sees.** It ignores the agent's
audience, so a public-audience draft can quote internal or private sources
(seen live: a draft quoted an internal staff handbook's door code and Wi-Fi
password). After publishing a customer-facing agent, ask the same awkward
questions with `{action:'visitor_test', agentId, message}`: it runs the
PUBLISHED agent through the real public visitor path, which is what a website
visitor actually gets (the published agent refused the door code). It works on
every plan, and a question it cannot answer lands
in the owner's Questions inbox, so say so before using it.

Access policies (the customer groups an agent narrows to: a policy named
`segment:all` or `segment:<group>`) are `policies` / `policy_create` /
`policy_update` / `policy_delete` / `policy_suspend` / `policy_resume` on the
same tool; each change applies to live callers at once, so show the owner
what changes first. API keys stay hub UI only (Knowledge, Agents): a key is
shown once there and never passes through a tool.

## 6. Publish only with explicit approval

`{action:'publish', agentId}` makes it live **immediately for every key bound
to the agent**. Ask first, publish, then read the version back and confirm.

`{action:'suspend', agentId}` stops it answering on every key at once, and
`{action:'resume', agentId}` starts it again on its last published version.
Both change what live callers get straight away: owner's approval first.

## 7. Put it where their customers are

Two destinations. Ask which they want; do not assume.

**A chat on their existing website — via n8n.** This is usually the right
answer, and the plumbing is already provisioned. See
`references/n8n-connection.md` §3: they install the **Verified Content Chat
Agent** card from the hub Library (`/templates`); it runs the agent they
published, publishes on install, and the Chat URL goes into the embed
snippet. Only when they want their OWN agent node (extra tools, a different
model) build it with the awesomate-n8n skill instead — Chat Trigger -> AI
Agent with the `knowledge_answer` library tool -> Respond — and read
`awesomate-n8n/references/ai-agents.md` before designing the agent node.

**A standalone page or app — via the app builder.** When they want a hosted
"ask our docs" page rather than a widget on an existing site, use the
awesomate-app-builder skill. A Node app calls the agent server-side so the key
stays out of the browser: get the agent's API key from the hub (shown once),
store it with the awesomate-credentials secret-drop flow, and never put it in
page JavaScript. A static site cannot hold a secret — if the page must call the
agent directly, route it through their n8n webhook instead.

## What to tell the user at the end

The library size, what it cost against their allowance, the agent's live
version, where it is reachable, and how to add more later. If anything failed
to ingest, say which files and why — a knowledge base with a silent hole is
worse than a smaller one they trust.
