Skip to main content
Glama

twining_status

Retrieve detailed Twining state health check including blackboard entry count, decision counts, graph entity/relation counts, and actionable warnings.

Instructions

Overall health check of the Twining state. Shows blackboard entry count, decision counts, graph entity/relation counts, actionable warnings, and a human-readable summary. Note: twining_assemble now includes a status summary — use this only when you need the full detailed health check.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The main handler function for the twining_status tool. Gathers blackboard entries, decision counts, graph entity/relation counts, agent stats, checks for stale provisionals, archiving needs, orphan entities, and returns a structured health report with warnings and summary.
      async () => {
        try {
          // Get project name from parent directory
          const projectRoot = path.dirname(twiningDir);
          const project = path.basename(projectRoot);
    
          // Count blackboard entries
          const { total_count: blackboard_entries } =
            await blackboardStore.read();
    
          // Count decisions by status
          const index = await decisionStore.getIndex();
          const active_decisions = index.filter(
            (e) => e.status === "active",
          ).length;
          const provisional_decisions = index.filter(
            (e) => e.status === "provisional",
          ).length;
    
          // Graph counts
          const entities = await graphStore.getEntities();
          const relations = await graphStore.getRelations();
          const graph_entities = entities.length;
          const graph_relations = relations.length;
    
          // Find last activity timestamp
          const recentEntries = await blackboardStore.recent(1);
          const lastBBActivity =
            recentEntries.length > 0 ? recentEntries[0]!.timestamp : null;
          const lastDecisionActivity =
            index.length > 0
              ? index.reduce((latest, e) =>
                  e.timestamp > latest ? e.timestamp : latest,
                index[0]!.timestamp)
              : null;
    
          let last_activity = "none";
          if (lastBBActivity && lastDecisionActivity) {
            last_activity =
              lastBBActivity > lastDecisionActivity
                ? lastBBActivity
                : lastDecisionActivity;
          } else if (lastBBActivity) {
            last_activity = lastBBActivity;
          } else if (lastDecisionActivity) {
            last_activity = lastDecisionActivity;
          }
    
          // Archiving threshold
          const archiveThreshold =
            config.archive.max_blackboard_entries_before_archive;
          const needs_archiving = blackboard_entries >= archiveThreshold;
    
          // Actionable warnings
          const warnings: string[] = [];
    
          // Stale provisionals: older than 7 days
          const sevenDaysAgo = new Date(
            Date.now() - 7 * 24 * 60 * 60 * 1000,
          ).toISOString();
          const staleProvisionals = index.filter(
            (e) =>
              e.status === "provisional" && e.timestamp < sevenDaysAgo,
          );
          if (staleProvisionals.length > 0) {
            warnings.push(
              `${staleProvisionals.length} provisional decisions older than 7 days need resolution`,
            );
          }
    
          // Archive needed
          if (needs_archiving) {
            warnings.push(
              `Blackboard has ${blackboard_entries} entries, archive recommended (threshold: ${archiveThreshold})`,
            );
          }
    
          // Orphan entities: entities with zero relations
          if (graph_entities > 0) {
            const entityIds = new Set(entities.map((e) => e.id));
            const connectedIds = new Set<string>();
            for (const r of relations) {
              connectedIds.add(r.source);
              connectedIds.add(r.target);
            }
            const orphanCount = [...entityIds].filter(
              (id) => !connectedIds.has(id),
            ).length;
            if (orphanCount > 0) {
              warnings.push(
                `${orphanCount} graph entities have no relations`,
              );
            }
          }
    
          // Agent counts
          let registered_agents = 0;
          let active_agents = 0;
          if (agentStore) {
            const agents = await agentStore.getAll();
            registered_agents = agents.length;
            const thresholds =
              config.agents?.liveness ?? DEFAULT_LIVENESS_THRESHOLDS;
            const now = new Date();
            active_agents = agents.filter(
              (a) =>
                computeLiveness(a.last_active, now, thresholds) === "active",
            ).length;
          }
    
          // Build summary string
          const healthStatus =
            warnings.length === 0 ? "Healthy" : "Needs attention";
          const warningsSummary =
            warnings.length > 0 ? ` ${warnings.join(". ")}.` : "";
          const agentSummary = ` ${registered_agents} registered agents (${active_agents} active).`;
          const summary = `${healthStatus}. ${blackboard_entries} blackboard entries, ${active_decisions} active decisions, ${graph_entities} graph entities.${agentSummary}${warningsSummary}`;
    
          return toolResult({
            project,
            blackboard_entries,
            active_decisions,
            provisional_decisions,
            graph_entities,
            graph_relations,
            registered_agents,
            active_agents,
            last_activity,
            needs_archiving,
            warnings,
            summary,
          });
        } catch (e) {
          return toolError(
            e instanceof Error ? e.message : "Unknown error",
            "INTERNAL_ERROR",
          );
        }
      },
    );
  • The registerLifecycleTools function registers 'twining_status' (and 'twining_archive') on the MCP server. It is called from src/server.ts when toolMode is 'full'.
    export function registerLifecycleTools(
      server: McpServer,
      twiningDir: string,
      blackboardStore: BlackboardStore,
      decisionStore: DecisionStore,
      graphStore: GraphStore,
      archiver: Archiver,
      config: TwiningConfig,
      agentStore: AgentStore | null = null,
    ): void {
      // twining_status — Overall health check of the Twining state
      server.registerTool(
        "twining_status",
        {
          description:
            "Overall health check of the Twining state. Shows blackboard entry count, decision counts, graph entity/relation counts, actionable warnings, and a human-readable summary. Note: twining_assemble now includes a status summary — use this only when you need the full detailed health check.",
        },
        async () => {
          try {
            // Get project name from parent directory
            const projectRoot = path.dirname(twiningDir);
            const project = path.basename(projectRoot);
    
            // Count blackboard entries
            const { total_count: blackboard_entries } =
              await blackboardStore.read();
    
            // Count decisions by status
            const index = await decisionStore.getIndex();
            const active_decisions = index.filter(
              (e) => e.status === "active",
            ).length;
            const provisional_decisions = index.filter(
              (e) => e.status === "provisional",
            ).length;
    
            // Graph counts
            const entities = await graphStore.getEntities();
            const relations = await graphStore.getRelations();
            const graph_entities = entities.length;
            const graph_relations = relations.length;
    
            // Find last activity timestamp
            const recentEntries = await blackboardStore.recent(1);
            const lastBBActivity =
              recentEntries.length > 0 ? recentEntries[0]!.timestamp : null;
            const lastDecisionActivity =
              index.length > 0
                ? index.reduce((latest, e) =>
                    e.timestamp > latest ? e.timestamp : latest,
                  index[0]!.timestamp)
                : null;
    
            let last_activity = "none";
            if (lastBBActivity && lastDecisionActivity) {
              last_activity =
                lastBBActivity > lastDecisionActivity
                  ? lastBBActivity
                  : lastDecisionActivity;
            } else if (lastBBActivity) {
              last_activity = lastBBActivity;
            } else if (lastDecisionActivity) {
              last_activity = lastDecisionActivity;
            }
    
            // Archiving threshold
            const archiveThreshold =
              config.archive.max_blackboard_entries_before_archive;
            const needs_archiving = blackboard_entries >= archiveThreshold;
    
            // Actionable warnings
            const warnings: string[] = [];
    
            // Stale provisionals: older than 7 days
            const sevenDaysAgo = new Date(
              Date.now() - 7 * 24 * 60 * 60 * 1000,
            ).toISOString();
            const staleProvisionals = index.filter(
              (e) =>
                e.status === "provisional" && e.timestamp < sevenDaysAgo,
            );
            if (staleProvisionals.length > 0) {
              warnings.push(
                `${staleProvisionals.length} provisional decisions older than 7 days need resolution`,
              );
            }
    
            // Archive needed
            if (needs_archiving) {
              warnings.push(
                `Blackboard has ${blackboard_entries} entries, archive recommended (threshold: ${archiveThreshold})`,
              );
            }
    
            // Orphan entities: entities with zero relations
            if (graph_entities > 0) {
              const entityIds = new Set(entities.map((e) => e.id));
              const connectedIds = new Set<string>();
              for (const r of relations) {
                connectedIds.add(r.source);
                connectedIds.add(r.target);
              }
              const orphanCount = [...entityIds].filter(
                (id) => !connectedIds.has(id),
              ).length;
              if (orphanCount > 0) {
                warnings.push(
                  `${orphanCount} graph entities have no relations`,
                );
              }
            }
    
            // Agent counts
            let registered_agents = 0;
            let active_agents = 0;
            if (agentStore) {
              const agents = await agentStore.getAll();
              registered_agents = agents.length;
              const thresholds =
                config.agents?.liveness ?? DEFAULT_LIVENESS_THRESHOLDS;
              const now = new Date();
              active_agents = agents.filter(
                (a) =>
                  computeLiveness(a.last_active, now, thresholds) === "active",
              ).length;
            }
    
            // Build summary string
            const healthStatus =
              warnings.length === 0 ? "Healthy" : "Needs attention";
            const warningsSummary =
              warnings.length > 0 ? ` ${warnings.join(". ")}.` : "";
            const agentSummary = ` ${registered_agents} registered agents (${active_agents} active).`;
            const summary = `${healthStatus}. ${blackboard_entries} blackboard entries, ${active_decisions} active decisions, ${graph_entities} graph entities.${agentSummary}${warningsSummary}`;
    
            return toolResult({
              project,
              blackboard_entries,
              active_decisions,
              provisional_decisions,
              graph_entities,
              graph_relations,
              registered_agents,
              active_agents,
              last_activity,
              needs_archiving,
              warnings,
              summary,
            });
          } catch (e) {
            return toolError(
              e instanceof Error ? e.message : "Unknown error",
              "INTERNAL_ERROR",
            );
          }
        },
      );
  • Input schema for twining_status — no input parameters, only a description showing what the tool returns.
    {
      description:
        "Overall health check of the Twining state. Shows blackboard entry count, decision counts, graph entity/relation counts, actionable warnings, and a human-readable summary. Note: twining_assemble now includes a status summary — use this only when you need the full detailed health check.",
    },
  • src/server.ts:220-233 (registration)
    Where registerLifecycleTools is called, enabling twining_status in 'full' tool mode.
    // Extended tools (full mode only)
    if (toolMode === "full") {
      registerLifecycleTools(
        server,
        twiningDir,
        blackboardStore,
        decisionStore,
        graphStore,
        archiver,
        config,
        agentStore,
      );
      registerGraphTools(server, graphEngine);
    }
Behavior4/5

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

No annotations provided, but description implies read-only behavior by saying 'shows' and listing outputs. Adds context beyond basic status, but doesn't specify side effects or permissions.

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?

Two concise sentences: first describes purpose and outputs, second provides usage guidance. Front-loaded with key information, no wasted words.

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

Completeness5/5

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

Comprehensive for a zero-parameter health check tool: describes what it shows, includes usage guidance, and no output schema needed since return values are described adequately.

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

Parameters4/5

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

No parameters in input schema, so description adds value by detailing what the tool returns. Baseline 4 is appropriate as no parameter info needed beyond schema.

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?

Clearly states it performs a health check of Twining state, listing specific outputs like blackboard entry count, decision counts, etc. Differentiates from sibling tool twining_assemble.

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

Usage Guidelines5/5

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

Explicitly tells when to use this tool (need full detailed health check) and when not to (use twining_assemble for status summary), providing a clear alternative.

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/daveangulo/twining-mcp'

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