| name | perfmemory |
| description | Manage the PerfMemory lessons-learned layer — search for past fixes before debugging, store debug sessions and attempts during debugging, and ingest existing knowledge from debug manifests or lessons-learned documents. Use when the user mentions perfmemory, lessons learned, debug memory, ingesting debug manifests, or searching for past fixes. |
PerfMemory — Lessons Learned Memory Layer
When to Use This Skill
- User asks to search memory for past fixes before debugging
- User asks to store a debug session or attempt in memory
- User asks to ingest a debug manifest or lessons-learned document into memory
- User asks to review, verify, or archive stored lessons
- User asks for memory statistics or session details
- The
jmeter-debugging skill references PerfMemory integration steps
Taxonomy Compliance
Before storing any debug session or attempt, agents MUST check taxonomy.yaml for
canonical values. Do NOT invent new freeform values for taxonomy-controlled fields.
Rules
-
error_category — Check taxonomy.yaml → error_categories for an existing
canonical name or alias that matches the error. Use the canonical name. If the error
fits an existing category (even approximately), use it. Do NOT create a new category
name without confirming with the user first.
-
environment — Accepts either a specific environment name from
taxonomy.yaml → environments (e.g., "QA1", "STG-East") or a canonical type
from environment_types (e.g., qa, staging). The MCP resolves the input via
a 3-step process: (1) match specific environment name, (2) match canonical type,
(3) literal fallback. The env_type column is auto-derived — do NOT pass it
manually to store_debug_session.
-
auth_flow_type — Use values from taxonomy.yaml → auth_flow_types. Common
values: none, oauth_pkce, entra_id, saml, token_chain, custom_sso. If
unsure, check the taxonomy aliases list before guessing.
-
system_under_test — Use the canonical application name from taxonomy.yaml →
applications[].name. If the application is registered, use its exact canonical name.
What to Do When a Value Doesn't Match
- If the error/value clearly fits an existing category but uses different wording,
use the existing canonical name (the alias system handles resolution).
- If the value is genuinely new and doesn't fit any existing category, inform the user:
"This value is not in the taxonomy. Should I use [closest existing category] or would
you like to add a new entry to taxonomy.yaml first?"
- NEVER silently insert a non-canonical value without checking.
Reference
What PerfMemory Does
PerfMemory is a persistent memory layer backed by PostgreSQL + pgvector + Apache AGE.
It stores structured debug sessions, attempts, and vector embeddings of symptoms so
AI agents can recall past fixes via semantic similarity search and graph traversal.
Instead of starting every debug workflow from scratch, agents check memory first and
apply known fixes proactively.
How It Works
- Symptom text is embedded into a vector using the configured embedding provider
- The vector is stored alongside structured metadata (diagnosis, fix, outcome, etc.)
- Graph nodes and edges are created in the Apache AGE knowledge graph, linking
attempts to projects, error patterns, and fix patterns
- When a new symptom is encountered, it is embedded and compared against stored vectors
- Graph traversal finds structurally related issues across projects — even when
the symptom text is phrased differently
- Matches are ranked by a combined vector + graph score and returned with their fix details
Structured Symptom Text Template
The symptom_text field is the ONLY field that gets embedded as a vector. Its structure
directly affects search quality. Always use this format:
[Error Category] on [Sampler Name] — [Response Code].
[Error Message / Symptom Description].
[Brief Diagnosis Context].
Good example:
Missing Correlations (OAuth Parameters) on TC01_S02_GET /oauth2/authorize
— java.net.URISyntaxException. Illegal character in query caused by unresolved
${oauth_redirect_uri}, ${oauth_client_id}, ${oauth_response_mode}, ${oauth_nonce},
${oauth_state}. The redirect URL from GET /home contains all OAuth parameters
URL-encoded in the goto query parameter but no extractors existed.
Bad example (too vague):
OAuth error on login page. Variables not found.
Similarity Score Interpretation
| Score Range | Recommendation | Action |
|---|
> 0.85 or source: "both" | apply_known_fix | Apply the fix directly, especially if confirmed_count > 1 |
| 0.60 - 0.85 | review_suggestions | Present matches to user or review before applying |
| < 0.60 | no_match | No useful matches — proceed with normal debugging |
The default threshold is set in perfmemory-mcp/config.yaml under search.similarity_threshold.
Match Source Field
When graph is enabled, each match includes a source field:
| Source | Meaning |
|---|
vector | Found via pgvector cosine similarity only |
graph | Found via Apache AGE graph traversal only |
both | Found by both vector search and graph traversal (highest confidence) |
Related Rules
prerequisites.mdc — test_run_id is required for all workflows
skill-execution-rules.mdc — Follow steps in order, do not skip
mcp-error-handling.mdc — MCP tool error handling (no retry for code-based tools)
Workflow A — Search Memory Before Debugging
Use this workflow when an agent encounters an error and wants to check if a similar
issue has been solved before. This is called BEFORE starting a debug loop.
Collect Inputs
REQUIRED:
symptom_text = [the current error symptom — use the structured template above]
OPTIONAL:
system_under_test = [filter to a specific system, e.g. "Shopping Cart"]
system_alias = [filter by app alias/short name, e.g. "OSP", "CART"]
service_name = [filter by microservice name, e.g. "cart-service"]
error_category = [filter to a specific category, e.g. "Missing Correlations"]
Step 1 — Search for Similar Attempts
find_similar_attempts(
symptom_text = {symptom_text},
system_under_test = {system_under_test}, # optional
system_alias = {system_alias}, # optional — resolves to system_under_test
service_name = {service_name}, # optional
error_category = {error_category} # optional — aliases resolved automatically
)
Step 2 — Interpret Results
If recommendation = apply_known_fix (similarity > 0.85 or source = "both"):
- Review the top match's
diagnosis and fix_description
- If
confirmed_count > 1 and is_verified = true: apply the fix directly
- If
confirmed_count = 1 or is_verified = false: present to user for confirmation
- Check the
source field: matches found by "both" vector and graph are highest confidence
If recommendation = review_suggestions (similarity 0.60 - 0.85):
- Present the top 2-3 matches to the user with their diagnosis and fix
- Let the user decide which (if any) to apply
- If the user approves a fix, apply it and pass the
attempt_id as
matched_attempt_id when storing the new attempt (Workflow B)
If recommendation = no_match:
- Try
find_cross_project_patterns if error_category is known (Step 3)
- If still no results, proceed with normal debugging workflow
Step 3 — Cross-Project Pattern Search (Graph)
If vector search returned no_match but you have an error_category, check the
knowledge graph for cross-project patterns:
find_cross_project_patterns(
error_category = {error_category},
current_project = {system_under_test}, # optional — excludes own project
response_code = {response_code}, # optional — omit to match all response codes
enrich = true # optional — fetches full attempt details
)
If matches are returned:
- Review the
graph_path field to understand how the match was found
(e.g., ErrorPattern(Missing Correlations/*) or SIMILAR_TO(hops<=2))
- When
enrich = true (default), each match includes symptom_text, diagnosis,
fix_description, sampler_name, api_endpoint, confirmed_count, is_verified,
system_alias, service_name, test_case_id, and test_case_name
from the relational store — enough context to decide whether to apply the fix
- If a match looks promising, use
get_related_issues (Step 4) to explore its
neighborhood for additional related fixes
Step 4 — Explore Graph Neighborhood (Optional)
When a match from Step 2 or Step 3 looks relevant, explore its graph neighborhood
to discover additional related fixes that may not have surfaced in the initial search:
get_related_issues(
attempt_id = {matched_attempt_id},
include_same_project = true, # set false for cross-project only
enrich = true # optional — fetches full neighbor details
)
This returns:
- error_patterns: The error categories and response codes linked to this attempt
- fix_patterns: The fix types and component types that resolved it
- neighbors: Other attempts connected via SIMILAR_TO edges, with full details
when
enrich = true (symptom, diagnosis, fix, confidence signals)
Use this when:
- A match was found but you want to see if there are related fixes for the same class of issue
- You want to understand the full context of an error pattern before applying a fix
- You want to check if the same fix type has been applied successfully in other projects
Workflow B — Store Lessons During Debugging
Use this workflow during an active debug session to record what was tried and what
worked. This is integrated into the jmeter-debugging skill at specific steps.
Step 1 — Open a Debug Session
Called once at the start of debugging.
Before calling: Check taxonomy.yaml for the correct canonical values:
system_under_test → use applications[].name if the app is registered
environment → use a specific name from environments (e.g., "QA1", "STG-East")
or a canonical type from environment_types (e.g., "qa", "staging"). The MCP
auto-derives env_type from the input — do NOT pass env_type manually.
auth_flow_type → use a value from auth_flow_types (none, oauth_pkce, entra_id, saml, etc.)
REQUIRED:
system_under_test = [what is being tested, e.g. "Shopping Cart"]
test_run_id = [the artifact test run ID]
OPTIONAL:
system_alias = [short name/alias, e.g. "OSP"]
service_name = [microservice name, e.g. "cart-service"]
script_name = [the JMX filename]
auth_flow_type = [none, oauth_pkce, oauth_auth_code, saml, token_chain,
custom_sso, entra_id, msal_pkce, other]
auth_alias = [human-readable auth label, e.g. "Corporate SSO"]
environment = [specific env name (e.g. "QA1", "STG-East") or
canonical type (e.g. "qa", "staging") — env_type is
auto-derived via taxonomy resolution]
created_by = [PTE name or "cursor"]
notes = [freeform session notes]
strict_taxonomy = [true/false — override config-level strictness]
store_debug_session(
system_under_test = {system_under_test},
test_run_id = {test_run_id},
system_alias = {system_alias},
service_name = {service_name},
script_name = {script_name},
auth_flow_type = {auth_flow_type},
auth_alias = {auth_alias},
environment = {environment},
created_by = {created_by},
notes = {notes},
strict_taxonomy = {strict_taxonomy}
)
Save: session_id from the response.
Check: taxonomy_warnings in the response — if present, review and note for future taxonomy updates.
Step 2 — Store Each Debug Attempt
Called after each debug iteration (after applying a fix and observing the result).
Before calling: Check taxonomy.yaml → error_categories for an existing canonical
name or alias that matches the error. Use the canonical name. Do NOT invent new error
category names — if the error fits an existing category (even approximately), use it.
If genuinely new, ask the user before proceeding.
store_debug_attempt(
session_id = {session_id},
iteration_number = {iteration_count},
symptom_text = {structured symptom — use the template},
outcome = {resolved | failed | environment_issue | test_data_issue |
authentication_issue | needs_investigation},
error_category = {from taxonomy.yaml error_categories — use canonical name},
severity = {Critical | High | Medium},
response_code = {HTTP status code or exception name},
hostname = {host where the error occurred},
sampler_name = {the failing JMeter sampler},
api_endpoint = {the failing URL/endpoint},
diagnosis = {root cause determination — plain language},
fix_description = {what fix was applied — plain language},
fix_type = {add_extractor | move_extractor | edit_request_body |
edit_header | edit_correlation | other},
component_type = {json_extractor | regex_extractor | jsr223_postprocessor |
jsr223_preprocessor | http_sampler | test_plan | other},
test_case_id = {optional — e.g. "TC01"},
test_case_name = {optional — e.g. "User Login Flow"},
test_step_id = {optional — e.g. "S03"},
test_step_name = {optional — e.g. "Submit Credentials"},
manifest_excerpt = {optional — raw manifest iteration text},
matched_attempt_id = {optional — UUID of a memory match that was applied}
)
Save: attempt_id from the response.
Check: taxonomy_warnings in the response for unrecognized error categories.
If matched_attempt_id was provided, the response will also include
confirmed_match_id and new_confirmed_count showing the updated confidence.
Step 3 — Close the Debug Session
Called once when debugging completes (resolved or not).
close_debug_session(
session_id = {session_id},
final_outcome = {resolved | unresolved | environment_issue |
test_data_issue | authentication_issue |
iteration_limit_reached | needs_investigation},
resolution_attempt_id = {attempt_id of the fix that resolved the issue},
notes = {summary of remaining issues if unresolved}
)
Workflow C — Ingest Existing Knowledge
Use this workflow to bulk-load lessons from existing documents into perfmemory.
Supports two source formats.
Source 1: Debug Manifests
Debug manifests are markdown files at artifacts/{test_run_id}/analysis/debug_manifest.md
generated by the jmeter-debugging skill. Each iteration maps to one attempt.
Ingestion Steps:
- Read the manifest file
- Parse the header for session metadata:
Test Run ID → test_run_id
Script → script_name
Status → infer final_outcome
- Call
store_debug_session with the parsed metadata
system_under_test: ask the user if not obvious from the manifest
auth_flow_type: infer from the content (OAuth, SAML, CDSSO references)
environment: infer from hostnames (e.g., -stg. = "STG-East" or "staging")
- For each
## Iteration N section, parse:
- Error Identified →
error_category, sampler_name, response_code, error_message
- Diagnosis →
diagnosis
- Fix Applied →
fix_description, fix_type, component_type
- Result After Fix →
outcome (resolved if it improved, failed if it didn't)
- Build
symptom_text using the structured template from the parsed fields
- Call
store_debug_attempt for each iteration
- Call
close_debug_session with the final outcome
Field Mapping Reference:
| Manifest Field | PerfMemory Field |
|---|
| Iteration number | iteration_number |
| Sampler name from "Error Identified" | sampler_name |
| Response Code from "Error Identified" | response_code |
| Error Category from "Error Identified" | error_category |
| "Diagnosis" section | diagnosis |
| "Fix Applied" section | fix_description |
| Fix component type (JSR223, JSON Extractor, etc.) | component_type |
| Fix action (add extractor, edit body, etc.) | fix_type |
| "Result After Fix" section | outcome |
Source 2: Lessons-Learned Documents
Lessons-learned documents are curated pattern files (e.g. .md files)
that contain numbered lessons with symptoms, causes, and fixes. These represent
human-reviewed knowledge and are stored as verified attempts.
Ingestion Steps:
- Read the lessons-learned document
- Create a single debug session to group all lessons:
store_debug_session(
system_under_test = {inferred or ask user},
test_run_id = {document filename or ask user},
notes = "Ingested from lessons-learned document: {filename}"
)
- For each numbered lesson (e.g.,
## 1. Always Use follow_redirects...):
- Parse the lesson title as the
error_category
- Parse the description for
symptom_text (the problem pattern)
- Parse the Fix section for
fix_description
- Infer
fix_type and component_type from the fix content
- Set
outcome = "resolved" (these are known-good patterns)
- Call
store_debug_attempt
- For pre-debug checklist items (e.g., "Prerequisites: Pre-Debug Validation"):
- Each checklist item becomes a separate attempt
- Set
error_category = "Pre-Debug Validation"
- Set
outcome = "resolved"
- Call
close_debug_session with final_outcome = "resolved"
- Mark all attempts as human-verified:
verify_attempt(attempt_id = {each_attempt_id})
Symptom text for lessons-learned entries should capture the general pattern,
not a specific sampler or test case:
JMeter Cookie/Redirect Handling — auto_redirects=true delegates redirect handling
to Java's HttpURLConnection which does not use JMeter's CookieManager. SSO cookies
are lost during redirect chains, causing authentication to fail silently.
Maintenance Operations
Verify an Attempt (Human Review)
When a human confirms that a stored lesson is correct and reliable:
verify_attempt(attempt_id = {attempt_id})
Archive an Attempt (Outdated Lesson)
When a lesson becomes outdated (API changed, issue no longer occurs):
archive_attempt(
attempt_id = {attempt_id},
reason = {why this lesson is no longer valid}
)
Archived attempts remain in the database for audit but are excluded from search results.
View Memory Stats
get_memory_stats(
system_under_test = {optional — filter to a specific system}
)
Review a Session
get_session_detail(session_id = {session_id})
Browse Sessions
list_sessions(
system_under_test = {optional},
system_alias = {optional — filter by app alias},
service_name = {optional — filter by microservice},
environment = {optional — specific name (e.g. "QA1") or canonical type
(e.g. "qa"). Smart resolution: checks specific names first,
then canonical types, then literal match.},
env_type = {optional — direct filter on canonical type (e.g. "qa",
"staging"). No resolution — exact column match.},
final_outcome = {optional},
limit = 20
)
Explore Graph Neighborhood
View the graph connections for a specific attempt (requires graph.enabled: true).
Use this after finding a match to discover additional related fixes, or to understand
the structural context of a known issue before applying a fix.
get_related_issues(
attempt_id = {attempt_id},
max_hops = 2, # optional — graph traversal depth
include_same_project = true, # optional — include same-project neighbors
enrich = true # optional — fetches full neighbor details
)
Returns:
- error_patterns: list of
{error_category, response_code} linked to this attempt
- fix_patterns: list of
{fix_type, component_type} that resolved it
- neighbors: other attempts connected via SIMILAR_TO edges
When enrich = true, each neighbor includes symptom_text, diagnosis,
fix_description, sampler_name, api_endpoint, confirmed_count, is_verified,
system_alias, service_name, test_case_id, and test_case_name.
Cross-Project Pattern Search
Find if an error class has been resolved in other projects (requires graph.enabled: true).
Use this as a fallback when vector search returns no matches, or proactively when
starting work on a new project to check for known patterns.
find_cross_project_patterns(
error_category = {error_category},
current_project = {system_under_test}, # optional — excludes own project
response_code = {response_code}, # optional — omit to match all response codes
fix_type = {fix_type}, # optional
max_hops = 2, # optional
limit = 5, # optional
enrich = true # optional — fetches full attempt details
)
When enrich = true, each match includes symptom_text, diagnosis,
fix_description, sampler_name, api_endpoint, confirmed_count, is_verified,
system_alias, service_name, test_case_id, and test_case_name.
Error Handling
- If
store_debug_session or store_debug_attempt fails, report the error immediately.
Do NOT retry — these are code-based MCP tools (per mcp-error-handling.mdc).
- If
find_similar_attempts fails, proceed with normal debugging (memory is advisory).
- If graph tools (
find_cross_project_patterns, get_related_issues) fail or return
"Graph layer is not enabled", fall back to vector-only results. Graph is supplementary.
- If the database is unreachable, skip all perfmemory operations and inform the user.
Do not block the debugging workflow because memory is unavailable.
- Never modify the perfmemory MCP source code to work around errors.