feedback-loops
User feedback signals for RAG improvement: thumbs up/down, click-through, dwell time, explicit ratings. Implicit vs explicit signals, logging schema, feedback -> retraining pipelines (embedding fine-tuning with hard negatives, reranker fine-tuning from CTR), A/B testing RAG variants, Langfuse/LangSmith feedback APIs, feature stores for online learning. USE WHEN: user mentions "user feedback", "thumbs up down", "CTR", "dwell time", "RAG evaluation feedback", "hard negatives", "reranker fine-tune", "A/B test RAG" DO NOT USE FOR: offline eval metrics - use `rag-evaluation`; retriever algorithms - use `advanced-retrieval`; observability as such - use `rag-observability`
What this skill does
# Feedback Loops for RAG
## Signals
### Explicit
| Signal | Latency | Density | Reliability |
|---|---|---|---|
| Thumbs up/down | Immediate | Low (< 5% response rate) | Medium — noisy |
| Star rating (1-5) | Immediate | Very low | Higher, but biased toward extremes |
| Free-text feedback | Delayed | Lowest | Highest — but hard to parse |
| Per-citation accept/reject | Immediate | Low | High — directly actionable |
### Implicit
| Signal | Captures | Caveat |
|---|---|---|
| Click-through on citation | Perceived relevance | Confirms source seen, not correctness |
| Dwell time on cited doc | Actual engagement | Needs session tracking |
| Copy text | High intent | Only partial |
| Regenerate click | Answer failure | Ambiguous — could be style not content |
| Next-turn "no, I meant..." | Answer failure | Parse NLU |
| Session abandonment | Frustration | Noisy — could be scheduled meeting |
| Scroll depth on answer | Read-through | Weak signal |
Always log both. Implicit is dense; explicit is reliable. Combine.
## Logging Schema
```python
from pydantic import BaseModel
from datetime import datetime
from typing import Literal
from uuid import UUID
class RagEvent(BaseModel):
event_id: UUID
session_id: UUID
user_id: str | None
ts: datetime
# Query context
query: str
rewritten_query: str | None
classifier_path: str
# Retrieval
retriever_variant: str # for A/B
retrieved_ids: list[str] # chunk IDs with rank
retrieval_scores: list[float]
# Generation
answer_id: UUID
model: str
tokens_in: int
tokens_out: int
latency_ms: int
# Citations emitted by answer
cited_ids: list[str]
class FeedbackEvent(BaseModel):
event_id: UUID
answer_id: UUID # FK to RagEvent
kind: Literal["thumbs", "rating", "citation_click", "citation_accept",
"citation_reject", "regenerate", "copy", "dwell", "free_text"]
value: float | str | None # 1/-1 for thumbs, 1-5 for rating, seconds for dwell
chunk_id: str | None # for citation-level feedback
ts: datetime
```
Store in an append-only table (Postgres, BigQuery, Clickhouse). Join on `answer_id` downstream. Do not mutate rows — feedback events are facts.
## Integration: Langfuse
```python
from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context
langfuse = Langfuse()
@observe()
def answer(q: str, session_id: str):
langfuse_context.update_current_trace(session_id=session_id, input=q)
docs = retriever.invoke(q)
langfuse_context.update_current_observation(
metadata={"retrieved": [d.metadata["id"] for d in docs]}
)
ans = llm.invoke(build_prompt(q, docs))
langfuse_context.update_current_trace(output=ans.content)
return ans.content, langfuse_context.get_current_trace_id()
# Later, from the UI:
def record_thumbs(trace_id: str, value: int):
langfuse.score(trace_id=trace_id, name="thumbs", value=value, data_type="BOOLEAN")
```
## Integration: LangSmith
```python
from langsmith import Client
client = Client()
def record_feedback(run_id: str, kind: str, value):
client.create_feedback(run_id, key=kind, score=value if kind == "thumbs" else None,
value=str(value))
```
## From Signal to Hard Negatives
Low-rated answers plus the chunks that were retrieved but not helpful are hard-negative training material for the embedding model.
```python
import polars as pl
def build_hard_negatives(days: int = 30):
df = pl.read_database("SELECT * FROM rag_events WHERE ts > now() - INTERVAL '30 days'")
fb = pl.read_database("SELECT * FROM feedback_events WHERE kind='thumbs'")
joined = df.join(fb, on="answer_id").filter(pl.col("value") == -1)
# Explicit negative signal: query + retrieved chunks that did not lead to a useful answer.
pairs = []
for row in joined.iter_rows(named=True):
for cid in row["retrieved_ids"][:5]:
pairs.append({"query": row["query"], "negative_id": cid})
return pairs
```
Pair hard negatives with user-accepted positive citations (citation_accept events) to build triples:
```python
def triples():
# (query, positive_chunk, negative_chunk)
...
```
## Embedding Fine-Tuning (Sentence-Transformers)
```python
from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader
model = SentenceTransformer("BAAI/bge-base-en-v1.5")
examples = [
InputExample(texts=[row["query"], row["positive_text"], row["negative_text"]])
for row in triples_df.iter_rows(named=True)
]
loader = DataLoader(examples, shuffle=True, batch_size=16)
loss = losses.TripletLoss(model=model, triplet_margin=0.3)
model.fit([(loader, loss)], epochs=3, warmup_steps=100)
model.save("models/bge-ft-v2")
```
Re-embed the corpus with the fine-tuned model (blue-green index swap). Measure retrieval P@5 and MRR on a held-out eval set before promoting.
## Reranker Fine-Tuning from CTR
The reranker ranks top-50. User clicks on citations at ranks 1-5 are a cheap pairwise preference signal.
```python
# Pairwise: if user clicked chunk at rank 3 and not chunks at ranks 1-2,
# then (q, chunk_3) > (q, chunk_1) and > (q, chunk_2).
from sentence_transformers import CrossEncoder
clicks_pairs = build_pairwise_from_clicks(days=30, min_per_query=2)
ce = CrossEncoder("BAAI/bge-reranker-base", num_labels=1)
train = [InputExample(texts=[q, doc], label=float(label)) for q, doc, label in clicks_pairs]
ce.fit(train_dataloader=DataLoader(train, shuffle=True, batch_size=16),
epochs=2, warmup_steps=200)
ce.save("models/reranker-ft-v2")
```
## Online Learning via Feature Store
For low-latency reranking blends (boost chunks user interacted with in prior sessions), use a feature store.
```python
# Feast example
from feast import FeatureStore
fs = FeatureStore(repo_path="feast_repo")
def enrich_with_user_features(user_id: str, candidate_ids: list[str]):
feats = fs.get_online_features(
features=["chunk:click_rate_7d", "user_chunk:last_seen_days"],
entity_rows=[{"user_id": user_id, "chunk_id": cid} for cid in candidate_ids],
).to_dict()
return feats
```
Blend online features into the reranker score (linear combination or LTR model).
## A/B Testing RAG Variants
```python
import hashlib
def assign_variant(session_id: str) -> str:
h = int(hashlib.md5(session_id.encode()).hexdigest(), 16)
return "B" if h % 100 < 50 else "A"
def retrieve(q: str, session_id: str):
variant = assign_variant(session_id)
if variant == "A":
return retriever_v1.invoke(q)
return retriever_v2.invoke(q)
```
Primary metric: thumbs-up rate per answer. Secondary: CTR on first citation, regenerate rate, dwell time. Guardrail: latency P95, cost per answer.
Sample size: for a thumbs-up rate of ~30% with detection of a 2 percentage point lift at 80% power, you need ~7k answers per variant. Run the experiment until both conditions hit the floor.
## Counterfactual / Off-Policy Evaluation
Most feedback is collected only on the variant actually shown. To evaluate a new ranker offline without a live experiment:
```python
# Inverse propensity scoring over logged clicks
# P(action | variant) used as the propensity weight.
def ips_estimator(logs, new_ranker):
total = 0
for row in logs:
new_rank = new_ranker.rank(row.query, row.chunk_id)
if new_rank == row.shown_rank:
total += row.reward / row.propensity
return total / len(logs)
```
Useful when a live A/B is too expensive. Validate with a small live test before full rollout.
## Anti-Patterns
| Anti-Pattern | Fix |
|---|---|
| Only thumbs up/down | Add implicit signals; explicit is too sparse |
| Conflating "regenerate" with negative feedback | Regenerate can mean "want different style"; look at next interaction |
| Fine-tuning on tiny data | Need >= 1-5k triples before fine-tuning helps Related in AI Agents
skill-development
IncludedComprehensive meta-skill for creating, managing, validating, auditing, and distributing Claude Code skills and slash commands (unified in v2.1.3+). Provides skill templates, creation workflows, validation patterns, audit checklists, naming conventions, YAML frontmatter guidance, progressive disclosure examples, and best practices lookup. Use when creating new skills, validating existing skills, auditing skill quality, understanding skill architecture, needing skill templates, learning about YAML frontmatter requirements, progressive disclosure patterns, tool restrictions (allowed-tools), skill composition, skill naming conventions, troubleshooting skill activation issues, creating custom slash commands, configuring command frontmatter, using command arguments ($ARGUMENTS, $1, $2), bash execution in commands, file references in commands, command namespacing, plugin commands, MCP slash commands, Skill tool configuration, or deciding between skills vs slash commands. Delegates to docs-management skill for official documentation.
reprompter
IncludedTransform messy prompts into well-structured, effective prompts — single or multi-agent. Use when: "reprompt", "reprompt this", "clean up this prompt", "structure my prompt", rough text needing XML tags and best practices, "reprompter teams", "repromptception", "run with quality", "smart run", "smart agents", multi-agent tasks, audits, parallel work, anything going to agent teams. Don't use when: simple Q&A, pure chat, immediate execution-only tasks. See "Don't Use When" section for details. Outputs: Structured XML/Markdown prompt, quality score (before/after), optional team brief + per-agent sub-prompts, agent team output files. Success criteria: Single mode quality score ≥ 7/10; Repromptception per-agent prompt quality score 8+/10; all required sections present, actionable and specific.
adaptive-compaction
IncludedAdaptive add-on policy and recovery layer that decides WHEN to compact, prune, snapshot, or fork -- replacing fixed-percent auto-compaction across Claude Code, Codex, and MCP-capable hosts. Trigger on auto-compact timing or damage: "when should I compact", "is it safe to compact now or start a fresh session", "auto-compact fires too early/mid-task", "switching to an unrelated task but the window still has space", "context rot", "answers get worse the longer the session runs", "the agent forgot the plan or my decisions after it summarized", "add a layer on top that manages context without changing the agent", raising autoCompactWindow to give the policy room, or installing/tuning a cross-tool compaction policy or PreCompact hook -- even when "compaction" is never said but the problem is context-window pressure or post-summarization memory loss. Do NOT use to summarize a conversation, build RAG, write a summarization prompt (decides WHEN not HOW), or answer max-context-length trivia.
agent-skill-creator
IncludedCreate cross-platform agent skills from workflow descriptions. Activates when users ask to create an agent, automate a repetitive workflow, create a custom skill, or need advanced agent creation. Triggers on phrases like create agent for, automate workflow, create skill for, every day I have to, daily I need to, turn process into agent, need to automate, create a cross-platform skill, validate this skill, export this skill, migrate this skill. Supports single skills, multi-agent suites, transcript processing, template-based creation, interactive configuration, cross-platform export, and spec validation.
llm-wiki
IncludedUse when building or maintaining a persistent personal knowledge base (second brain) in Obsidian where an LLM incrementally ingests sources, updates entity/concept pages, maintains cross-references, and keeps a synthesis current. Triggers include "second brain", "Obsidian wiki", "personal knowledge management", "ingest this paper/article/book", "build a research wiki", "compound knowledge", "Memex", or whenever the user wants knowledge to accumulate across sessions instead of being re-derived by RAG on every query.
skill-master
IncludedAgent Skills authoring, evaluation, and optimization. Create, edit, validate, benchmark, and improve skills following the agentskills.io specification. Use when designing SKILL.md files, structuring skill folders (references, scripts, assets), ingesting external documentation into skills, running trigger evals, benchmarking skill quality, optimizing descriptions, or performing blind A/B comparisons. Keywords: agentskills.io, SKILL.md, skill authoring, eval, benchmark, trigger optimization.