Skip to main content
Glama

twining_status

Check overall health of the Twining state by displaying blackboard entries, decision counts, graph entities, actionable warnings, and a human-readable summary.

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.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The implementation of the `twining_status` tool handler.
    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.",
      },
      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",
          );
        }
      },
    );

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