Adding Tools & Integrations — Definition of Done
Use this checklist whenever you add or materially change:- a tool — under
integrations/<vendor>/tools/for a single-vendor tool, ortools/system//tools/cross_vendor/for a cross-cutting one (see tool-placement-policy.md) - an integration under
integrations/<name>/— its config, client, verifier, and tools - investigation source wiring for an existing tool or integration
1. Tool checklist
Files usually involved
integrations/<vendor>/tools/<tool_name>_tool/__init__.py— the tool package (most common path: the tool belongs to a vendor integration)tools/system/<tool_name>/ortools/cross_vendor/<tool_name>/— only when the tool is not vendor-specific (e.g.tools/system/sre_guidance_tool/)integrations/<name>/client.py— reuse a dedicated integration API client instead of inlining requestscore/tool_framework/utils/— shared helper code reused across vendorsdocs/<tool_name>.mdx— user-facing usage, parameters, examplestests/tools/test_<tool_name>.py— behavior and regression coverage
tools/ and integrations/<vendor>/tools/, so placement is about ownership, not discovery — see tool-placement-policy.md. Wherever a tool lives, it calls integration-local clients/helpers rather than inlining transport, and never lives in a top-level vendors/ or services/ package.
Tool packages must be substantive production modules — no empty or discovery-only __init__.py, no thin wrapper that only satisfies registry import. Any tool with validation, credential/parameter resolution, transport/client calls, output normalization, or error handling should split those concerns into focused sibling files (tool.py, models.py, validation.py, delivery.py/client.py, results.py), leaving __init__.py as a small registry entrypoint that imports the public tool object.
Contract and implementation
- Pick the simplest shape that fits (
@tool(...)for lightweight tools, a richer class only when needed) -
__init__.pyis a small registry entrypoint; non-trivial tools use sibling modules for implementation concerns - Metadata is complete and accurate:
name,description,source,surfaces,requires, and anyuse_cases/outputs/retrieval_controls -
input_schemamatches the actual runtime arguments and required fields -
is_availablereturnsTrueonly when the tool can genuinely run -
extract_paramsmaps resolved integration state into tool args correctly - Validation, credential/parameter resolution, transport/client calls, and result formatting are separated so each can be tested independently
- Reusable transport or integration-specific parsing lives in
integrations/<name>/orcore/tool_framework/utils/, not copied into the tool body - Failure responses have a stable, investigation-friendly shape; expected external failures (missing config, auth, rate limit, upstream 4xx/5xx) return structured errors rather than raising — unexpected exceptions use the global
BaseToolwrapper intentionally or are migrated with telemetry coverage - Output is normalized enough for the planner/LLM to consume reliably
- Secrets never leak through
extract_params, return values, logs, or traceable tool-call kwargs; secret/PII output is run throughplatform/masking/before return - External side effects declare
side_effect_level,requires_approval, andapproval_reasonwhere appropriate - To appear in both investigation and chat, set
surfaces=("investigation", "chat")
Live payload parsing
If the tool parses API, MCP, log, or webhook payloads:- Validate against the real or documented upstream response shape, not only idealized mocks
- Handle alternate field names used in live payloads
- Handle missing or partial fields without returning unusable output
- Preserve important context when truncating, tailing, paginating, or flattening data
- Upstream 429 / 5xx responses return a clear, investigation-friendly error rather than raising
- Add at least one regression test using a realistic fixture payload
hasMore / cursor mismatches; content-vs-pointer shapes (logs_content vs logs_url-style payloads).
2. Integration checklist
Files usually involved
integrations/<name>/__init__.py— config builders, validators, selectors, normalization helpersintegrations/<name>/client.py— a dedicated API client, when the integration makes direct remote callsintegrations/<name>/verifier.py— local verification logicintegrations/<name>/tools/<tool_name>_tool/— the vendor’s agent-callable tools (see §1)integrations/catalog.py— resolve the integration into the shared runtime configintegrations/verify.py— wire the local verification pathdocs/<name>.mdx— user-facing setup, usage, verificationtests/integrations/test_<name>.py, plustests/tools/,tests/e2e/, ortests/synthetic/where tools or scenarios exercise it
integrations/<name>/ owns everything about one vendor — config, resolution, clients, verifiers, helpers, and its tools. Only vendor-less (tools/system/) and cross-vendor (tools/cross_vendor/) tools live under top-level tools/.
Examples from the repo
- Datadog:
integrations/datadog/(withintegrations/datadog/tools/),integrations/catalog.py, tests undertests/integrations/datadog/andtests/tools/test_datadog_*.py. - Grafana:
integrations/grafana/(withintegrations/grafana/tools/),integrations/catalog.py,surfaces/cli/wizard/local_grafana_stack/, tests undertests/integrations/grafana/andtests/tools/test_grafana_*.py. - Hermes:
integrations/hermes/(withintegrations/hermes/tools/hermes_logs_tool/and.../hermes_session_evidence_tool/),surfaces/cli/commands/hermes.py,tests/hermes/,tests/synthetic/hermes/.
Core completeness
- Config, normalization, and validators are in place under
integrations/<name>/__init__.py - Catalog resolution / env loading is wired correctly
- Verification path is wired in
integrations/verify.pyand adapters/registry as needed - Integration-local client added under
integrations/<name>/client.py(only if it makes direct remote calls) - Tool layer is wired and stable
- CLI setup flow is updated if the integration is user-configurable locally
-
opensre onboardparity is added, or intentionally documented as out of scope - New required env vars / credentials are added to
.env.example(never.env) -
make verify-integrationspasses
3. Investigation wiring
If the tool/integration is relevant to investigations:- Review alert-source seeding in
core/domain/alerts/alert_source.py - Review source-priority/prompt mapping in
tools/investigation/stages/gather_evidence/prompt.py - Review evidence/source registration in
core/domain/types/or related state models - Add scenario coverage proving the tool surfaces useful RCA evidence
alert_source, review the source-to-tool maps explicitly.
4. Discovery and edge cases
For tools that list, search, or inspect resources:- Folder/nested resource layouts are considered where the upstream supports them
- Large result sets are capped or paginated intentionally
- Partial fetches are surfaced clearly (
truncated,fetch_error, etc.) - Time/order-sensitive results preserve causal ordering where it matters
5. Docs and tests
Docs
- Ship or update a
docs/page/section in the same PR (new tool, CLI command, pipeline behavior, or integration; and whenever a tool’s API/schema or an integration’s setup changes) - Any new
docs/page is registered indocs/docs.json(without the.mdxsuffix) so Mintlify navigation shows it - Investigation LLM tool-calling changes follow investigation-tool-calling.md
Tests
- Unit tests for config/normalization
- Tool contract tests, or equivalent schema/metadata coverage
- A registry/discovery test proves the tool is visible on the expected surface(s)
- Runtime behavior tests for success and failure paths
- At least one realistic fixture for live-payload parsing when external payloads are involved
- If investigation-relevant, a test proves the planner/agent can discover or invoke the tool through the normal runtime path (plus synthetic/scenario coverage when the loop depends on it)
-
tests/integrations/updated when integration wiring changes
Final gate (new integrations)
Everything above is complete, and:- Screenshot or demo GIF showing the integration working end-to-end
- E2E or synthetic test added
- CI checks pass (see CI.md)