Skip to content

Tool Authoring Guide: ARVIS

Position in ARVIS Architecture

Tools are NOT executed directly.

They are executed via the Syscall System:

Pipeline → Intent → Kernel → Syscall (tool.execute) → Tool

Overview

A Tool is a capability exposed to the Cognitive OS.

It must: - be deterministic when possible - handle errors safely - expose a clear contract


Base Class

from arvis.tools.base import BaseTool

class MyTool(BaseTool):
    name = "my_tool"
    description = "Does something useful"

    def execute(self, input_data):
        ...

Input Structure

Prefer the structured invocation API:

def execute_invocation(self, invocation: ToolInvocation):
    payload = invocation.payload
    identity = invocation.effect_context
    idempotency_key = invocation.idempotency_key

The legacy execute(input_data) adapter is restricted to:

input_data = {
    "tool_payload": dict,
    "effect_context": AuthorizedEffectContext,
    "idempotency_key": str | None,
    "invocation": ToolInvocation,
}

There is no pipeline context, raw authentication object or syscall service container in either API.


Execution Constraints

Tools MUST:

  • be callable via syscall only
  • not be invoked directly by the pipeline
  • not modify cognitive reasoning

Example

class EchoTool(BaseTool):
    name = "echo"

    def execute(self, input_data):
        payload = input_data["tool_payload"]
        return {"echo": payload}

def validate(self, input_data):
    if "tool_payload" not in input_data:
        raise ValueError("Missing payload")

Best Practices

1. Pure vs Side Effects

Type Example Pure compute, transform Side-effect API call, DB write


2. Idempotency

Declaring an operation idempotent is only the first step:

ToolSpec(
    name="payments.capture",
    description="Capture one payment",
    idempotent=True,
)

Every effectful adapter MUST forward the exact key provided by ARVIS rather than generating a new one:

def execute_invocation(self, invocation: ToolInvocation):
    return provider.capture(
        payload=invocation.payload,
        idempotency_key=invocation.idempotency_key,
    )

Legacy adapters receive the same value under input_data["idempotency_key"]. The key is stable for one logical action, persisted in the pre-effect outbox and bound by the audit receipt through the intent commitment. A retry or crash-recovery worker MUST reuse the persisted key exactly. idempotent=True without forwarding the key is only a declaration and does not make an external effect safely replayable.


3. Error Handling

Never swallow errors:

raise Exception("clear message")

Executor will capture it.


4. Latency Awareness

Execution time is automatically tracked.


Anti-patterns forbidden

  • reading identity, tenant or target from a pipeline context
  • carrying credentials, clients or database sessions in effect context
  • hidden global state
  • non-deterministic behavior without reason
  • blocking calls without timeout

ToolSpec and the Capability Manifest

A ToolSpec describes a tool's contract, its execution semantics, and its governance. Beyond the basic identity and schemas, the spec carries a capability manifest: declarative metadata a host reads to govern sovereignty, egress and consent uniformly across local tools and external (e.g. MCP) tools.

from arvis.tools.spec import ToolSpec

ToolSpec(
    name="my_tool",
    description="...",
    input_schema={...},
    output_schema={...},

    # execution semantics
    idempotent=False,
    retryable=True,
    side_effectful=True,
    timeout_seconds=None,

    # governance (enforced by ToolPolicyEvaluator, except where noted)
    max_risk=1.0,               # deny if the request risk exceeds this ceiling
    requires_confirmation=False,  # deny with a confirmation-required decision
    audit_required=False,       # declarative: tool.execute always journals
    reversible=True,            # False marks an effect that cannot be undone

    # capability manifest (opaque to ARVIS; the host interprets the labels)
    provider=None,              # third-party service ("google", "notion"), or None
    data_egress=False,          # True if it sends the caller's data outbound
    data_class="unspecified",   # host-defined sensitivity ("public", "personal", ...)
    required_consent=None,      # opaque consent key the host must have granted
)

Sovereignty taxonomy

The manifest yields three tool classes, derived from provider and data_egress (a host reads spec.crosses_trust_boundary, i.e. provider is not None):

Class provider data_egress Meaning
sovereign None False No third party; stays in the local boundary.
connected (read) set False Talks to a provider, inbound fetch only.
connected (egress) set True Sends the caller's data outbound; maximal scrutiny.

ARVIS does not interpret provider, data_class or required_consent -- they are opaque labels. The host maps them onto its own consent system, data taxonomy and egress policy (for example, denying egress of a confidential data class, or gating a tool on a granted consent). Declaring a manifest is how a new tool, local or external, becomes governable without bespoke policy code.

Examples

# Sovereign: reads a local encrypted store, no third party.
ToolSpec(name="calendar.read", description="Read the calendar.",
         side_effectful=False, required_consent="calendar_read")

# Connected (egress): publishes the caller's content to a third party.
ToolSpec(name="notion.create_page", description="Create a page in Notion.",
         side_effectful=True, reversible=False,
         provider="notion", data_egress=True, data_class="personal",
         required_consent="notion_access")

Testing

Minimal test:

def test_tool():
    tool = MyTool()
    result = tool.execute({"tool_payload": {...}})
    assert result is not None

Summary

A good tool is:

  • predictable
  • observable
  • bounded
  • safe

Calling tools from a host

A host must never call a tool body, ToolManager.run(), or executor capability methods to perform a business effect. Register the tool, then let CognitiveOS and its tool.execute syscall perform authorization, outbox persistence, capability activation, execution, and journaling.

os.register_tool(MyTool())
result = os.run_as(principal=principal, cognitive_input=request)

ToolManager and ToolExecutor remain importable for advanced runtime composition and Veramem compatibility, but their public effect methods are fail-closed. Focused test composition helpers live under tests/support, which is not included in the distributed package.

Veramem integration pattern

Veramem owns business dependencies and injects them when constructing a tool:

from typing import Protocol

from arvis.adapters.tools.invocation import ToolInvocation
from arvis.tools.base import BaseTool


class DocumentService(Protocol):
    def delete(
        self,
        *,
        document_id: str,
        principal: str,
        tenant: str | None,
        idempotency_key: str | None,
    ) -> dict: ...


class VeramemDocumentDeleteTool(BaseTool):
    name = "veramem.document.delete"

    def __init__(self, document_service: DocumentService) -> None:
        self._document_service = document_service

    def execute_invocation(self, invocation: ToolInvocation) -> dict:
        return self._document_service.delete(
            document_id=str(invocation.payload["document_id"]),
            principal=invocation.effect_context.principal,
            tenant=invocation.effect_context.tenant,
            idempotency_key=invocation.idempotency_key,
        )

    def execute(self, input_data: dict) -> dict:  # compatibility contract
        raise RuntimeError("structured invocation required")

The service may own a repository, transaction factory or API client. The invocation never carries those objects. Veramem must also stamp a real AuthenticatedPrincipal, persist the pre-effect intent through a qualified database sink, and preserve confirmation/idempotency state across its workers.