Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tags & Effects

Tags let you control how bobbin scores and filters search results. By assigning tags to chunks via pattern rules, then configuring effects (boost, demote, exclude, pin), you tune search quality without changing the indexing pipeline.

Overview

The tag system has three layers:

  1. Rules — Glob patterns that assign tags to files during indexing
  2. Effects — Score adjustments applied when tagged chunks appear in results
  3. Scoped Effects — Role-specific overrides (e.g., boost lifecycle docs only for witness)

Configuration lives in .bobbin/tags.toml at the root of your bobbin data directory.

Tags Configuration

Rules

Rules match file paths and assign tags:

[[rules]]
pattern = "**/CHANGELOG.md"
tags = ["type:changelog"]

[[rules]]
pattern = "**/ansible/roles/*/tasks/main.yml"
tags = ["domain:iac", "criticality:high"]
repo = "goldblum"  # Optional: only apply when indexing this repo
  • pattern — Standard glob pattern matched against relative file paths
  • tags — List of tag strings to assign (convention: namespace:value)
  • repo — Optional repo scope (only applies during indexing of that repo)

Glob pattern note: Patterns are matched against paths relative to the repo root (e.g., snapshots/ian/2026-03-12.md, not the absolute path). The **/ prefix matches both root-level and nested paths — **/CHANGELOG.md matches both CHANGELOG.md and docs/CHANGELOG.md.

Effects

Effects modify scores when tagged chunks appear in search results:

[effects."type:changelog"]
boost = -0.6      # Demote: score *= (1 + boost) = 0.4

[effects."auto:init"]
exclude = true     # Remove entirely from results

[effects."criticality:high"]
boost = 0.2        # Boost: score *= 1.2

[effects."feedback:hot"]
boost = 0.3
pin = true         # Always include, bypass relevance threshold
budget_reserve = 20  # Reserve 20 lines of budget for pinned chunks

Score formula: final_score = raw_score * product(1 + boost) for all matching tags, clamped to [0.01, 10.0].

FieldTypeDescription
boostfloatScore multiplier. Positive = boost, negative = demote.
excludeboolRemove chunks with this tag from results entirely.
pinboolBypass relevance threshold; always include if budget allows.
budget_reserveintLines of budget reserved for pinned chunks.

Scoped Effects

Override global effects for specific roles:

# Globally demote lifecycle docs
[effects."domain:lifecycle"]
boost = -0.3

# But boost them for witness role
[[effects_scoped]]
tag = "domain:lifecycle"
role = "*/witness"
boost = 0.2

# Exclude internal docs for external users
[[effects_scoped]]
tag = "type:internal"
role = "external/*"
exclude = true

The role field supports glob patterns. When a request includes a role (via --role flag or BOBBIN_ROLE env var), scoped effects override global effects for matching roles.

Tag Assignment Sources

Tags are assigned from four sources during indexing. All sources merge — multiple matching rules union their tags into a comma-separated sorted string per chunk.

1. Convention Tags (auto-assigned)

TagApplied to
auto:initGo init() functions
auto:testTest functions (Go, Rust, Python, JS)
auto:docsDocumentation files (markdown, rst, etc.)
auto:configConfig files (YAML, TOML, JSON, .env, etc.)
auto:generatedGenerated code (.min.js, .gen.go, .generated.ts, etc.)

2. Pattern Rules (tags.toml)

Glob patterns matched against repo-relative paths. See Rules above.

3. Frontmatter Tags

Markdown files with YAML frontmatter between --- fences:

---
tags: [canonical, architecture]
---

Supported field names: tags, bobbin-tags, labels. Supports inline arrays, single values, and block lists. Tags without a : prefix are auto-prefixed with user: (e.g., canonicaluser:canonical).

4. Code Comment Directives

Inline tag assignment in source code:

#![allow(unused)]
fn main() {
// bobbin:tag security critical
fn handle_auth() { ... }

bobbin:tag deprecated
def old_api():
}

Supports //, #, and /* */ comment styles. Tags are applied to the chunk containing the comment. Same user: auto-prefix as frontmatter.

Role Specificity

When multiple scoped effects match a role, the most specific pattern wins. Specificity is counted by non-wildcard path segments:

PatternSpecificityExample match
aegis/crew/stryder3 (most specific)Exact agent
*/crew/stryder2Any rig’s stryder
*/crew/*1Any crew member
*0 (least specific)Everyone

If a scoped effect matches, it fully replaces the global effect for that tag — it does not stack.

Tag Naming Conventions

Use namespace:value format for clarity:

NamespacePurposeExamples
auto:Auto-assigned by bobbinauto:test, auto:init
type:Document/chunk typetype:changelog, type:design, type:eval
role:Agent instruction filesrole:claude-md, role:agents-md
domain:Domain/topic areadomain:iac, domain:lifecycle, domain:comms
criticality:Importance levelcriticality:high
feedback:Feedback-driven scoringfeedback:hot, feedback:cold

Scoring Mechanics

When tag effects are applied during context assembly, the formula is:

effective_factor = product(1 + boost for each matching tag effect)
final_score = raw_score * clamp(effective_factor, 0.01, 10.0)

Examples:

  • boost = 0.5 → factor = 1.5 → 50% score increase
  • boost = -0.8 → factor = 0.2 → 80% score decrease
  • Multiple tags: canonical (boost +0.5) + architecture (boost +0.2) → factor = 1.5 × 1.2 = 1.8

When a chunk has any tag with a configured effect, the tag effect replaces the default doc_demotion. This means a doc file tagged criticality:high with boost = 0.3 gets a 30% boost instead of the usual doc demotion penalty.

Exclude vs demote vs pin

MechanismWhen appliedEffectUse case
Exclude (exclude = true)Pre-search (SQL WHERE clause)Chunk never fetched from DBNoise that always wastes budget
Demote (negative boost)Post-search scoringScore reduced but chunk still includedSometimes useful, usually low-priority
Pin (pin = true)Assembly stage (reserved budget)Bypasses relevance threshold, injected firstCritical content that must always appear

Excludes are the most efficient — chunks are filtered at the database level before scoring. Use them for content that is never useful (auto-generated files, boilerplate init functions).

Pinned chunks have reserved budget (budget_reserve lines, default 50) and are always injected regardless of query relevance. Use sparingly.

/search vs /context

The /search endpoint returns raw scores — only exclude filters are applied (pre-search WHERE clauses). Boost/demote/pin effects are NOT applied.

The /context endpoint and hook injection apply the full scoring pipeline: excludes + boosts + demotes + pins.

To verify tag effects are working, test with bobbin context or the /context API, not /search.

Scoped Effect Resolution

When resolving effects for a role, specificity wins:

  1. Check [[effects_scoped]] entries matching both tag AND role glob
  2. Most specific role pattern wins (count non-wildcard segments)
  3. Fall back to global [effects.tag]
  4. No match = no effect (default behavior)
[effects.test]
boost = -0.3                # Global: demote test files

[[effects_scoped]]
tag = "test"
role = "aegis/crew/sentinel"
boost = 0.3                 # Sentinel: BOOST test files (review coverage)

Scoped effects can also un-exclude a globally excluded tag:

[effects.internal]
exclude = true              # Global: always exclude

[[effects_scoped]]
tag = "internal"
role = "aegis/*"
exclude = false             # Aegis agents CAN see internal content

Example: Reducing Noise

A common pattern is to identify noisy document types and demote them:

# Problem: Changelog entries surface in unrelated queries
[effects."type:changelog"]
boost = -0.6  # Demote to 40% of original score

[[rules]]
pattern = "**/CHANGELOG.md"
tags = ["type:changelog"]

# Problem: Eval task YAML files match cargo/build queries
[effects."type:eval"]
boost = -0.5

[[rules]]
pattern = "**/eval/tasks/**"
tags = ["type:eval"]
repo = "bobbin"

# But un-demote for the developer who works on evals
[[effects_scoped]]
tag = "type:eval"
role = "aegis/crew/stryder"
boost = 0.0

Applying Changes

After modifying tags.toml:

  1. Reindex affected repos to apply new tag rules to chunks:

    bobbin index /path/to/data --repo <name> --source /path/to/repo --force
    
  2. Restart the server if running in HTTP mode (tags config is loaded at startup):

    sudo systemctl restart bobbin
    

Tag effects are applied during context assembly (/context endpoint), so restarting the server picks up both new effect weights and new tag assignments from the reindex.

Note: The /search endpoint returns raw relevance scores without tag effects. To verify that tag boosts/demotions are working, test with the /context endpoint or the bobbin context CLI command.

Debugging Tags

To see which tags are assigned to search results, use the JSON output:

bobbin search "your query" --json | jq '.results[].tags'

Check tag coverage after indexing:

✓ Indexed 942 files (12270 chunks)
  Tags: 41096 tagged, 21327 untagged chunks

High untagged counts are normal — not every chunk needs tags. Focus rules on document types that cause noise (changelogs, templates, test files) or that need boosting (critical configs, design docs).

To filter search results by tag:

bobbin search "your query" --tag type:guide        # Include only guides
bobbin search "your query" --exclude-tag type:eval  # Exclude eval artifacts