# Instrument your own agent

> Put real tool callbacks behind Thoth's policy check and preserve actor context.

Source: https://docs.atensecurity.com/docs/getting-started/your-agent

Complete [Your first governed agent](https://docs.atensecurity.com/docs/getting-started/quickstart) before
connecting tools that read private data or change external systems.

## 1. Define the action and its owner

Choose a distinct agent ID and stable tool names. Start with one read-only
tool. Agree with your administrator on the server policy for that action,
the initiating identity, and which actions need human approval.

`approved_scope` describes the intended tool set; adding a name to it does not
create server policy. Use your tenant's supplied endpoint and a runtime execute
key. Keep administrative credentials out of the agent process.

## 2. Wrap the tool execution path

In the first-agent example, replace the `demo.read` callback with your own
read-only function, change the agent ID, and set the agreed scope. Pass the
initiating user's verified identity and a session ID for the request. Keep the
mode and failure settings agreed for your deployment.

Choose the adapter that matches how your framework actually executes tools:

| Your execution path | Integration |
| --- | --- |
| Python agent exposing `.tools` | [instrument()](https://docs.atensecurity.com/docs/sdks/python#thothinstrument) |
| Python function map or custom model tool loop | [Python SDK](https://docs.atensecurity.com/docs/sdks/python): instrument the toolchain, then dispatch through its governed function map |
| LangGraph graph, tools, or ToolNode | [LangGraph integration](https://docs.atensecurity.com/docs/sdks/langgraph-integration) |
| TypeScript tool objects or model tool loop | [TypeScript SDK](https://docs.atensecurity.com/docs/sdks/typescript) |
| Go function callbacks | [Go SDK](https://docs.atensecurity.com/docs/sdks/go): WrapTool or a session-scoped wrapper |
| External MCP server used by a desktop client | [MCP proxy](https://docs.atensecurity.com/docs/mcp/proxy) |

Your model may choose a tool name and arguments. Your application must dispatch
that choice through the governed wrapper. Do not retain an alternate route to
the original callback. Reject unknown tool names before dispatch.

### Python: dispatch a tool through a governed function map

This complete example uses a local document lookup to illustrate a custom
agent loop. Ask your administrator to configure policy for the new agent ID
and `read_document` tool first. Set `THOTH_AGENT_ID` to that agreed agent ID
and `APP_USER_ID` to the identity established by your application. Reuse your
runtime connection variables from the first-agent guide.

Save as `my_agent.py` in the project where you installed `atensec-thoth`:

```python title="my_agent.py"
import os
from uuid import uuid4
from thoth import instrument_toolchain, toolchain_function_map, ThothPolicyViolation


def read_document(args: dict) -> str:
    documents = {"welcome": "Welcome to your governed agent"}
    return documents[args["document_id"]]


governed = instrument_toolchain(
    {"read_document": read_document},
    agent_id=os.environ["THOTH_AGENT_ID"],
    tenant_id=os.environ["THOTH_TENANT_ID"],
    api_url=os.environ["THOTH_API_URL"],
    api_key=os.environ["THOTH_API_KEY"],
    approved_scope=["read_document"],
    user_id=os.environ["APP_USER_ID"],
    session_id=str(uuid4()),
    enforcement="block",
)
functions = toolchain_function_map(governed)

# In a real loop, validate the model's proposed name and arguments here.
# The model never receives credentials or the original callback.
proposed_name = "read_document"
proposed_args = {"document_id": "welcome"}
if proposed_name not in functions:
    raise ValueError("Unknown tool")
try:
    print(functions[proposed_name](proposed_args))
except ThothPolicyViolation as refusal:
    print(f"Action refused: {refusal.reason}")
```

Run `python my_agent.py`. If policy permits the action, the output is
`Welcome to your governed agent`. Otherwise inspect the refusal and server
decision; do not retry `read_document` directly. Replace the local lookup with
a real read-only callback after you verify policy. For a model-specific tool
loop, use the corresponding [Python SDK adapter](https://docs.atensecurity.com/docs/sdks/python); TypeScript
and Go can reuse the wrapped tool execution pattern from the first-agent guide.

## 3. Handle decisions without bypassing them

- **ALLOW:** execute the governed callback and return its result.
- **STEP_UP:** let the supported SDK approval flow resolve; keep the action paused.
- **BLOCK:** return a clear refusal or escalate through the approved workflow.
- **Service or authentication error:** keep fail-open disabled and resolve the failure.

Do not catch an exception and invoke the raw callback as a fallback. Keep policy
refusals separate from callback failures in your application's logs and user
messages. Avoid exposing keys, hold tokens, or sensitive arguments in those messages.

## 4. Verify before adding more tools

Test a permitted call and a refused call against your configured policy.
Confirm the refused callback never executed. Inspect the reason, agent, actor,
session, and correlation identifiers in decision history. If approval is part
of your policy, test approval, denial, and expiry before enabling sensitive actions.

Check telemetry delivery separately and verify any receipt or export you
intend to use as evidence. Repeat verification when you change tools, policies,
SDK releases, or identity handling.

Next, use your [SDK reference](https://docs.atensecurity.com/docs/sdks) for framework and lifecycle details,
or [configure supported governance settings as code](https://docs.atensecurity.com/docs/getting-started/trial-settings).
