Back to README | See also: Customization, Architecture
Knowledge Agent Template aggregates content from multiple sources into a unified, searchable knowledge base. A source is anything that produces files — GitHub repos, YouTube transcripts, and any custom source you build. Everything ends up as files in a sandbox, and the AI agent searches across all of them.
The system is designed to be extensible. Built-in source types handle GitHub and YouTube, but the architecture supports any source that can output files: Reddit threads, Slack exports, RSS feeds, custom APIs, static markdown — anything.
Sources are managed through the admin interface at /admin. From there you can:
- Add new sources (GitHub repositories, YouTube channels, or custom types)
- Edit existing source configurations
- Delete sources
- Trigger a sync to update the knowledge base
Sources can also be listed programmatically via the SDK:
const sources = await savoir.client.getSources()Sources are stored in SQLite via NuxtHub. The schema:
export const sources = sqliteTable('sources', {
id: text('id').primaryKey(),
type: text('type', { enum: ['github', 'youtube'] }).notNull(),
label: text('label').notNull(),
basePath: text('base_path').default('/docs'),
// GitHub fields
repo: text('repo'),
branch: text('branch'),
contentPath: text('content_path'),
outputPath: text('output_path'),
readmeOnly: integer('readme_only', { mode: 'boolean' }),
// YouTube fields
channelId: text('channel_id'),
handle: text('handle'),
maxVideos: integer('max_videos').default(50),
createdAt: integer('created_at', { mode: 'timestamp' }),
updatedAt: integer('updated_at', { mode: 'timestamp' }),
})Fetches Markdown documentation from GitHub repositories.
| Field | Type | Description |
|---|---|---|
id |
string |
Unique identifier |
label |
string |
Display name |
repo |
string |
GitHub repository (owner/repo) |
branch |
string? |
Branch to fetch from (default: main) |
contentPath |
string? |
Path to content directory (default: docs) |
outputPath |
string? |
Output directory in snapshot (default: id) |
basePath |
string? |
URL base path for this source (default: /docs) |
readmeOnly |
boolean? |
Only fetch README.md (default: false) |
additionalSyncs |
array? |
Extra repos to sync into the same source |
Fetches video transcripts from YouTube channels.
| Field | Type | Description |
|---|---|---|
id |
string |
Unique identifier |
label |
string |
Display name |
channelId |
string |
YouTube channel ID |
handle |
string? |
YouTube handle (e.g. @TheAlexLichter) |
maxVideos |
number? |
Maximum videos to fetch (default: 50) |
Content syncing is triggered from the admin interface. You can also trigger it programmatically via the SDK:
// Sync all sources
await savoir.client.sync()
// Sync a specific source
await savoir.client.syncSource('my-docs')- A Vercel Sandbox is created from the latest snapshot
- All source repositories are cloned/updated
- Changes are pushed to the snapshot repository
- A new sandbox snapshot is taken for instant startup
This runs as a durable Vercel Workflow with automatic retries. See Architecture > Sandbox System for more details on how snapshots and sandboxes work.
The system tracks when sources were last synced:
lastSyncAttimestamp is stored in KV after each successful syncGET /api/sourcesreturns thelastSyncAtvalue- The admin interface shows a reminder if the last sync was more than 7 days ago
During sync, only documentation-relevant files are kept in the snapshot. All other files are discarded.
| Extension | Handling |
|---|---|
.md |
Preserved as-is |
.mdx |
Preserved (treated as Markdown) |
.yml / .yaml |
Preserved |
.json |
Preserved |
| All other types | Deleted during sync |
Source code files (.ts, .js, .vue, etc.), images, binaries, and any file not in the list above are automatically removed after cloning. Only the supported types end up in the snapshot repository.
node_modules/- Empty directories (cleaned up after filtering)
The snapshot repository contains all aggregated content:
{NUXT_GITHUB_SNAPSHOT_REPO}/
├── docs/
│ ├── my-framework/
│ │ ├── getting-started/
│ │ └── api/
│ ├── my-library/
│ └── ...
└── youtube/
└── my-channel/
Configure via environment variable:
NUXT_GITHUB_SNAPSHOT_REPO=my-org/content-snapshot