Skip to content

Creating Tools ​

Tools are the infrastructure layer of MicroCoreOS. They provide technical capabilities to Plugins and are designed to be stateless and isolated.

Tool Structure ​

A tool lives in tools/{tool_name}/{tool_name}_tool.py and inherits from BaseTool.

python
from core.base_tool import BaseTool

class MyServiceTool(BaseTool):
    @property
    def name(self) -> str:
        # This is the name used for Dependency Injection
        return "my_service"

    def setup(self):
        # 1. Resource Allocation Phase
        # Initialize connections, clients, or load environment variables.
        print("[MyService] Initializing...")

    def get_interface_description(self) -> str:
        # 2. Self-Documentation Phase
        # This string is used to auto-generate AI_CONTEXT.md.
        # Be explicit with method signatures for better AI performance.
        return """
        My Service Tool (my_service):
        - PURPOSE: Provides access to external XYZ capability.
        - CAPABILITIES:
            - do_something(data: dict) -> dict: Processes XYZ and returns a result.
        """

    def on_boot_complete(self, container):
        # 3. Finalization Phase (Optional)
        # Called after ALL tools and plugins are initialized.
        # You can access other tools via the container if needed.
        pass

    async def on_instrument(self, tracer_provider) -> None:
        # 4. OTel Instrumentation Phase (Optional)
        # Called by TelemetryTool when OTEL_ENABLED=true, AFTER on_boot_complete.
        # Runs on the raw tool instance, bypassing ToolProxy — a failure here
        # will never mark your tool as DEAD.
        #
        # Implement this if your tool wraps a framework that has its own
        # OpenTelemetry instrumentation library (e.g. FastAPI, asyncpg, Redis).
        # This adds driver-level spans on top of the automatic ToolProxy spans.
        #
        # Example for a hypothetical Redis tool:
        #   from opentelemetry.instrumentation.redis import RedisInstrumentor
        #   RedisInstrumentor().instrument(tracer_provider=tracer_provider)
        #
        # If you don't implement this, your tool still gets automatic spans
        # for every method call via ToolProxy — no action needed.
        pass

    def shutdown(self):
        # 5. Cleanup Phase
        # Close connections and release resources gracefully.
        print("[MyService] Cleaning up...")

    # --- Capability Methods ---
    def do_something(self, data: dict):
        return {"result": f"Processed {data}"}

Rules for Tools ​

  1. Stateless: Tools should not contain business logic or domain-specific state. They are pure "capabilities".
  2. Naming: The name property must be unique. This is how the Kernel identifies the tool for injection.
  3. DI: Tools are injected into Plugins by matching the parameter name in the Plugin's __init__.
  4. No Cross-Tool Imports: Tools should never import or depend on other tools directly. If you need coordination, move that logic to a Plugin.

Lifecycle Summary ​

HookWhen it runsRequired
setup()Before any plugin bootsYes
get_interface_description()On boot — feeds AI_CONTEXT.mdYes
on_boot_complete(container)After all tools and plugins are readyNo
on_instrument(tracer_provider)When OTEL_ENABLED=true, after on_boot_completeNo
shutdown()On graceful shutdownNo

get_interface_description() is mandatory

If this method returns an empty string, the system will print a warning on every boot: [ContextTool] WARNING: Tool 'my_tool' has no interface description. Document your tool's capabilities here — it's what the AI reads to know how to use it.

OTel Instrumentation: two levels ​

When OTEL_ENABLED=true, your tool gets observability at two levels automatically:

Level 1 — ToolProxy spans (automatic, no code needed) Every call to your tool's public methods gets a span with: tool name, method name, duration, success/fail. This happens for ALL tools with zero implementation.

Level 2 — Driver-level spans (optional, via on_instrument) If your tool wraps a framework with its own OTel library, implement on_instrument to activate it. This adds richer data: SQL query text, HTTP route details, cache hit/miss, etc.

python
# Example: a PostgreSQL tool
async def on_instrument(self, tracer_provider) -> None:
    try:
        from opentelemetry.instrumentation.asyncpg import AsyncPGInstrumentor
        AsyncPGInstrumentor().instrument(tracer_provider=tracer_provider)
    except ImportError:
        pass  # degrade gracefully if package not installed

TIP

Always wrap on_instrument in try/except ImportError so the tool works even when the OTel instrumentation package is not installed.

Integration ​

Once created, the Kernel will automatically discover and load your tool if it's placed in the tools/ directory. No manual registration is required.

Released under the MIT License.