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",
          );
        }
      },
    );
Behavior3/5

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

With no annotations provided, the description carries full burden. It discloses that this is a read-only operation ('shows', 'check') and describes what information is returned, but doesn't mention behavioral traits like rate limits, authentication needs, or whether it's a lightweight vs. heavy operation. It provides basic context but lacks operational details.

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 a single, well-structured sentence that efficiently lists all key outputs (blackboard entries, decisions, graph entities/relations, warnings, summary). Every element earns its place by specifying what information the health check provides.

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?

For a parameterless status tool with no output schema, the description provides good completeness by listing the specific metrics and summary it returns. It could be slightly more complete by mentioning the format of the return data (e.g., structured JSON vs. text), but covers the essential information needed to understand what the tool does.

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?

The tool has zero parameters, and schema description coverage is 100% (empty schema). The description appropriately doesn't discuss parameters since none exist. It focuses instead on what the tool returns, which is appropriate for a parameterless status tool.

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's purpose with specific verbs ('health check', 'shows') and resources ('Twining state'), listing concrete outputs like blackboard entry count and decision counts. It distinguishes itself from siblings by focusing on overall system status rather than specific operations like adding entities or querying graphs.

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

Usage Guidelines3/5

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

The description implies usage for checking system health and status, but doesn't explicitly state when to use this tool versus alternatives like twining_agents (for agent status) or twining_what_changed (for recent changes). The context is clear but lacks explicit guidance on exclusions or prerequisites.

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