Viking Content Crawler
When to Use
Use this skill when the user wants to crawl content from websites and import it into Viking AI Search to build a searchable knowledge base. This covers news sites, blogs, academic papers, GitHub repositories, product documentation, RSS feeds, and similar web content sources.
The agent writes crawler code tailored to the target sites, outputs data in a fixed JSONL schema, and then hands off to the vs-item-onboarding skill for dataset creation and import.
Do not use this skill when:
- The user already has a local file ready to import (use
vs-item-onboarding directly).
- The user wants to import from a database (use
vs-item-onboarding directly with MySQL).
Version Check
Before starting this skill workflow, run vs version check --json. Continue only when status is up-to-date. If status is update-available, stop and tell the user to update the cloned vs repository, then run git pull --ff-only, bash ./scripts/install.sh, and bash ./scripts/install-skills.sh all --target auto --force (PowerShell: scripts/install.ps1 and scripts/install-skills.ps1). If the status is unknown, stop and report that the CLI version could not be verified.
Fixed Schema
All crawled records MUST conform to this schema. Every record is a flat JSON object written as one line in a JSONL file.
| Field |
Type |
Required |
Description |
id |
string |
yes |
Unique identifier. Use a source-native stable ID (e.g., arXiv ID, GitHub owner/repo, post slug) when available; otherwise derive a deterministic ID from title + author + published_at. Must be deterministic so re-crawling the same item produces the same ID. |
title |
string |
yes |
Content title (headline, post title, paper title, repo name, doc page title). |
summary |
string |
yes |
Short abstract or description (100-500 characters recommended). |
content |
string |
yes |
Full text body with HTML stripped to plain text. For GitHub repos, concatenate README content. For PDF/DOC documents, extract the text content directly into this field. |
category |
string |
yes |
One of: news, blog, paper, github, docs, other. |
source |
string |
yes |
Human-readable source name, e.g. "Hacker News", "arXiv", "Viking Docs". |
author |
string |
no |
Author name(s); multiple authors separated by commas. |
published_at |
string |
no |
ISO 8601 datetime, e.g. "2026-07-16T10:30:00Z". Use crawl time if unavailable. |
tags |
array<string> |
no |
Tags, keywords, or topics. |
language |
string |
no |
ISO 639-1 code: "en", "zh", etc. |
source_url |
string |
no |
Canonical URL of the source page (the URL the record was crawled from). Must be a fully-qualified URL with scheme and host. |
metadata |
object |
no |
Structured key-value data. Must be flat (one level deep, no nested objects). Values must be scalar (string, number, boolean) — no arrays or objects inside. Only the standard keys listed below are allowed; do not add custom keys. All sources must use the same metadata schema. |
Example Record
{
"id": "viking-blog-introducing-viking-ai-search",
"title": "Introducing Viking AI Search",
"summary": "Viking AI Search is a new generation of hybrid search engine combining BM25 and vector search...",
"content": "Full article text with HTML removed and paragraphs separated by newlines...",
"category": "blog",
"source": "Viking Blog",
"author": "Jane Doe",
"published_at": "2026-07-15T08:00:00Z",
"tags": ["search", "vector database", "hybrid search"],
"language": "en",
"source_url": "https://viking.example.com/blog/introducing-viking-ai-search",
"metadata": {
"read_time": "8 min",
"word_count": 2340,
"views": 12580
}
}
Standard Metadata Fields
Only these keys are allowed in metadata. Do not add custom keys — every crawled record, regardless of source or category, must use exactly these keys when the data is available, and omit keys whose data is unavailable. This guarantees schema consistency across all crawl sources so downstream consumers (schema inference, search relevance tuning) see a uniform shape.
| Key |
Type |
Category |
Description |
read_time |
string |
content |
Estimated reading time, e.g. "8 min". |
word_count |
number |
content |
Word count of the article / document body. |
views |
number |
engagement |
View count or page view count. |
likes |
number |
engagement |
Like / upvote / thumbs-up count. |
comments |
number |
engagement |
Comment count. |
shares |
number |
engagement |
Share count. |
stars |
number |
repo / paper |
GitHub stars (for github category) or citation-equivalent metric. |
forks |
number |
repo |
GitHub fork count (for github category). |
citations |
number |
paper |
Citation count (for paper category). |
venue |
string |
paper |
Publication venue, e.g. "NeurIPS 2025", "arXiv". |
doi |
string |
paper |
Digital Object Identifier, e.g. "10.1234/abcde". |
Values must be flat scalars (string / number / boolean). No nested objects, no arrays. If a data point does not map to any standard key, omit it rather than inventing a new key.
Preconditions
vs CLI >= 0.2.0 is installed and authenticated (vs auth status and vs doctor succeed).
- The crawl target is reachable from the execution environment.
- A suitable runtime is available (Python 3.8+ with
requests and beautifulsoup4 recommended).
Commands
This skill delegates dataset creation and import to vs-item-onboarding. The crawler workflow itself uses:
| Stage |
Action |
Purpose |
| Crawl |
Run agent-written crawler script |
Fetch content and write JSONL |
| Onboard |
Invoke vs-item-onboarding skill |
Create dataset, infer schema, import data, optionally start sync |
| Schedule |
Set up cron/launchd wrapper |
For scheduled mode: periodically re-crawl and append new lines |
Workflow
Run in strict order.
Confirm crawl mode — resolve whether the user wants one-time crawl or scheduled recurring crawl. Only skip the question when the request contains an explicit, unambiguous signal (apply detection to whatever language the user is writing in):
- Explicit one-time: phrases carrying "once", "one-time", "just this time", or equivalent single-crawl semantics.
- Explicit scheduled: phrases carrying "daily", "scheduled", "keep updated", "auto-crawl", "sync", "incremental", or equivalent recurring semantics.
- If the request is neutral — e.g. "crawl X", bare "crawl", mentions target sites but says nothing about scheduling/once — you MUST ask the user to choose. The bare crawl verb is NOT a one-time signal; it is ambiguous. Never silently default to one-time.
Identify crawl targets and write the crawler. Based on the user's target sites, write a crawler script. The crawler MUST:
- Output records conforming to the Fixed Schema as JSONL (one record per line).
- Write output to a stable path:
/tmp/viking/crawler/<job-name>/items.jsonl.
- For scheduled mode: support incremental crawling — track the last crawl cursor (most recent
published_at or last seen item IDs) in /tmp/viking/crawler/<job-name>/state.json so subsequent runs only fetch new content.
- Deduplicate by
id within each run and against previous state.
- Strip HTML to plain text; never include raw HTML in
content.
- When encountering PDF, DOC, or other document links, download the document and extract its text content directly into the
content field. Use available libraries (e.g. PyPDF2/pypdf for PDF, python-docx for DOCX, beautifulsoup4 for HTML) to extract readable text. Do not store document links in records; put the extracted full text in content.
- Be polite: set a descriptive User-Agent, respect
robots.txt, add 1-3 second delays between requests, retry transient errors with backoff.
- Prefer structured sources (RSS/Atom feeds > sitemap.xml > official APIs > HTML scraping).
- Log per-item errors and continue; do not abort on single-page failures.
- Strictly follow the Fixed Schema defined above — the same field names, types, and
metadata key set, regardless of the source. Do not add source-specific top-level fields or metadata keys. Print a summary to stdout: crawled count, new count, output path.
Run the crawler to produce the initial JSONL file at /tmp/viking/crawler/<job-name>/items.jsonl.
Hand off to vs-item-onboarding. Invoke the vs-item-onboarding skill with the following context:
- Source type: JSONL file
- File path:
/tmp/viking/crawler/<job-name>/items.jsonl
- Import mode: one-time import if the user chose one-time crawl; one-time import + ongoing incremental sync if the user chose scheduled crawl.
- App creation: required — the user wants both a dataset AND an application so the crawled content is immediately searchable. Tell
vs-item-onboarding to run through app creation and dataset attachment (steps 12–13) rather than stopping after dataset creation.
- Schema confirmation: auto-confirm — the crawler outputs a fixed, well-defined schema (see Fixed Schema above). When
vs-item-onboarding reaches the Schema Confirmation step (step 7), automatically reply yes to proceed without surfacing the confirmation prompt to the user. Only surface it if the backend returns warnings that indicate actual schema problems (e.g. missing PK BizAttr).
- Readiness: do NOT block waiting for Ready. After
vs-item-onboarding prints its hand-off block with console links, the workflow is complete. Do not run vs app wait-ready, do not poll for readiness, do not add any extra waiting steps. The user will check the console themselves.
- Let
vs-item-onboarding handle all subsequent steps (schema inference, confirmation, dataset creation, data write, app creation, dataset attach, optional sync start, console hand-off).
- Do NOT re-implement the onboarding steps yourself — defer entirely to
vs-item-onboarding.
(Scheduled mode only) Set up recurring crawl + sync. After vs-item-onboarding completes successfully and the dataset is created:
- The JSONL connector sync (set up by
vs-item-onboarding during step 4) already watches the JSONL file for new lines and imports them automatically. You do NOT need to separately configure vs connector init/run for the file — vs-item-onboarding handles this when it chooses the sync path.
- Create a wrapper script that:
- Runs the crawler in incremental mode (using
state.json to skip already-crawled content), appending new records to /tmp/viking/crawler/<job-name>/items.jsonl.
- Exits cleanly if no new records are found.
- Schedule the wrapper script using the platform-appropriate mechanism:
- cron (macOS/Linux): add a crontab entry. Recommended interval: 30 minutes to a few hours depending on how frequently the source updates.
- launchd (macOS): create a LaunchAgent plist with
StartInterval.
- Surface the schedule info, log file path, and how to stop/inspect the job in the hand-off.
Customer Environment Principle
- In customer environments, assume repository source code is unavailable.
- Execute tasks using only the installed skills, the packaged
vs CLI surface (--help, command output, observed runtime behavior), and explicit user-provided information.
- If the installed CLI behavior conflicts with a skill, trust the installed CLI behavior first.
Constraints
- Never write raw HTML into
content. Always strip to plain text.
- Never hardcode credentials in crawler code. Use environment variables for API keys.
- Always generate a stable
id. Use a source-native stable ID (e.g., arXiv ID, GitHub owner/repo, post slug) when available; otherwise derive a deterministic ID from title + author + published_at. Must be deterministic so re-crawling the same item produces the same ID.
- All datetime values MUST be ISO 8601 (e.g.,
"2026-07-16T10:30:00Z").
- All output MUST be valid JSONL: one JSON object per line, UTF-8 encoded.
- The
category field MUST use the predefined values (news, blog, paper, github, docs, other).
- Dataset creation and import MUST go through
vs-item-onboarding. Do not call vs dataset create, vs data write, etc. directly from this skill.
- For scheduled mode, incremental sync is handled by the JSONL file connector (configured by
vs-item-onboarding). The scheduled job only needs to run the crawler to append new lines to the JSONL file; the connector daemon picks up new lines automatically.
- Respect rate limits and robots.txt. Add polite delays between requests.
- Extract text from PDF/DOC documents. When encountering PDF, DOCX, or other document links, download the file and extract its text content directly into the
content field using appropriate libraries (e.g., pypdf for PDF, python-docx for DOCX). Do not store document links in output records.
- Auto-confirm Schema Confirmation during onboarding. The crawler produces records against the Fixed Schema defined above, which is stable and well-defined. When handing off to
vs-item-onboarding, instruct it to automatically reply yes at the Schema Confirmation step without surfacing the prompt to the user. Only pause and surface schema details if the backend inference returns genuine errors (e.g. missing primary-key BizAttr) that require user intervention.
- Never block waiting for dataset/app readiness. After
vs-item-onboarding completes its hand-off (printing console links + readiness reminder), end your turn. Do NOT run vs app wait-ready, vs dataset wait-ready, or any polling loop to wait for the Ready state. Readiness is an asynchronous backend process; tell the user to check the console links themselves.
metadata keys are fixed — only standard keys allowed. All records from all sources must use only the standard metadata keys listed in the Standard Metadata Fields table. Never invent custom keys. If a data point does not fit any standard key, omit it. This guarantees uniform schema across all crawl sources.
- Before executing any concrete
vs ... command, first consult vs-product-qa to verify the current command surface and required flags.
Recovery Hints
- Crawler returns zero records → verify target site/feed accessibility, check for rate limiting (HTTP 429), review error logs.
- Duplicate records appear → verify
id generation is deterministic (same item always produces the same ID).
- Content extraction produces garbled text → ensure HTTP response encoding is correctly detected.
- PDF text extraction fails or is garbled → try a different PDF library (e.g., switch from
pypdf to pdfplumber) or fall back to extracting abstract/metadata only.
- Sync is not picking up new lines → verify the JSONL connector daemon is running via
vs connector status --job <job>.
1---2name: vs-crawler3description: Crawl websites (news, blogs, papers, GitHub, product docs, RSS feeds) into a fixed-schema JSONL file, then create a dataset and a searchable application in Viking AI Search. Supports one-time crawl and scheduled recurring crawl with automatic incremental sync.4---56# Viking Content Crawler78## When to Use910Use this skill when the user wants to crawl content from websites and import it into Viking AI Search to build a searchable knowledge base. This covers news sites, blogs, academic papers, GitHub repositories, product documentation, RSS feeds, and similar web content sources.1112The agent writes crawler code tailored to the target sites, outputs data in a fixed JSONL schema, and then hands off to the `vs-item-onboarding` skill for dataset creation and import.1314Do not use this skill when:1516- The user already has a local file ready to import (use `vs-item-onboarding` directly).17- The user wants to import from a database (use `vs-item-onboarding` directly with MySQL).1819## Version Check2021Before starting this skill workflow, run `vs version check --json`. Continue only when `status` is `up-to-date`. If `status` is `update-available`, stop and tell the user to update the cloned `vs` repository, then run `git pull --ff-only`, `bash ./scripts/install.sh`, and `bash ./scripts/install-skills.sh all --target auto --force` (PowerShell: `scripts/install.ps1` and `scripts/install-skills.ps1`). If the status is `unknown`, stop and report that the CLI version could not be verified.2223## Fixed Schema2425All crawled records MUST conform to this schema. Every record is a flat JSON object written as one line in a JSONL file.2627| Field | Type | Required | Description |28|---|---|---|---|29| `id` | string | yes | Unique identifier. Use a source-native stable ID (e.g., arXiv ID, GitHub `owner/repo`, post slug) when available; otherwise derive a deterministic ID from title + author + published_at. Must be deterministic so re-crawling the same item produces the same ID. |30| `title` | string | yes | Content title (headline, post title, paper title, repo name, doc page title). |31| `summary` | string | yes | Short abstract or description (100-500 characters recommended). |32| `content` | string | yes | Full text body with HTML stripped to plain text. For GitHub repos, concatenate README content. For PDF/DOC documents, extract the text content directly into this field. |33| `category` | string | yes | One of: `news`, `blog`, `paper`, `github`, `docs`, `other`. |34| `source` | string | yes | Human-readable source name, e.g. `"Hacker News"`, `"arXiv"`, `"Viking Docs"`. |35| `author` | string | no | Author name(s); multiple authors separated by commas. |36| `published_at` | string | no | ISO 8601 datetime, e.g. `"2026-07-16T10:30:00Z"`. Use crawl time if unavailable. |37| `tags` | array\<string\> | no | Tags, keywords, or topics. |38| `language` | string | no | ISO 639-1 code: `"en"`, `"zh"`, etc. |39| `source_url` | string | no | Canonical URL of the source page (the URL the record was crawled from). Must be a fully-qualified URL with scheme and host. |40| `metadata` | object | no | Structured key-value data. Must be flat (one level deep, no nested objects). Values must be scalar (string, number, boolean) — no arrays or objects inside. **Only the standard keys listed below are allowed**; do not add custom keys. All sources must use the same metadata schema.4142### Example Record4344```json45{46 "id": "viking-blog-introducing-viking-ai-search",47 "title": "Introducing Viking AI Search",48 "summary": "Viking AI Search is a new generation of hybrid search engine combining BM25 and vector search...",49 "content": "Full article text with HTML removed and paragraphs separated by newlines...",50 "category": "blog",51 "source": "Viking Blog",52 "author": "Jane Doe",53 "published_at": "2026-07-15T08:00:00Z",54 "tags": ["search", "vector database", "hybrid search"],55 "language": "en",56 "source_url": "https://viking.example.com/blog/introducing-viking-ai-search",57 "metadata": {58 "read_time": "8 min",59 "word_count": 2340,60 "views": 1258061 }62}63```6465### Standard Metadata Fields6667**Only these keys are allowed in `metadata`.** Do not add custom keys — every crawled record, regardless of source or category, must use exactly these keys when the data is available, and omit keys whose data is unavailable. This guarantees schema consistency across all crawl sources so downstream consumers (schema inference, search relevance tuning) see a uniform shape.6869| Key | Type | Category | Description |70|---|---|---|---|71| `read_time` | string | content | Estimated reading time, e.g. `"8 min"`. |72| `word_count` | number | content | Word count of the article / document body. |73| `views` | number | engagement | View count or page view count. |74| `likes` | number | engagement | Like / upvote / thumbs-up count. |75| `comments` | number | engagement | Comment count. |76| `shares` | number | engagement | Share count. |77| `stars` | number | repo / paper | GitHub stars (for `github` category) or citation-equivalent metric. |78| `forks` | number | repo | GitHub fork count (for `github` category). |79| `citations` | number | paper | Citation count (for `paper` category). |80| `venue` | string | paper | Publication venue, e.g. `"NeurIPS 2025"`, `"arXiv"`. |81| `doi` | string | paper | Digital Object Identifier, e.g. `"10.1234/abcde"`. |8283Values must be flat scalars (string / number / boolean). No nested objects, no arrays. If a data point does not map to any standard key, omit it rather than inventing a new key.8485## Preconditions8687- `vs` CLI >= 0.2.0 is installed and authenticated (`vs auth status` and `vs doctor` succeed).88- The crawl target is reachable from the execution environment.89- A suitable runtime is available (Python 3.8+ with `requests` and `beautifulsoup4` recommended).9091## Commands9293This skill delegates dataset creation and import to `vs-item-onboarding`. The crawler workflow itself uses:9495| Stage | Action | Purpose |96|---|---|---|97| Crawl | Run agent-written crawler script | Fetch content and write JSONL |98| Onboard | Invoke `vs-item-onboarding` skill | Create dataset, infer schema, import data, optionally start sync |99| Schedule | Set up cron/launchd wrapper | For scheduled mode: periodically re-crawl and append new lines |100101## Workflow102103Run in strict order.1041051. **Confirm crawl mode** — resolve whether the user wants one-time crawl or scheduled recurring crawl. **Only skip the question when the request contains an explicit, unambiguous signal** (apply detection to whatever language the user is writing in):106 - **Explicit one-time**: phrases carrying "once", "one-time", "just this time", or equivalent single-crawl semantics.107 - **Explicit scheduled**: phrases carrying "daily", "scheduled", "keep updated", "auto-crawl", "sync", "incremental", or equivalent recurring semantics.108 - If the request is **neutral** — e.g. "crawl X", bare "crawl", mentions target sites but says nothing about scheduling/once — **you MUST ask the user to choose**. The bare crawl verb is NOT a one-time signal; it is ambiguous. **Never silently default to one-time.**1091102. **Identify crawl targets and write the crawler.** Based on the user's target sites, write a crawler script. The crawler MUST:111 - Output records conforming to the Fixed Schema as JSONL (one record per line).112 - Write output to a stable path: `/tmp/viking/crawler/<job-name>/items.jsonl`.113 - For scheduled mode: support incremental crawling — track the last crawl cursor (most recent `published_at` or last seen item IDs) in `/tmp/viking/crawler/<job-name>/state.json` so subsequent runs only fetch new content.114 - Deduplicate by `id` within each run and against previous state.115 - Strip HTML to plain text; never include raw HTML in `content`.116 - When encountering PDF, DOC, or other document links, download the document and extract its text content directly into the `content` field. Use available libraries (e.g. `PyPDF2`/`pypdf` for PDF, `python-docx` for DOCX, `beautifulsoup4` for HTML) to extract readable text. Do not store document links in records; put the extracted full text in `content`.117 - Be polite: set a descriptive User-Agent, respect `robots.txt`, add 1-3 second delays between requests, retry transient errors with backoff.118 - Prefer structured sources (RSS/Atom feeds > sitemap.xml > official APIs > HTML scraping).119 - Log per-item errors and continue; do not abort on single-page failures.120 - Strictly follow the Fixed Schema defined above — the same field names, types, and `metadata` key set, regardless of the source. Do not add source-specific top-level fields or metadata keys. Print a summary to stdout: crawled count, new count, output path.1211223. **Run the crawler** to produce the initial JSONL file at `/tmp/viking/crawler/<job-name>/items.jsonl`.1231244. **Hand off to vs-item-onboarding.** Invoke the `vs-item-onboarding` skill with the following context:125 - Source type: **JSONL file**126 - File path: `/tmp/viking/crawler/<job-name>/items.jsonl`127 - Import mode: **one-time import** if the user chose one-time crawl; **one-time import + ongoing incremental sync** if the user chose scheduled crawl.128 - App creation: **required** — the user wants both a dataset AND an application so the crawled content is immediately searchable. Tell `vs-item-onboarding` to run through app creation and dataset attachment (steps 12–13) rather than stopping after dataset creation.129 - Schema confirmation: **auto-confirm** — the crawler outputs a fixed, well-defined schema (see Fixed Schema above). When `vs-item-onboarding` reaches the Schema Confirmation step (step 7), automatically reply `yes` to proceed without surfacing the confirmation prompt to the user. Only surface it if the backend returns warnings that indicate actual schema problems (e.g. missing PK BizAttr).130 - Readiness: **do NOT block waiting for Ready.** After `vs-item-onboarding` prints its hand-off block with console links, the workflow is complete. Do not run `vs app wait-ready`, do not poll for readiness, do not add any extra waiting steps. The user will check the console themselves.131 - Let `vs-item-onboarding` handle all subsequent steps (schema inference, confirmation, dataset creation, data write, app creation, dataset attach, optional sync start, console hand-off).132 - Do NOT re-implement the onboarding steps yourself — defer entirely to `vs-item-onboarding`.1331345. **(Scheduled mode only) Set up recurring crawl + sync.** After `vs-item-onboarding` completes successfully and the dataset is created:135 - The JSONL connector sync (set up by `vs-item-onboarding` during step 4) already watches the JSONL file for new lines and imports them automatically. You do NOT need to separately configure `vs connector init/run` for the file — `vs-item-onboarding` handles this when it chooses the sync path.136 - Create a wrapper script that:137 1. Runs the crawler in incremental mode (using `state.json` to skip already-crawled content), appending new records to `/tmp/viking/crawler/<job-name>/items.jsonl`.138 2. Exits cleanly if no new records are found.139 - Schedule the wrapper script using the platform-appropriate mechanism:140 - **cron** (macOS/Linux): add a crontab entry. Recommended interval: 30 minutes to a few hours depending on how frequently the source updates.141 - **launchd** (macOS): create a LaunchAgent plist with `StartInterval`.142 - Surface the schedule info, log file path, and how to stop/inspect the job in the hand-off.143144## Customer Environment Principle145146- In customer environments, assume repository source code is unavailable.147- Execute tasks using only the installed skills, the packaged `vs` CLI surface (`--help`, command output, observed runtime behavior), and explicit user-provided information.148- If the installed CLI behavior conflicts with a skill, trust the installed CLI behavior first.149150## Constraints1511521. **Never write raw HTML into `content`.** Always strip to plain text.1532. **Never hardcode credentials** in crawler code. Use environment variables for API keys.1543. **Always generate a stable `id`.** Use a source-native stable ID (e.g., arXiv ID, GitHub `owner/repo`, post slug) when available; otherwise derive a deterministic ID from `title` + `author` + `published_at`. Must be deterministic so re-crawling the same item produces the same ID.1554. **All datetime values MUST be ISO 8601** (e.g., `"2026-07-16T10:30:00Z"`).1565. **All output MUST be valid JSONL**: one JSON object per line, UTF-8 encoded.1576. **The `category` field MUST use the predefined values** (`news`, `blog`, `paper`, `github`, `docs`, `other`).1587. **Dataset creation and import MUST go through `vs-item-onboarding`.** Do not call `vs dataset create`, `vs data write`, etc. directly from this skill.1598. **For scheduled mode, incremental sync is handled by the JSONL file connector** (configured by `vs-item-onboarding`). The scheduled job only needs to run the crawler to append new lines to the JSONL file; the connector daemon picks up new lines automatically.1609. **Respect rate limits and robots.txt.** Add polite delays between requests.16110. **Extract text from PDF/DOC documents.** When encountering PDF, DOCX, or other document links, download the file and extract its text content directly into the `content` field using appropriate libraries (e.g., `pypdf` for PDF, `python-docx` for DOCX). Do not store document links in output records.16211. **Auto-confirm Schema Confirmation during onboarding.** The crawler produces records against the Fixed Schema defined above, which is stable and well-defined. When handing off to `vs-item-onboarding`, instruct it to automatically reply `yes` at the Schema Confirmation step without surfacing the prompt to the user. Only pause and surface schema details if the backend inference returns genuine errors (e.g. missing primary-key BizAttr) that require user intervention.16312. **Never block waiting for dataset/app readiness.** After `vs-item-onboarding` completes its hand-off (printing console links + readiness reminder), end your turn. Do NOT run `vs app wait-ready`, `vs dataset wait-ready`, or any polling loop to wait for the Ready state. Readiness is an asynchronous backend process; tell the user to check the console links themselves.16413. **`metadata` keys are fixed — only standard keys allowed.** All records from all sources must use only the standard `metadata` keys listed in the Standard Metadata Fields table. Never invent custom keys. If a data point does not fit any standard key, omit it. This guarantees uniform schema across all crawl sources.16514. Before executing any concrete `vs ...` command, first consult `vs-product-qa` to verify the current command surface and required flags.166167## Recovery Hints168169- Crawler returns zero records → verify target site/feed accessibility, check for rate limiting (HTTP 429), review error logs.170- Duplicate records appear → verify `id` generation is deterministic (same item always produces the same ID).171- Content extraction produces garbled text → ensure HTTP response encoding is correctly detected.172- PDF text extraction fails or is garbled → try a different PDF library (e.g., switch from `pypdf` to `pdfplumber`) or fall back to extracting abstract/metadata only.173- Sync is not picking up new lines → verify the JSONL connector daemon is running via `vs connector status --job <job>`.