Production Patterns

Larger Ohtools apps work best when domain code, services, tool definitions, and app composition stay separate.

When to use this: read it before promoting a tool app from local experiments to shared agent or operator workflows.

src/
  domain/
  application/
  infrastructure/
  tools/
  tooling/
    ohtools-store.ts

Keep Tools Close To Services

Tool factories should receive the service they need. They should not construct repositories, clients, or process-level state directly.

// docs-snippet: skip
import { defineTool, jsonSchema } from "@bosun-sh/ohtools";
import type { DeploymentService } from "../application/deployment-service";

export function deploymentPlanTool(service: DeploymentService) {
  return defineTool({
    id: "deploy.plan",
    description: "Create a deployment plan.",
    input: jsonSchema<{ service: string; environment: string }>({
      type: "object",
      properties: {
        service: { type: "string" },
        environment: { type: "string" }
      },
      required: ["service", "environment"],
      additionalProperties: false
    }),
    run: (input) => service.plan(input)
  });
}

Compose Hierarchy In One Place

Groups are the map agents inspect before they run tools.

// docs-snippet: skip
import { defineGroup } from "@bosun-sh/ohtools";
import type { DeploymentService } from "../application/deployment-service";
import { deploymentPlanTool } from "./deployment-tools";

export function deploymentHierarchy(service: DeploymentService) {
  return defineGroup(
    { id: "deploy", description: "Deployment planning and status tools." },
    (deploy) => deploy.tool(deploymentPlanTool(service))
  );
}

Centralize Runtime Wiring

Use one module to construct process-level dependencies, compose the Ohtools app, and expose runtime access.

// docs-snippet: skip
import { Ohtools } from "@bosun-sh/ohtools";
import { mcpAdapter } from "@bosun-sh/ohtools/adapters/mcp";
import { DeploymentService } from "../application/deployment-service";
import { deploymentHierarchy } from "../tools/deployment-hierarchy";

let service: DeploymentService | undefined;
let app: Ohtools | undefined;

export function useDeploymentService() {
  service ??= new DeploymentService();
  return service;
}

export function useOhtoolsApp() {
  app ??= new Ohtools({ name: "deployment-tools" })
    .group(deploymentHierarchy(useDeploymentService()))
    .adapter(mcpAdapter({ stdio: true }));

  return app;
}

This keeps the server entrypoint boring and makes tool behavior easier to test.

Validate The Surface

For production-facing tool apps, check four boundaries:

  • Registry: every public tool ID, group ID, and description is stable.
  • Explore: graph and descriptors can be inspected without side effects.
  • Runtime: inputs and outputs are validated at tool boundaries.
  • Adapter: MCP and CLI expose the same intended app surface.

Run docs and examples checks when you change public behavior.

Use Objectives for the release harness and Changelog for compatibility notes.