Skip to main content
Glama

capture_feedback

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,
      };
    }

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