Skip to main content
Glama
carloshpdoc

memorydetective

Count instances reachable from a specific cycle root

reachableFromCycle

Count reachable objects from a chosen retain cycle root to distinguish the cycle culprit from its dependencies. Returns per-class occurrence counts and total reachable nodes.

Instructions

[mg.memory] Cycle-scoped reachability + class counting. Answers questions like "how many NSURLSessionConfiguration instances are reachable from the cycle rooted at DetailViewModel?" — distinguishing the actual culprit (the cycle root) from its retained dependencies. Pick a cycle by zero-based cycleIndex or by rootClassName substring. Returns per-class counts ranked by occurrence, plus the total reachable node count.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYesAbsolute path to a `.memgraph` file.
cycleIndexNoZero-based index of the ROOT CYCLE to scope to. Mutually exclusive with `rootClassName`. When neither is given, defaults to cycle index 0.
rootClassNameNoSubstring of the root cycle's class name (e.g. "DetailViewModel"). Picks the first ROOT CYCLE whose root matches. Mutually exclusive with `cycleIndex`.
classNameNoOptional filter — only count nodes whose className contains this substring. When omitted, returns the full per-class breakdown.
topNNoCap on per-class entries returned (default 20).
verbosityNoClass-name verbosity for the response. See analyzeMemgraph for the same flag.compact

Implementation Reference

  • Main async handler that runs leaks + parse, then delegates to the pure reachableFromReport function.
    export async function reachableFromCycle(
      input: ReachableFromCycleInput,
    ): Promise<ReachableFromCycleResult> {
      if (input.cycleIndex !== undefined && input.rootClassName !== undefined) {
        throw new Error(
          "Provide either `cycleIndex` or `rootClassName`, not both.",
        );
      }
      const { report, resolvedPath } = await runLeaksAndParse(input.path);
      return reachableFromReport(report, resolvedPath, input);
    }
  • Pure function that computes the reachability result from a parsed LeaksReport (exposed for testing).
    export function reachableFromReport(
      report: LeaksReport,
      path: string,
      input: ReachableFromCycleInput,
    ): ReachableFromCycleResult {
      const picked = pickCycle(report, input);
      if (!picked) {
        throw new Error(
          input.rootClassName
            ? `No ROOT CYCLE found whose root class contains "${input.rootClassName}".`
            : `No ROOT CYCLE at index ${input.cycleIndex ?? 0}. Available roots: ${rootCyclesOnly(report.cycles).length}.`,
        );
      }
    
      const verbosity = input.verbosity ?? "compact";
      const { byClass, total } = countReachableFromNode(picked.node, verbosity);
    
      let entries: ClassCount[] = Array.from(byClass.entries()).map(([n, c]) => ({
        className: n,
        count: c,
      }));
    
      if (input.className) {
        entries = entries.filter((e) =>
          e.className.includes(input.className!),
        );
      }
      entries.sort((a, b) => b.count - a.count);
      entries = entries.slice(0, input.topN ?? 20);
    
      // Find a class that looks "app-level" (not stdlib / SwiftUI internal) to
      // suggest as the followup target. The cycle root is the canonical answer
      // when it itself is app-level; otherwise we walk the counts.
      const suggestedNextCalls: NextCallSuggestion[] = [];
      const rootShort = shortenForVerbosity(picked.node.className, verbosity);
      const candidate =
        pickPrimaryAppClass([rootShort]) ??
        pickPrimaryAppClass(entries.map((e) => e.className));
      if (candidate) {
        suggestedNextCalls.push(suggestionGetDefinition({ symbolName: candidate }));
        suggestedNextCalls.push(suggestionFindReferences({ symbolName: candidate }));
      }
    
      return {
        ok: true,
        path,
        cycle: {
          index: picked.index,
          rootClass: rootShort,
          rootAddress: picked.node.address,
          totalReachable: total,
        },
        counts: entries,
        uniqueClasses: byClass.size,
        ...(suggestedNextCalls.length > 0 ? { suggestedNextCalls } : {}),
      };
    }
  • Zod schema defining the input parameters: path, cycleIndex, rootClassName, className, topN, verbosity.
    export const reachableFromCycleSchema = z.object({
      path: z.string().min(1).describe("Absolute path to a `.memgraph` file."),
      cycleIndex: z
        .number()
        .int()
        .nonnegative()
        .optional()
        .describe(
          "Zero-based index of the ROOT CYCLE to scope to. Mutually exclusive with `rootClassName`. When neither is given, defaults to cycle index 0.",
        ),
      rootClassName: z
        .string()
        .optional()
        .describe(
          "Substring of the root cycle's class name (e.g. \"DetailViewModel\"). Picks the first ROOT CYCLE whose root matches. Mutually exclusive with `cycleIndex`.",
        ),
      className: z
        .string()
        .optional()
        .describe(
          "Optional filter — only count nodes whose className contains this substring. When omitted, returns the full per-class breakdown.",
        ),
      topN: z
        .number()
        .int()
        .positive()
        .max(100)
        .default(20)
        .describe("Cap on per-class entries returned (default 20)."),
      verbosity: z
        .enum(["compact", "normal", "full"])
        .default("compact")
        .describe(
          "Class-name verbosity for the response. See analyzeMemgraph for the same flag.",
        ),
    });
  • src/index.ts:411-425 (registration)
    Registration of the 'reachableFromCycle' tool with MCP server, including title, description, inputSchema, and handler.
    server.registerTool(
      "reachableFromCycle",
      {
        title: "Count instances reachable from a specific cycle root",
        description:
          "[mg.memory] Cycle-scoped reachability + class counting. Answers questions like \"how many `NSURLSessionConfiguration` instances are reachable from the cycle rooted at `DetailViewModel`?\" — distinguishing the actual culprit (the cycle root) from its retained dependencies. Pick a cycle by zero-based `cycleIndex` or by `rootClassName` substring. Returns per-class counts ranked by occurrence, plus the total reachable node count.",
        inputSchema: reachableFromCycleSchema.shape,
      },
      async (input) => {
        const result = await reachableFromCycle(input);
        return {
          content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
        };
      },
    );
  • Helper function that builds a NextCallSuggestion to chain reachableFromCycle after analyzeMemgraph.
    /** Build suggestion to call reachableFromCycle (used after analyzeMemgraph). */
    export function suggestionReachableFromCycle(opts: {
      path: string;
      cycleIndex?: number;
    }): NextCallSuggestion {
      return {
        tool: "reachableFromCycle",
        args: {
          path: opts.path,
          cycleIndex: opts.cycleIndex ?? 0,
        },
        why: "Confirm which app-level class is the actual culprit (the cycle root) versus collateral retained dependencies.",
      };
    }
Behavior4/5

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

No annotations are provided, so the description carries full burden. It discloses the tool is read-only (no mutation implied), scopes reachability to a cycle, and returns per-class counts plus total. It does not detail error conditions (e.g., invalid index/class) but otherwise provides adequate behavioral context for an agent.

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 four well-structured sentences: statement of purpose, example, parameter guidance, and output description. Every sentence adds value, no redundancy, and it is appropriately front-loaded.

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

Completeness4/5

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

Given the complexity (6 parameters, no output schema, no annotations), the description covers input selection and output shape adequately. It could be more complete with an example response structure, but the provided details (per-class counts ranked, total reachable node count) sufficiently inform an agent.

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

Parameters3/5

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

The input schema has 100% description coverage, so baseline is 3. The description adds a practical example ('Pick a cycle by zero-based cycleIndex or by rootClassName substring') but does not significantly augment parameter meanings beyond what the schema already provides.

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

Purpose5/5

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

The description clearly states the tool counts instances reachable from a cycle root, with a concrete example ('how many NSURLSessionConfiguration instances are reachable from the cycle rooted at DetailViewModel?'). It distinguishes itself from sibling tools like findCycles or classifyCycle by focusing on reachability counting per class, explicitly noting it 'distinguishes the actual culprit (the cycle root) from its retained dependencies.'

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

Usage Guidelines4/5

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

The description explains how to select a cycle by cycleIndex or rootClassName, and notes default behavior when neither is given. It provides context for when to use this tool (to get per-class counts of instances reachable from a known cycle). However, it does not explicitly state when not to use it or suggest alternatives among the sibling tools.

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/carloshpdoc/memorydetective'

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