Ingests an external data source into the Second Brain. Fetches content from Confluence, Google Docs, GitHub repositories, remote URLs, or any local file format supported by docling (DOCX, PPTX, XLSX, PDF, HTML, EPUB, images, Markdown, CSV, and more), converts non-markdown
Installs just this skill. Get the whole plugin for auto-invocation.
โก How it fires
How this skill gets triggered: by you, by Claude, or both.
Fires itselfClaude auto-loads it when your prompt matches the work.
You can call itInvoke it directly when you want it.
Slash command/learn
๐๏ธ Context preview
The summary Claude sees to decide when to auto-load this skill.
Ingests an external data source into the Second Brain. Fetches content from Confluence, Google Docs, GitHub repositories, remote URLs, or any local file format supported by docling (DOCX, PPTX, XLSX, PDF, HTML, EPUB, images, Markdown, CSV, and more), converts non-markdown
๐ Stats
Stars83
Forks8
LanguageHTML
LicenseMIT
๐ฆ Ships with bedrock
</> SKILL.md
learn.SKILL.md
---name: learn
description: >
Ingests an external data source into the Second Brain. Fetches content from
Confluence, Google Docs, GitHub repositories, remote URLs, or any local file format
supported by docling (DOCX, PPTX, XLSX, PDF, HTML, EPUB, images, Markdown, CSV, and more),
converts non-markdown formats to markdown via docling, runs the /graphify extraction pipeline,
and delegates entity persistence (including the graphify-output merge) to /bedrock:preserve.
Use when: "bedrock learn", "bedrock-learn", "learn", "ingest source", "import document", "/bedrock:learn",
or when the user provides a Confluence, Google Docs, or GitHub URL, a remote file URL, or
a local file path to incorporate into the vault.
user_invocable: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Skill, Agent, WebFetch, mcp__plugin_github_github__*, mcp__plugin_atlassian_atlassian__*
---# /bedrock:learn โ External Source Ingestion into the Second Brain
## Plugin Paths
Entity definitions and templates are in the plugin directory, not at the vault root.
Use the "Base directory for this skill" provided at invocation to resolve paths:
- Entity definitions: `<base_dir>/../../entities/`
- Templates: `<base_dir>/../../templates/{type}/_template.md`
- Plugin CLAUDE.md: `<base_dir>/../../CLAUDE.md` (already injected automatically into context)
Where `<base_dir>` is the path provided in "Base directory for this skill".
---
## Vault Resolution
Resolve which vault to learn. This skill can be invoked from any directory.
**Step 1 โ Parse `--vault` flag:**
Check if the input arguments include `--vault <name>`. If found, extract the vault name and remove it from the arguments (the remaining text is the source URL/path).
**Step 2 โ Resolve vault path:**
1. **If `--vault <name>` was provided:**
Read the vault registry at `<base_dir>/../../vaults.json`. Find the entry matching the name.
If not found: error โ "Vault `<name>` is not registered. Run `/bedrock:vaults` to see available vaults."
If found: set `VAULT_PATH` to the entry's `path` value. Store the resolved vault name as `VAULT_NAME`.
2. **If no `--vault` flag โ CWD detection:**
Read `<base_dir>/../../vaults.json`. Check if the current working directory is inside any registered vault path
(CWD starts with a registered vault's absolute path). If multiple match, use the longest path (most specific).
If found: set `VAULT_PATH` to the matching vault's `path`. Store its name as `VAULT_NAME`.
3. **If CWD detection fails โ default vault:**
From the registry, find the vault with `"default": true`.
If found: set `VAULT_PATH` to the default vault's `path`. Store its name as `VAULT_NAME`.
4. **If no resolution:**
Error โ "No vault resolved. Available vaults:" followed by the registry listing.
"Use `--vault <name>` to specify, or run `/bedrock:setup` to register a vault."
**Step 3 โ Validate vault path:**
```bash
test -d "<VAULT_PATH>" && echo "exists" || echo "missing"
```
If missing: error โ "Vault path `<VAULT_PATH>` does not exist on disk. Run `/bedrock:setup` to re-register."
**Step 4 โ Read vault config:**
```bash
cat <VAULT_PATH>/.bedrock/config.json 2>/dev/null
```
Extract `language` and other relevant fields for use in later phases.
**From this point forward, ALL vault file operations use `<VAULT_PATH>` as the root.**
- Graphify output: `<VAULT_PATH>/graphify-out/`
- When delegating to `/bedrock:preserve`, pass `--vault <VAULT_NAME>`
---
## Overview
This skill receives an external source (URL or local path), fetches its content to a temporary
directory, converts non-markdown files to markdown via docling, runs the `/graphify` extraction
pipeline on the tmp content, and delegates entity persistence (plus graphify-output merge) to
`/bedrock:preserve`.
**You are a fetcher and orchestrator agent.** Your job is to:
1. Ensure docling is installed (auto-install if missing)
2. Classify the input and fetch content to `/tmp`
3. Convert fetched files to markdown via docling (when applicable)
4. Invoke `/graphify` to extract a knowledge graph into a per-run temp directory
5. Delegate graph merge and entity writes to `/bedrock:preserve`
6. Clean up temporary files
You do NOT classify entities, create vault files, write to the vault directly, or merge graph state.
All extraction is done by `/graphify`. All writes (including the graphify-output merge into the
vault's cumulative `graphify-out/`) are done by `/bedrock:preserve`.
Follow the phases below in order, without skipping steps.
---
## Phase 0 โ Ensure docling is installed
Before any fetch or conversion, verify that the `docling` CLI is available. If missing, install
it silently using the same fallback chain `/bedrock:setup` uses for graphify, emitting a single
echo "ERROR: docling install failed. Run /bedrock:setup to install it, or install manually: pipx install docling"
exit 1
fi
echo "Phase 0: docling installed."
fi
```
**Failure mode:** If install fails (no `pipx`/`pip`, network outage, permission denied), abort
the skill with the error above. Do NOT fetch or mutate anything. Direct the user to `/bedrock:setup`.
**No user prompt:** this step is silent โ one status line on success, one error line on failure.
---
## Phase 1 โ Fetch
### 1.1 Classify the input
The user provides an argument. Classify it in the following priority order. URL-type routing
is unchanged; local files no longer have an extension allowlist โ any existing file is accepted,
and Phase 1.5 decides whether to run docling on it.
| Input | Detected type | Fetch method |
|---|---|---|
| URL containing `confluence` or `atlassian.net` | confluence | Read `skills/confluence-to-markdown/SKILL.md`, follow instructions, save output to tmp |
| URL containing `docs.google.com` | gdoc | Read `skills/gdoc-to-markdown/SKILL.md`, follow instructions, save output to tmp |
| URL containing `github.com` | github-repo | `git clone --depth 1` to tmp + GitHub MCP enrichment (docling never runs on GitHub repos) |
| URL starting with `http://` or `https://` (any other) | remote-binary | Download raw bytes to tmp via `curl`/WebFetch; Phase 1.5 decides conversion |
| Local file path (any existing file) | local-file | Copy to tmp; Phase 1.5 decides conversion |
| Local directory path | local-dir | Copy directory to tmp |
| No match above | manual | Ask the user: "Could not identify the source type. Paste the content or provide a valid URL/path." |
If no argument was provided: ask the user "What source do you want to ingest? Provide a URL (Confluence, Google Docs, GitHub, or any HTTP(S) URL) or a local file path (any file type โ docling will convert it to markdown if supported)."
### 1.2 Create temporary directory
All content is fetched to a temporary directory. This is the single input path for `/graphify`.
```bash
LEARN_TMP="/tmp/bedrock-learn-$(date +%s)"
mkdir -p "$LEARN_TMP"
echo "Temporary directory: $LEARN_TMP"
```
Store the path for use in subsequent phases.
### 1.3 Fetch content
Execute the fetch strategy for the detected type. All content lands in `$LEARN_TMP/`.
#### 1.3.1 GitHub repository
For GitHub URLs (e.g.: `https://github.com/acme-corp/billing-api`):
1. Extract `owner/repo` and `repo-name` from the URL
3. GitHub MCP enrichment โ call directly in main context (NOT via subagent โ MCP permissions are not inherited):
- `mcp__plugin_github_github__get_file_contents` โ read the repo's README.md
- `mcp__plugin_github_github__list_commits` โ last 10 commits
- `mcp__plugin_github_github__list_pull_requests` โ last 5 PRs (state=all, sort=updated)
4. Compile MCP results into a single markdown file and save as `$LEARN_TMP/<repo-name>/_github_metadata.md`
> **Best-effort:** If any MCP call fails, continue with what was obtained. Do NOT block ingestion.
#### 1.3.2 Confluence
For Confluence URLs:
1. Read the internal skill at `<base_dir>/../confluence-to-markdown/SKILL.md`
2. Follow its instructions to parse the URL, choose layer (MCP โ API โ browser), and extract content
3. Save the returned Markdown content to `$LEARN_TMP/<slug>.md`
- `<slug>` is derived from the page title or URL path (kebab-case, lowercase)
If all three layers (MCP, API, browser) are unavailable: warn the user with the guidance message from the fetcher module and abort this source type.
#### 1.3.3 Google Docs / Sheets
For Google Docs or Sheets URLs:
1. Read the internal skill at `<base_dir>/../gdoc-to-markdown/SKILL.md`
2. Follow its instructions to parse the URL, detect document type (Doc vs Sheet), choose layer (MCP โ API/public export โ browser), and extract content
3. The fetcher saves output to `/tmp/gdoc_{docId}.md` or `/tmp/gsheet_{docId}.md`
4. Copy the output file to `$LEARN_TMP/<slug>.md`
- `<slug>` is derived from the document title or URL path (kebab-case, lowercase)
If all three layers (MCP, API/public export, browser) are unavailable: warn the user with the guidance message from the fetcher module and abort this source type.
#### 1.3.4 Remote URL (generic)
For any other HTTP/HTTPS URL, download the raw bytes so docling can operate on binary formats
(PDF, DOCX, PPTX, XLSX, images, etc.) that WebFetch cannot return faithfully as text:
- `GRAPH_REPORT.md` โ audit report with god nodes, surprising connections
- `obsidian/*.md` โ one markdown file per node
- `.graphify_analysis.json` โ communities, cohesion scores, god nodes
Report: "Phase 2 complete: graphify extraction finished in `$GRAPHIFY_OUT_NEW`. Graph: N nodes, M edges. Will be merged into the vault by /bedrock:preserve."
`actor_context` tells `/preserve` that the entire corpus belongs to a single actor in the vault. When set, every `file_type=document/paper` graphify node is classified as `code` of that actor with `node_type โ {concept, decision}`, instead of as a global `concept`/`topic`/`fleeting`.
Derivation rules by `source_type`:
| `source_type` | `actor_context` derivation |
|---|---|
| `github-repo` | Use the cloned repo's `repo-name` (kebab-case) when an actor with the same slug exists in `<VAULT_PATH>/actors/`. Otherwise leave `actor_context` unset and let `/preserve` use corpus-agnostic classification. |
| `local-dir` | Same rule as `github-repo`: use the directory's basename when it matches a vault actor; otherwise leave unset. |
| `confluence`, `gdoc`, `remote-binary`, `local-file`, `manual` | Leave `actor_context` unset. These corpora are not scoped to a single actor by default. |
**Multi-actor abort.** Before passing `actor_context`, scan the cloned repo's top-level subdirectories. If 2 or more of those subdirectory names match existing actor slugs in `<VAULT_PATH>/actors/`, abort with:
> "Detected multiple actor candidates in this corpus: `<list>`. `/learn` only accepts a single-actor corpus per invocation. Run `/learn` separately against each actor, e.g.: `/learn <url>/<sub-actor-1>` and `/learn <url>/<sub-actor-2>`. If the repo is a true monorepo and you want a single ingestion, leave `actor_context` unset by passing `--no-actor-context` (graphify nodes will be classified globally instead of as `code` of one actor)."
>
> Do NOT proceed to /preserve.
For non-`github-repo`/`local-dir` source types, no multi-actor scan is needed.
#### 3.1.2 Build the input
Pass the **temp** graphify output path, provenance metadata, and (optional) `actor_context` to `/bedrock:preserve`. The skill's Phase 0.2 merges this temp output into the vault's cumulative `graphify-out/`:
(Pulled verbatim from `/bedrock:preserve`'s `graphify_merge` return block.)
### Entities processed (via /bedrock:preserve)
| Type | Name | Action |
|---|---|---|
| actor | billing-api | update |
| topic | 2026-04-migration-payments | create |
| code | process-transaction | create |
### Provenance
Each entity above received in the `sources` frontmatter field:
- url: <source_url>
- type: <source_type>
- synced_at: <today's date>
### Git
- Commit: <hash from /bedrock:preserve or "no entities">
- Push: success / failed (reason)
### Suggestions
- [list of entities mentioned in the content but not created, if any]
- [recommendations for future re-ingestion, if applicable]
```
---
## Critical Rules
| Rule | Detail |
|---|---|
| Invoke /graphify via Skill tool | NEVER call graphify Python API directly (`graphify.detect`, `graphify.build`, `graphify.extract`, etc.). Always invoke via the Skill tool. |
| All remote content fetched to /tmp | Every input type is fetched to `/tmp/bedrock-learn-<ts>/` before invoking graphify. graphify receives only a local path. |
| /learn does NOT classify entities | Entity classification, filtering, matching, and user confirmation are `/bedrock:preserve`'s responsibility. /learn passes the graphify output path and provenance metadata. |
| Delegate to /bedrock:preserve | ALL entities are persisted via `/bedrock:preserve` โ learn does NOT create, update, or write vault entities. |
| /learn does NOT merge graphify output into the vault | Graphify is invoked into `$LEARN_TMP/graphify-out/` (per-run temp dir); `/bedrock:preserve`'s Phase 0.2 merges that into `<VAULT_PATH>/graphify-out/`. /learn never writes directly to the vault's `graphify-out/`. |
| Docling auto-install is silent | Phase 0 auto-installs docling if missing with a single status line โ no user prompt. Fail the skill if install fails; direct the user to `/bedrock:setup`. |
| Docling routing rule | Run docling on files with docling-supported extensions (see Phase 1.5.1). Pass-through for `.md`/`.txt`/`.csv` and for extensions not in docling's supported list. |
| Docling failure fallback | On docling non-zero exit: if file is `.md`/`.txt`/`.csv`, continue with raw file. For any other extension, abort the entire skill and clean up `$LEARN_TMP`. |
| Cleanup /tmp after /preserve confirms | Remove `/tmp/bedrock-learn-<ts>/` only after /preserve confirms completion, not after graphify finishes. |
| Provenance via source_url | ALWAYS include `source_url` and `source_type` when delegating to /bedrock:preserve. |
| Internal fetcher skills | Read internal skills from `<base_dir>/../confluence-to-markdown/SKILL.md` and `<base_dir>/../gdoc-to-markdown/SKILL.md` for content fetching. Never invoke external skills. |
| Best-effort for external sources | If MCP or fetch fails, warn and continue with what was obtained. Never block ingestion. |
| MCP in main context | Do NOT use subagents for GitHub/Atlassian MCP calls โ permissions are not inherited. |
| Maximum 2 push attempts | After that, abort and inform (handled by /preserve). |
| Sensitive data | NEVER include credentials, tokens, passwords, PANs, CVVs. |
| Vault resolution first | Resolve `VAULT_PATH` before any file operation โ never assume CWD is the vault |
| Pass --vault to /preserve | ALWAYS include `--vault <VAULT_NAME>` when delegating to `/bedrock:preserve` |
| Derive `actor_context` for actor corpora | For `source_type โ {github-repo, local-dir}`, when the repo/dir basename matches an existing vault actor slug, pass `actor_context: <slug>` to `/preserve`. For other source types, leave `actor_context` unset. |
| Multi-actor abort | Before passing `actor_context`, scan top-level subdirectories of the cloned repo. If 2+ subdirectories match existing actor slugs in `<VAULT_PATH>/actors/`, abort with guidance to split the invocation. Never auto-partition. |