Skip to main content
Glama

capture_feedback

Destructive

Collect structured feedback with up/down signals and reasons to improve system performance, storing vague inputs for clarification instead of immediate processing.

Instructions

Capture an up/down signal plus one line of why. Vague feedback is logged, then returned with a clarification prompt instead of memory promotion.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
signalYes
contextNoOne-sentence reason describing what worked or failed
whatWentWrongNo
whatToChangeNo
whatWorkedNo
tagsNo
skillNo
rubricScoresNo
guardrailsNo

Implementation Reference

  • The primary implementation of the `captureFeedback` tool, which processes feedback signals, performs validations (like rubric checks), handles memory promotion, and manages ML side-effects.
    function captureFeedback(params) {
      const { FEEDBACK_LOG_PATH, MEMORY_LOG_PATH, FEEDBACK_DIR } = getFeedbackPaths();
      const signal = normalizeSignal(params.signal);
      if (!signal) {
        return {
          accepted: false,
          reason: `Invalid signal "${params.signal}". Use up/down or positive/negative.`,
        };
      }
    
      const context = params.context || '';
      extractAndSetConstraints(context);
    
      const providedTags = Array.isArray(params.tags)
        ? params.tags
        : String(params.tags || '')
            .split(',')
            .map((t) => t.trim())
            .filter(Boolean);
    
      const semanticTags = inferSemanticTags(context);
      const tags = Array.from(new Set([...providedTags, ...semanticTags]));
    
      let rubricEvaluation = null;
      try {
        if (params.rubricScores != null || params.guardrails != null) {
          rubricEvaluation = buildRubricEvaluation({
            rubricScores: params.rubricScores,
            guardrails: parseOptionalObject(params.guardrails, 'guardrails'),
          });
        }
      } catch (err) {
        return {
          accepted: false,
          reason: `Invalid rubric payload: ${err.message}`,
        };
      }
    
      const action = resolveFeedbackAction({
        signal,
        context: params.context || '',
        whatWentWrong: params.whatWentWrong,
        whatToChange: params.whatToChange,
        whatWorked: params.whatWorked,
        reasoning: params.reasoning,
        visualEvidence: params.visualEvidence,
        tags,
        rubricEvaluation,
      });
    
      // Tool-call attribution: link feedback to specific action (#203)
      const lastAction = params.lastAction
        ? {
          tool: params.lastAction.tool || 'unknown',
          contextKey: params.lastAction.contextKey || null,
          file: params.lastAction.file || null,
          timestamp: params.lastAction.timestamp || null,
        }
        : null;
    
      const now = new Date().toISOString();
      const rawFeedbackEvent = {
        id: `fb_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
        signal,
        context: params.context || '',
        lastAction,
        whatWentWrong: params.whatWentWrong || null,
        whatToChange: params.whatToChange || null,
        whatWorked: params.whatWorked || null,
        reasoning: params.reasoning || null,
        visualEvidence: params.visualEvidence || null,
        tags,
        skill: params.skill || null,
        rubric: rubricEvaluation
          ? {
            rubricId: rubricEvaluation.rubricId,
            weightedScore: rubricEvaluation.weightedScore,
            failingCriteria: rubricEvaluation.failingCriteria,
            failingGuardrails: rubricEvaluation.failingGuardrails,
            judgeDisagreements: rubricEvaluation.judgeDisagreements,
            promotionEligible: rubricEvaluation.promotionEligible,
          }
          : null,
        actionType: action.type,
        actionReason: action.reason || null,
        timestamp: now,
      };
    
      // Rich context enrichment (QUAL-02, QUAL-03) — non-blocking
      let feedbackEvent = enrichFeedbackContext(rawFeedbackEvent, params);
      const shouldDiagnose = signal === 'negative'
        || (rubricEvaluation && (
          (rubricEvaluation.failingCriteria || []).length > 0
          || (rubricEvaluation.failingGuardrails || []).length > 0
        ))
        || (typeof rawFeedbackEvent.actionReason === 'string' && /rubric gate/i.test(rawFeedbackEvent.actionReason));
      const diagnosis = shouldDiagnose
        ? diagnoseFailure({
          step: 'feedback_capture',
          context,
          rubricEvaluation,
          feedbackEvent,
          suspect: signal === 'negative' || action.type === 'no-action',
        })
        : null;
      const storedDiagnosis = toStoredDiagnosis(diagnosis);
      if (storedDiagnosis) {
        feedbackEvent = {
          ...feedbackEvent,
          diagnosis: storedDiagnosis,
        };
      }
      const historyEntries = readJSONL(FEEDBACK_LOG_PATH).slice(-SEQUENCE_WINDOW);
    
      const summary = loadSummary();
      summary.total += 1;
      summary[signal] += 1;
    
      if (action.type === 'no-action') {
        const clarification = buildClarificationMessage({
          signal,
          context: params.context || '',
          whatWentWrong: params.whatWentWrong,
          whatToChange: params.whatToChange,
          whatWorked: params.whatWorked,
        });
        summary.rejected += 1;
        summary.lastUpdated = now;
        saveSummary(summary);
        appendJSONL(FEEDBACK_LOG_PATH, feedbackEvent);
        try {
          appendSequence(historyEntries, feedbackEvent, getFeedbackPaths(), { accepted: false });
        } catch {
          // Sequence tracking failure is non-critical
        }
        try {
          const riskScorer = getRiskScorerModule();
          if (riskScorer) {
            riskScorer.trainAndPersistRiskModel(FEEDBACK_DIR);
          }
        } catch {
          // Risk model refresh is non-critical
        }
        return {
          accepted: false,
          status: clarification ? 'clarification_required' : 'rejected',
          reason: action.reason,
          message: clarification ? clarification.message : 'Signal logged, but reusable memory was not created.',
          feedbackEvent,
          ...(clarification || {}),
        };
      }
    
      const prepared = prepareForStorage(action.memory);
      if (!prepared.ok) {
        summary.rejected += 1;
        summary.lastUpdated = now;
        saveSummary(summary);
        appendJSONL(FEEDBACK_LOG_PATH, {
          ...feedbackEvent,
          validationIssues: prepared.issues,
        });
        try {
          appendSequence(historyEntries, feedbackEvent, getFeedbackPaths(), { accepted: false });
        } catch {
          // Sequence tracking failure is non-critical
        }
        try {
          const riskScorer = getRiskScorerModule();
          if (riskScorer) {
            riskScorer.trainAndPersistRiskModel(FEEDBACK_DIR);
          }
        } catch {
          // Risk model refresh is non-critical
        }
        return {
          accepted: false,
          status: 'rejected',
          reason: `Schema validation failed: ${prepared.issues.join('; ')}`,
          message: 'Signal logged, but reusable memory was not created.',
          feedbackEvent,
          issues: prepared.issues,
        };
      }
    
      const memoryRecord = {
        id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
        ...prepared.memory,
        diagnosis: storedDiagnosis,
        sourceFeedbackId: feedbackEvent.id,
        timestamp: now,
      };
    
      // Bayesian Belief Update (Project Bayes)
      try {
        const { updateBelief, shouldPrune } = require('./belief-update');
        const existingMemories = readJSONL(MEMORY_LOG_PATH);
        const similarMemory = existingMemories.slice().reverse().find(m => 
          m.tags && m.tags.some(t => memoryRecord.tags.includes(t) && !GENERIC_TAGS.has(t))
        );
    
        if (similarMemory && similarMemory.bayesian) {
          const likelihood = signal === 'positive' ? 0.9 : 0.1;
          memoryRecord.bayesian = updateBelief(similarMemory.bayesian, likelihood);
          memoryRecord.revisedFromId = similarMemory.id;
          
          if (shouldPrune(memoryRecord.bayesian)) {
            memoryRecord.pruned = true;
            memoryRecord.pruneReason = 'high_entropy_contradiction';
          }
        }
      } catch (_err) { /* bayesian update is non-blocking */ }
    
      appendJSONL(FEEDBACK_LOG_PATH, feedbackEvent);
      appendJSONL(MEMORY_LOG_PATH, memoryRecord);
    
      const contextFs = getContextFsModule();
      if (contextFs && typeof contextFs.registerFeedback === 'function') {
        try {
          contextFs.registerFeedback(feedbackEvent, memoryRecord);
        } catch {
          // Non-critical; feedback remains in primary logs
        }
      }
    
      // ML side-effects: sequence tracking and diversity (non-blocking — primary write already succeeded)
      const mlPaths = getFeedbackPaths();
      try {
        appendSequence(historyEntries, feedbackEvent, mlPaths, { accepted: true });
      } catch (err) {
        // Sequence tracking failure is non-critical
      }
      try {
        updateDiversityTracking(feedbackEvent, mlPaths);
      } catch (err) {
        // Diversity tracking failure is non-critical
      }
    
      // Vector storage side-effect (non-blocking — primary write already succeeded)
      const vectorStore = getVectorStoreModule();
      if (vectorStore && typeof vectorStore.upsertFeedback === 'function') {
        trackBackgroundSideEffect(vectorStore.upsertFeedback(feedbackEvent));
      }
    
      // RLAIF self-audit side-effect (non-blocking — 4th enrichment layer)
      try {
        const sam = getSelfAuditModule();
        if (sam) sam.selfAuditAndLog(feedbackEvent, mlPaths);
      } catch (_err) { /* non-critical */ }
    
      // Boosted risk model refresh — local, file-based, and non-blocking
      try {
        const riskScorer = getRiskScorerModule();
        if (riskScorer) {
          riskScorer.trainAndPersistRiskModel(FEEDBACK_DIR);
        }
      } catch (_err) { /* non-critical */ }
    
      // Attribution side-effects — fire-and-forget, never throw
      try {
        const toolName = feedbackEvent.toolName || feedbackEvent.tool_name || 'unknown';
        const toolInput = feedbackEvent.context || feedbackEvent.input || '';
        recordAction(toolName, toolInput);
        if (feedbackEvent.signal === 'negative') {
          attributeFeedback('negative', feedbackEvent.context || '');
        } else if (feedbackEvent.signal === 'positive') {
          attributeFeedback('positive', feedbackEvent.context || '');
        }
      } catch (e) {
        // attribution is non-blocking
      }
    
      // Auto-promote gates on negative feedback — non-blocking
      if (feedbackEvent.signal === 'negative') {
        try {
          const autoPromote = require('./auto-promote-gates');
          autoPromote.promote(FEEDBACK_LOG_PATH);
        } catch (_err) {
          // Gate promotion is non-critical — never fail the capture pipeline
        }
      }
    
      summary.accepted += 1;
      summary.lastUpdated = now;
      saveSummary(summary);
    
      return {
        accepted: true,
        status: 'promoted',
        message: 'Feedback promoted to reusable memory.',
        feedbackEvent,
        memoryRecord,
      };
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations indicate 'destructiveHint: true', which the description does not contradict. The description adds valuable behavioral context beyond annotations: it explains that vague feedback is logged and triggers a clarification prompt, and it distinguishes outcomes based on feedback quality. This provides insight into the tool's processing logic and response behavior, compensating for the lack of other annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and front-loaded, consisting of two sentences that directly state the tool's purpose and behavioral outcome. There is no wasted language, and each sentence adds critical information, making it efficient and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (9 parameters, nested objects, no output schema) and minimal annotations, the description is partially complete. It covers the core purpose and some behavioral aspects but lacks details on parameter usage, return values, and how it integrates with sibling tools. This results in a baseline adequacy with clear gaps in context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With a low schema description coverage of 11% (only one parameter has a description), the description does not adequately compensate. It mentions 'up/down signal' and 'one line of why,' which loosely relate to 'signal' and 'context' parameters, but ignores the other 7 parameters (e.g., 'whatWentWrong', 'tags', 'rubricScores'). This leaves most parameters undocumented, failing to add sufficient meaning beyond the sparse schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Capture an up/down signal plus one line of why.' It specifies the verb ('capture') and resource ('feedback'), but does not explicitly differentiate it from sibling tools like 'capture_memory_feedback' or 'feedback_stats', which prevents a score of 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage by mentioning that 'vague feedback is logged' and results in 'a clarification prompt instead of memory promotion,' suggesting it's for capturing feedback that may need refinement. However, it does not provide explicit guidance on when to use this tool versus alternatives like 'capture_memory_feedback' or 'feedback_summary', leaving room for ambiguity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/IgorGanapolsky/mcp-memory-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server