Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a3777c0
feat: recency boost for search (v0.27.0) — temporal intent auto-detec…
May 4, 2026
421ec75
v0.29.1 schema: pages.{effective_date, effective_date_source, import_…
garrytan May 6, 2026
ea63fa1
v0.29.1: computeEffectiveDate helper + putPage integration
garrytan May 6, 2026
c1734b9
v0.29.1: backfill orchestrator + library function for existing pages
garrytan May 6, 2026
228af0f
v0.29.1: gbrain reindex-frontmatter CLI command
garrytan May 6, 2026
307fd30
v0.29.1: recency-decay map + buildRecencyComponentSql (pure, unused)
garrytan May 6, 2026
9e4072e
v0.29.1: refactor getRecentSalience to consume buildRecencyComponentSql
garrytan May 6, 2026
a2e5899
v0.29.1: get_recent_salience gains recency_bias param (default 'flat')
garrytan May 6, 2026
6132fbc
v0.29.1: recompute_emotional_weight writes salience_touched_at; windo…
garrytan May 6, 2026
af6c768
v0.29.1: merge intent.ts → query-intent.ts; emit 3 suggestions per query
garrytan May 6, 2026
b5048a7
v0.29.1: applySalienceBoost + applyRecencyBoost + runPostFusionStages…
garrytan May 6, 2026
9f66273
v0.29.1: query op gains salience + recency + since + until params; PG…
garrytan May 6, 2026
d4eaf29
v0.29.1: migration v39 — eval_candidates capture columns for replay r…
garrytan May 6, 2026
67a1c68
v0.29.1: doctor checks — effective_date_health + salience_health
garrytan May 6, 2026
e737344
v0.29.1: docs + skills convention + CHANGELOG + version bump
garrytan May 6, 2026
7a4bd97
Merge remote-tracking branch 'origin/garrytan/v0.29-salience' into ga…
garrytan May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,130 @@

All notable changes to GBrain will be documented in this file.

## [0.29.1] - 2026-05-05

**Recency and salience as two orthogonal options. Agent in charge.**
**Two ranking knobs, smart heuristic, no default behavior change for existing callers.**

v0.29 made the brain tell you what's hot. v0.29.1 lets the agent ask for
recency or salience independently — two orthogonal axes on the regular
`query` op, both opt-in, both with smart auto-detection from query text.
"What's going on with widget-co" auto-fires both. "Who is widget-ceo"
keeps both off. The agent overrides per query.

The two axes:

- **`salience: 'off' | 'on' | 'strong'`** — boost pages with high
`emotional_weight` + many active takes. NO time component. Use for
"what matters about X."
- **`recency: 'off' | 'on' | 'strong'`** — per-prefix age decay. NO
mattering signal. `concepts/`, `originals/`, `writing/` stay
evergreen; `daily/`, `media/x/`, `chat/` decay aggressively. Use for
"what's new on X."

Plus `since` / `until` date filters (replacing PR #618's `afterDate` /
`beforeDate` with proper PGLite parity), a new `pages.effective_date`
column populated from frontmatter precedence (immune to auto-link
`updated_at` churn), and `gbrain reindex-frontmatter` for explicit
recompute. Existing callers (no new params) get UNCHANGED behavior.

### What this means for you

A v0.29.0 caller upgrading to v0.29.1 with no code changes gets
identical query results. The new axes are pure opt-in. The agent
reads the new tool descriptions on every `tools/list` poll and learns
when to pass each value.

Pass `salience='on'` for meeting prep, conversation recall, "what's
going on with X." Pass `recency='on'` for "latest" / "this week" /
"recent updates." Pass `recency='strong'` for "today" / "right now."
Omit and gbrain auto-detects via the layered classifier in
`src/core/search/query-intent.ts` (canonical patterns win over
current-state EXCEPT when explicit temporal bounds like "today" /
"this week" / "since X" are present).

### Itemized changes

**Schema** (additive only, NDJSON schema_version stays at 1):
- Migration v38 adds 4 nullable columns to `pages`: `effective_date`,
`effective_date_source`, `import_filename`, `salience_touched_at`.
- Migration v39 adds 7 nullable columns to `eval_candidates` for
agent-explicit recency capture (replay reproducibility per D11).
- Expression index `pages_coalesce_date_idx` for `since`/`until` filters.

**Engine methods** (composite-keyed for multi-source isolation):
- `getEffectiveDates(refs)` returns `COALESCE(effective_date,
updated_at, created_at)`. Map keyed by `${source_id}::${slug}`.
- `getSalienceScores(refs)` returns `emotional_weight × 5 + ln(1 +
take_count)`. Same composite key.

**Search pipeline**:
- New `runPostFusionStages` wrapper consolidates backlink + salience +
recency. Called from ALL THREE `hybridSearch` return paths so
keyless installs and embed failures get the same boost surface.
- `applySalienceBoost` — pure mattering. `applyRecencyBoost` — pure
age decay. Truly orthogonal.
- `buildRecencyComponentSql` shared SQL builder with typed `NowExpr`
enum (no SQL injection).

**Query op**: gains `salience`, `recency`, `since`, `until` with
load-bearing tool descriptions. `get_recent_salience` gains
`recency_bias: 'flat' | 'on'` (default `'flat'` = v0.29.0 verbatim).

**Back-compat**: `afterDate`/`beforeDate`/`recencyBoost` from PR #618
remain as deprecated aliases. Stderr warning fires once per process.
Removed in v0.30.

**Heuristic**: `query-intent.ts` replaces `intent.ts`. Single regex
pass returning `{intent, suggestedDetail, suggestedSalience,
suggestedRecency}`. Canonical-wins + narrow temporal-bound exception.
English-only in v0.29.1.

**Doctor**: `effective_date_health` + `salience_health` checks. Both
gracefully skip on pre-v0.29.1 brains.

**CLI**: `gbrain reindex-frontmatter` — recovery / explicit-rebuild
path mirroring `gbrain reindex-code`.

**Tests**: `test/effective-date.test.ts` (21 cases),
`test/recency-decay.test.ts` (25 cases), `test/query-intent.test.ts`
(21 cases).

### To take advantage of v0.29.1

`gbrain upgrade` runs the full migration chain automatically. Verify:

1. **Confirm upgrade**:
```bash
gbrain --version # 0.29.1
```

2. **Recompute emotional weights** (one-time after upgrade):
```bash
gbrain dream --phase recompute_emotional_weight
```

3. **Verify health checks**:
```bash
gbrain doctor --json | jq '.checks[] | select(.name == "salience_health" or .name == "effective_date_health")'
```

4. **Try the new axes**:
```bash
gbrain query "what's been going on with X" --explain --json | jq '._resolved'
# expected: salience='on', recency='on'

gbrain query "who is X" --explain --json | jq '._resolved'
# expected: salience='off', recency='off'
```

5. **If anything looks wrong** — `gbrain doctor --json` output and
`~/.gbrain/upgrade-errors.jsonl` (if present) on a Github issue:
https://github.com/garrytan/gbrain/issues

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Wintermute <wintermute@garrytan.com>

## [0.29.0] - 2026-05-03

**The brain tells you what's hot without being asked.**
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.29.0
0.29.1
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gbrain",
"version": "0.29.0",
"version": "0.29.1",
"description": "Postgres-native personal knowledge brain with hybrid RAG search",
"type": "module",
"main": "src/core/index.ts",
Expand Down
131 changes: 131 additions & 0 deletions skills/conventions/salience-and-recency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Salience + Recency on `gbrain query` (v0.29.1)

YOU ARE IN CHARGE of the `salience` and `recency` parameters on gbrain's
`query` op. They are TWO ORTHOGONAL axes — use either, both, or neither.

If you OMIT a parameter, gbrain auto-detects from query text via a
regex heuristic. The default for queries that don't match any pattern
is `'off'`. Prefer to pass values EXPLICITLY when you know what the
user wants.

## What each axis means

- `salience` — **mattering**. Boosts pages with high `emotional_weight`
and many active takes. NO time component. Use when the user wants
the most important / most-discussed pages on a topic, regardless of
when they were updated.

- `recency` — **age**. Boosts pages with recent `effective_date`. NO
mattering signal. Per-prefix decay (`concepts/`, `originals/`,
`writing/` are evergreen; `daily/`, `media/x/`, `chat/` decay
aggressively). Use when freshness is the signal.

## When to pass `salience='on'`

The "mattering" axis. The user wants what matters in this brain on
the topic, not the canonical encyclopedia entry.

- `"prep me for the widget-ceo meeting"` (meeting prep)
- `"catch me up on acme"` (conversation recall)
- `"what's going on with widget-co"` (current state matters)
- `"remind me about the deal"` (recall takes / opinions)
- `"what's been happening lately"`
- `"status update on X"`

Pair with `recency='on'` when current-state matters. Just `salience='on'`
alone gives you "what matters about X regardless of when."

## When to pass `recency='on'`

The "freshness" axis. The user wants recent content, with or without
mattering.

- `"latest news on AI"` (recent, no mattering needed)
- `"what's new this week"`
- `"recent updates on widget-co"`
- `"this week's announcements"`

Use `'strong'` when the user explicitly asks for the most recent:

- `"what happened today"`
- `"right now what's going on"`
- `"this morning"`

## When to pass BOTH `'off'`

The "canonical truth" axis. The user wants the authoritative answer.

- `"who is widget-ceo"` (entity lookup)
- `"what is widget-co"` (definitional)
- `"history of acme"` (historical research)
- `"explain how recursion works"` (concept query)
- `"tell me about widget-co"` (canonical recall)
- Code lookups: function/class names, syntax like `Foo::bar()` or `obj.method`
- Graph traversal: backlinks, inbound/outbound edges
- Anything not matching above

## Heuristic when unsure

> Current state → on. Canonical truth → off.

If you can't classify confidently, OMIT the param and let gbrain's
auto-detect handle it. The heuristic defaults to `off` for everything
that doesn't clearly match a current-state pattern. The `--explain`
output shows `_resolved.salience_source` and `_resolved.recency_source`
('caller' vs. 'auto_heuristic') so you can see what fired and why.

You can override at any time. gbrain is smart but not infallible. You
have context gbrain doesn't.

## Narrow temporal-bound exception

Even when a query matches canonical patterns, an explicit temporal
bound (`today`, `this week`, `right now`, `since X`, `last N days`)
overrides the canonical-wins rule:

- `"who is widget-ceo right now"` → recency = `'strong'`, salience = `'on'`
(the temporal bound wins over "who is")
- `"who is widget-ceo"` → recency = `'off'`, salience = `'off'` (no bound)

## English-only

The auto-detect heuristic is English-only in v0.29.1. Non-English
queries fall through to the default `off` for both axes. Pass
`salience` and `recency` explicitly for non-English queries.

## Tuning the recency formula

Defaults are in `src/core/search/recency-decay.ts`. Override per-brain
via `gbrain.yml`:

```yaml
recency:
daily/:
halflifeDays: 7
coefficient: 2.0
custom-prefix/:
halflifeDays: 30
coefficient: 0.5
```

Or per-process via env: `GBRAIN_RECENCY_DECAY="prefix:halflife:coefficient,..."`.
The parser fails LOUD on bad syntax (no silent fallback).

## Date filtering with `since` / `until`

Independent of the axes. Filter to pages whose `effective_date` is
within a range:

- `since: '7d'` — last 7 days
- `since: '2024-06-01'` — ISO-8601
- `until: '2024-06-30'` — ends at end-of-day

`since`/`until` work with OR without `salience`/`recency`. Pure filter,
no boost.

## See also

- `docs/recency.md` — full reference
- `gbrain query --explain` — see resolved values + factor contributions
- `get_recent_salience` op gains `recency_bias: 'flat' | 'on'` — opt
into per-prefix decay on the dedicated salience query
106 changes: 106 additions & 0 deletions skills/migrations/v0.29.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# v0.29.1 migration — Salience + Recency as two orthogonal options

`gbrain upgrade` runs `gbrain apply-migrations` automatically. Most users
won't need to do anything else. If you hit issues or want to verify the
upgrade succeeded, run the steps below.

## What changed

This release adds two new optional ranking axes to the `query` MCP op:

- `salience: 'off' | 'on' | 'strong'` — boost pages with high
`emotional_weight` + many active takes. NO time component.
- `recency: 'off' | 'on' | 'strong'` — boost pages with recent
`effective_date`, per-prefix decay. NO mattering signal.

They're TRULY ORTHOGONAL — either, both, or neither. The agent decides
per query, with `--explain` output showing the resolved values and
sources.

Existing v0.29.0 callers (no new params) get UNCHANGED behavior.

## What the agent should do

Read `skills/conventions/salience-and-recency.md` (auto-installed by
the skillpack). Then in your `gbrain query` calls:

1. Pass `salience='on'` for "what matters about X" queries (catch up,
meeting prep, conversation recall).
2. Pass `recency='on'` for "what's new on X" queries (latest, this
week, today's news).
3. Pass both for "what's been going on with X" queries.
4. Omit both for canonical / definitional / code / graph queries
(`who is X`, `what is X`, etc.) — gbrain's heuristic defaults
to `off`.

## Verification

```bash
# 1. Confirm upgrade
gbrain --version # 0.29.1
gbrain doctor --json | jq '.checks[] | select(.name | startswith("schema_version"))'

# 2. Recompute emotional weights (one-time after upgrade)
gbrain dream --phase recompute_emotional_weight

# 3. Verify health checks
gbrain doctor --json | jq '.checks[] | select(.name | startswith("salience_health") or startswith("effective_date_health"))'

# 4. Try the new axes
gbrain query "what's been going on with X" --explain --json | jq '._resolved'
# expected: { salience: "on", recency: "on", salience_source: "auto_heuristic", recency_source: "auto_heuristic" }

gbrain query "who is X" --explain --json | jq '._resolved'
# expected: { salience: "off", recency: "off", ... }

# 5. Date filter
gbrain query "acme" --since 7d --until 2024-06-30 --json
```

## If something looks wrong

```bash
# Re-apply migrations manually
gbrain apply-migrations --yes

# Force re-run the v0.29.1 backfill (computeEffectiveDate on every page)
gbrain reindex-frontmatter --yes --force

# Doctor for any warnings
gbrain doctor --json
```

If issues persist, file at https://github.com/garrytan/gbrain/issues
with the doctor output and contents of `~/.gbrain/upgrade-errors.jsonl`
(if it exists).

## Schema additions (idempotent, additive only)

Migration v38 adds 4 nullable columns to `pages`:
- `effective_date` — content-date computed from frontmatter precedence
- `effective_date_source` — sentinel for the doctor check
- `import_filename` — basename captured at import for filename-date precedence
- `salience_touched_at` — bumped by recompute_emotional_weight on changes

Migration v39 adds 7 nullable columns to `eval_candidates`:
- `as_of_ts`, `salience_param`, `recency_param`, `salience_resolved`,
`recency_resolved`, `salience_source`, `recency_source`

Plus the `pages_coalesce_date_idx` expression index for since/until filters.

NDJSON `schema_version` STAYS at 1; consumers ignore unknown fields.
No cross-repo coordination required.

## Behavior changes

- v0.29.0 `get_recent_salience` formula: UNCHANGED for callers who don't
pass `recency_bias='on'`. Pass `recency_bias='on'` to opt into per-prefix
decay (concepts/originals/writing/ evergreen; daily/, media/x/ aggressive).

- v0.29.0 SearchOpts: `afterDate`, `beforeDate`, `recencyBoost: 0|1|2`
remain as DEPRECATED ALIASES for `since`, `until`, `recency`. They
emit a stderr warning once per process. Removed in v0.30.

- `detail='high'` source-boost bypass — UNCHANGED in v0.29.1. The
known temporal-query swamp is documented; pass `salience='on'` to
compensate via salience boost.
Loading