/gaia-knowledge-refresh
user-facingWhat it does
/gaia-knowledge-refresh walks every source_type: ingested entry in the brain knowledge layer's index, re-fetches its source, and applies a hash-gated three-way reconcile so only changed content touches the disk.
It uses the same fetch, HTML-strip, and hash machinery as /gaia-feed (shared via a common library), so the two pipelines never drift apart.
After the per-entry reconcile, the command runs an expiry enforcement sweep that marks any entry whose TTL has elapsed without a successful revalidation as stale.
When to use it
- You previously ingested external reference material via
/gaia-feedand want to check whether any sources have been updated. - You want to keep ingested documents current without re-ingesting each one manually.
- You want a non-destructive refresh -- sources that fail to fetch are marked failed but their last-good content is preserved.
Prerequisites
- Ingested entries must exist. Run
/gaia-feedfirst to populate the knowledge store. If no ingested entries exist, the refresh exits cleanly with nothing to do. - A valid
brain-index.yamlmust be present in the knowledge store.
How to invoke
/gaia-knowledge-refresh
No arguments are required. The command operates on all ingested entries in the brain index.
Three-way reconcile
For each ingested entry, the refresh computes the sha256 of the re-fetched, post-strip content and compares it against the stored content_hash. Exactly one of three outcomes applies:
| Outcome | Condition | Side effects |
|---|---|---|
| Skip | Hash matches | No content rewrite. No brain-index content mutation. If the entry was previously marked failed, its status is healed back to current (the source is demonstrably reachable again). If the entry is past its expiry, this successful revalidation renews the TTL window (see Expiry). |
| Overwrite | Hash differs | Ingested file atomically overwritten with new content. Brain-index entry's content_hash, fetched_at, and expires_at all updated. The new expires_at is recomputed as now + ttl_days. Status set to current. |
| Mark failed | Fetch fails | Ingested file's frontmatter status set to failed. Stale file content preserved. No destructive delete. |
The failure branch is deliberately non-destructive: the last-good copy remains queryable while the source is temporarily unavailable.
Expiry and the status lifecycle
Every ingested document has an expires_at timestamp computed as fetched_at + ttl_days (default TTL is 30 days). The refresh command enforces this TTL through a two-phase process:
- Per-entry reconcile (described above) -- attempts to re-fetch each source. A successful re-fetch with changed content recomputes
expires_at = now + ttl_days, moving the expiry window forward. - Expiry enforcement sweep -- after all entries have been reconciled, the command walks every ingested entry once more and marks as
staleany entry whoseexpires_atis in the past and whose status is stillcurrent. This catches entries whose TTL elapsed without a successful revalidation during this run.
Status transitions
| From | To | Trigger |
|---|---|---|
current | stale | The entry's TTL has elapsed and it was not successfully revalidated during this refresh run. |
current or stale | failed | The source could not be re-fetched (network error, URL gone, etc.). |
failed | current | The source is reachable again and the re-fetched content matches the stored hash (hash-match heal). |
stale | current | The source is successfully re-fetched (whether content changed or matched) -- the entry is revalidated and its expiry renewed. |
Does running refresh reset the 30-day expiry?
No -- not for unchanged, unexpired sources. For a source whose content has not changed and whose TTL has not elapsed, the refresh is a true no-op: the expiry is left untouched. Repeated refreshes over an unchanged, unexpired knowledge store produce zero writes.
Expiry is recomputed (expires_at = now + ttl_days) only when:
- The content has actually changed (the overwrite path).
- The entry was expired but still valid -- the source was successfully re-fetched with identical content, revalidating it and renewing the TTL window.
In other words: a fresh, unchanged entry is left alone. An expired entry that passes revalidation gets a new lease. An expired entry that fails revalidation is marked stale (or failed if the fetch itself errors).
Stdin-sourced entries
Entries originally ingested via stdin (/gaia-feed -) have no re-fetchable origin -- the content was pasted once and cannot be re-read. The refresh command skips these entries entirely. It does not attempt a fetch and does not mark them as failed.
Stdin entries still participate in the expiry enforcement sweep: if their TTL elapses, they transition from current to stale like any other entry. To update a stale stdin entry, re-run /gaia-feed - with the same --slug and paste the new content.
Idempotency
A second consecutive run over unchanged, unexpired sources produces zero file writes and zero brain-index diffs. This follows directly from the hash-match skip path -- when content has not changed and no TTLs have elapsed, the entire refresh is a no-op.
This is observable via file modification times: after two back-to-back runs with no upstream changes, the mtimes of every ingested file and brain-index.yaml remain identical to those after the first run.
Outputs
| Output | Location | Description |
|---|---|---|
| Updated ingested files | .gaia/knowledge/ingested/<slug>.md | Only files whose content has changed are overwritten. |
| Updated brain-index entries | .gaia/knowledge/brain-index.yaml | Only entries with changed content have their content_hash, fetched_at, and expires_at updated. |
| Reconcile summary | stderr | Counts of skipped, updated, failed, and staled entries. |
What to run next
/gaia-brain-query-- query the brain to verify updated content is reflected./gaia-feed-- ingest new external documents alongside refreshed ones./gaia-unfeed-- remove an ingested document you no longer need.
Troubleshooting
All entries show "skipped"
This means every ingested source's content matches what is already stored. This is the expected outcome when nothing has changed upstream.
An entry shows "failed"
The source could not be re-fetched. The stale file is preserved. Check that the source URL is still reachable and that the site has not changed its URL structure. On the next successful refresh, a previously failed entry with matching content is automatically healed back to current.
Entries show "staled"
These entries had a TTL that elapsed and were not successfully revalidated during this run. Their content is still present and queryable, but marked stale to signal that they may be out of date. Re-run /gaia-feed with the same slug to refresh manually, or investigate why the upstream source could not be reached.
Brain-index validation failed
The updated index did not pass schema validation. The prior index is preserved unchanged. Check the error message for details.
Stdin entries are not refreshed
This is by design. Stdin-pasted sources have no re-fetchable origin. They are skipped during the fetch phase and only aged to stale when their TTL elapses. To update one, re-run /gaia-feed - --slug <slug> and paste the new content.