Block Dangerous AI Agent Commands

Guard an OpenRouter AI agent with Armor rules and log blocked tool calls.

Introduction

30 Second Summary

Giving an assistant permission to run commands can save time. One careless request can also delete files or pull something untrusted onto your computer.

In this project, you will build a small AI agent that sends shell tool calls through OpenRouter Armor. Its plain-English rules stop dangerous calls before they reach your code.

What You'll Build

You'll run a terminal demo where risky commands return as allowed, flagged, or blocked with every result captured in a short audit log.

By the end of this project, you'll have:

  • A guarded agent script that checks every proposed shell command before your code can run it.
  • A set of hard-deny, soft-deny, and allow rules that you can test against file deletion, network downloads, and one trusted domain.
  • An audit log that captures allowed and blocked attempts across the output modes you compare.
  • Secret Mission: An optional challenge to push your guarded agent skills further.

Are there any prerequisites?

You'll need an OpenRouter API key. You'll also need Python or Node installed on your computer.

Before We Start

Your guarded agent eventually sends tool calls through OpenRouter. A missing account or local runtime would stop each request before OpenRouter Armor can inspect it.

You’ll prepare your account first. You’ll then confirm that your computer has the local tools needed for the agent script.

In this step, get ready to:
  • Create your OpenRouter account.
  • Prepare your local development workspace.
  • Install the OpenRouter SDK.
Create your OpenRouter account

Your OpenRouter account provides access to the service used throughout this project. OpenRouter handles your sign-in details on its account site.

You do not create an API key in this step. That credential belongs to the first agent request in the next step.

That clears the first prerequisite. Your OpenRouter account is ready for the API key you create in the next step.

Prepare your local workspace

A terminal gives you a place to run the agent. The Python interpreter executes its script.

This project needs Python 3. Start by checking which runtime your terminal can find.

macOS

  • Press Cmd+Space to open Spotlight.
  • Type Terminal into the search field.
  • Press Return to open Terminal.
  • Check your Python 3 version by running this command:
python3 --version

What does this command do?

The command asks the Python 3 interpreter to print its version. A version response proves that Terminal can find the runtime.

Windows

  • Press the Windows key to open Windows Search.
  • Type PowerShell into the search field.
  • Press Enter to open PowerShell.
  • List your Python runtimes by running this command:
py list

What does this command do?

The command asks the Python install manager to list the runtimes available on your computer. A Python 3 entry confirms that PowerShell can reach the required runtime.

Choose the tab that matches the result in your terminal.

✔️ I see Python 3

Your terminal can reach Python 3. That gives the agent script the runtime it needs.

ⓧ I see an older major version

Your computer has Python available. The agent needs a Python 3 runtime.

macOS

The macOS installer may request administrator approval. This approval only authorizes the local Python installation.

  • Visit the official Python downloads page for macOS.
  • Download the current stable Python 3 installer.
  • Open the downloaded installer package.
  • Select Install in the macOS installer.
  • Return to Terminal after the installation completes.
  • Check the new Python 3 installation by running:
python3 --version
What confirms the installation?

The command now prints a version beginning with 3. That confirms the newer runtime is available.

Still seeing the older version?

Close Terminal after the installer finishes. Open a new Terminal window so it loads the updated command path.

Need help locating the old runtime? Help me troubleshoot my Python version on macOS.

Windows

Windows uses the Python install manager to add current runtimes. The installer may request permission to make local changes.

  • Visit the official Python downloads page for Windows.
  • Download the Python install manager.
  • Open the downloaded installer.
  • Select Install in the installer.
  • Return to PowerShell after the installation completes.
  • List the available Python runtimes by running:
py list
What confirms the installation?

The output now includes a Python 3 runtime. That confirms the required runtime is available.

Still seeing the older version?

Close PowerShell after the installation finishes. Open a new PowerShell window so it can find the Python install manager.

Need help with the listed runtimes? Help me troubleshoot my Python version on Windows.

ⓧ Python is not available

Your terminal cannot find a Python runtime yet. Install Python 3 before adding the agent’s client library.

macOS

The macOS installer may request administrator approval. This approval only authorizes the local Python installation.

  • Visit the official Python downloads page for macOS.
  • Download the current stable Python 3 installer.
  • Open the downloaded installer package.
  • Select Install in the macOS installer.
  • Return to Terminal after the installation completes.
  • Verify the Python 3 installation by running:
python3 --version
What confirms the installation?

The command prints a version beginning with 3. That confirms Terminal can reach the installed runtime.

Terminal still cannot find Python?

Close Terminal after the installer finishes. Open a new Terminal window so it loads the updated command path.

Still blocked? Help me finish the Python installation on macOS.

Windows

Windows uses the Python install manager to add current runtimes. The installer may request permission to make local changes.

  • Visit the official Python downloads page for Windows.
  • Download the Python install manager.
  • Open the downloaded installer.
  • Select Install in the installer.
  • Return to PowerShell after the installation completes.
  • List the installed Python runtimes by running:
py list
What confirms the installation?

The output includes a Python 3 runtime. That confirms PowerShell can reach the installation.

PowerShell still cannot find Python?

Close PowerShell after the installer finishes. Open a new PowerShell window so it can find the Python install manager.

Still blocked? Help me finish the Python installation on Windows.

A code editor gives you a clear place to write the agent script. Use an editor already installed on your computer or install Visual Studio Code below.

  • Press Cmd+Space on macOS or the Windows key on Windows to open system search.
  • Type the name of your code editor into the search field.
  • Press Enter to launch the editor.

✔️ My code editor opens

Your editor window confirms that you have a place to write the agent script. Keep it available for the next step.

ⓧ I need a code editor

Visual Studio Code is available for macOS and Windows. Choose your operating system below.

macOS

  • Visit the Visual Studio Code download page.
  • Download the macOS .dmg installer.
  • Open the downloaded .dmg file.
  • Drag Visual Studio Code.app into the Applications folder.
  • Use Spotlight to launch Visual Studio Code.

You’ll see the Visual Studio Code editor window when the installation is complete.

Windows

  • Visit the Visual Studio Code download page.
  • Download the Windows User Installer.
  • Open the downloaded .exe installer.
  • Complete the installation prompts.
  • Use Windows Search to launch Visual Studio Code.

You’ll see the Visual Studio Code editor window when the installation is complete.

Editor still will not open?

Confirm that you downloaded the installer for your operating system. A managed computer may require approval from its administrator.

Need help with the installation? Help me open Visual Studio Code.

Install the OpenRouter SDK

The OpenRouter SDK gives your future Python script a client for sending requests. Installing it prepares the dependency without creating an API key.

Pause for a prediction. Decide whether the installer will add only the client package or resolve its supporting packages too.

  • Return to the terminal from your Python check.
  • Install the OpenRouter SDK by running this command:
pip install openrouter

What does this command do?

The pip package installer downloads the OpenRouter client. It also resolves the supporting Python packages required by that client.

The command only changes your local Python environment. It does not send a model request.

You’ll see installation output that names the OpenRouter package. The final summary confirms that the SDK is available.

Package installation failed?

Confirm that your Python check returned a Python 3 runtime. A missing package installer can point to an incomplete Python installation.

Need help reading the output? Help me troubleshoot the OpenRouter SDK installation.

Your OpenRouter account is ready. Your local workspace can now support the agent script.

Next up, you’ll create an API key before asking a model to return its first tool call.

Create a Tool-Using Agent

Your guardrails need something concrete to inspect. In this project, that input is a tool call returned through OpenRouter.

You’ll create a limited API key. You’ll then ask a model to return a harmless run_command call.

In this step, get ready to:
  • Create an OpenRouter API key with a credit limit.
  • Build an agent script with a run_command tool.
  • Run the script to inspect the returned JSON tool call.
Create and protect your OpenRouter key

A standard OpenRouter key authorizes model requests from your script. Its credit limit caps how much that key can spend.

What access does this key provide?

A standard API key can call model endpoints within its credit limit. A management key is reserved for account administration.

OpenRouter bills model input tokens plus output tokens at the posted model rates. Checking those rates before a request keeps the cost visible.

  • Visit the tool-capable model catalog.
  • Search the catalog for openai/o4-mini.
  • Open the model result to review its current input price.
  • Review the model’s current output price.

This key can spend account credits, so a 1 USD limit keeps this experiment bounded. The secret value stays in your terminal session.

  • Return to the OpenRouter API Keys page.
  • Enter armor-agent as the key name.
  • Enter 1 as the credit limit in USD.
  • Submit the key creation form.
  • Copy the secret value when OpenRouter shows it.

Keep the key out of files

The secret is shown once. Store it in OPENROUTER_API_KEY without pasting it into agent.py.

If this key is exposed, delete it from the API Keys page. Create a replacement before making another request.

Use the command for your terminal platform. Replace sk-or-v1-YOUR-API-KEY with the secret you just copied.

macOS

  • Store the key in the current terminal session by running this command:
export OPENROUTER_API_KEY="sk-or-v1-YOUR-API-KEY"

What does this command do?

The command makes OPENROUTER_API_KEY available to programs launched from this terminal. Keep this terminal open for the rest of the project.

Windows

  • Store the key in the current PowerShell session by running this command:
$env:OPENROUTER_API_KEY="sk-or-v1-YOUR-API-KEY"

What does this command do?

The command makes OPENROUTER_API_KEY available to programs launched from this PowerShell session. Keep this terminal open for the rest of the project.

Key unavailable later?

  • Confirm you replaced the sample value with your copied key.
  • Keep using the same terminal session where you stored OPENROUTER_API_KEY.
  • Check that the environment variable name uses the exact capitalization shown above.

Need a hand? Help me troubleshoot my OpenRouter environment variable without asking me to share the key.

Build the tool-calling script

Your script needs a named tool with a parameter schema. The model uses that schema to return a structured command request.

The script only prints the requested call. It does not execute the command on your computer.

macOS

  • Create the openrouter-armor-agent folder on your Desktop by running these commands:
cd ~/Desktop
mkdir openrouter-armor-agent
cd openrouter-armor-agent
pwd

What do these commands do?

The commands create the project folder on your Desktop. The final command prints a path ending in openrouter-armor-agent.

Windows

  • Create the openrouter-armor-agent folder on your Desktop by running these commands:
New-Item -Path "$HOME\Desktop\openrouter-armor-agent" -ItemType Directory
Set-Location -Path "$HOME\Desktop\openrouter-armor-agent"
Get-Location

What do these commands do?

The commands create the project folder on your Desktop. The final command prints a path ending in openrouter-armor-agent.

Folder already exists?

Use the existing openrouter-armor-agent folder if you created it during an earlier attempt. Change into that folder before continuing.

Need help locating it? Help me find my openrouter-armor-agent folder on the Desktop.

  • Open the openrouter-armor-agent folder from your Desktop in your code editor.
  • Create agent.py inside the folder.
  • Copy the following code into agent.py:
import os
import requests

# Define the shell-command request the model can return.
run_command = {
    "type": "function",
    "name": "run_command",
    "description": "Run a shell command.",
    "parameters": {
        "type": "object",
        "properties": {"command": {"type": "string"}},
        "required": ["command"],
    },
}

# Ask the model to return one harmless command call.
response = requests.post(
    "https://openrouter.ai/api/v1/responses",
    headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
    json={
        "model": "openai/o4-mini",
        "input": "Use run_command to run pwd. Do not do anything else.",
        "tools": [run_command],
        "tool_choice": {"type": "function", "name": "run_command"},
    },
)
response.raise_for_status()

# Print the model's structured tool request without executing it.
print(response.json()["output"])

What does this code do?

  • The run_command schema tells the model that it can request one string named command.
  • The request reads OPENROUTER_API_KEY from the terminal environment.
  • The tool_choice value requires the model to return a run_command call.
  • The final line prints the response output without executing pwd.
  • Save agent.py.

Your editor now lists agent.py inside openrouter-armor-agent.

✔️ Awesome, I've got everything!

Your saved script contains the tool definition plus the OpenRouter request.

ⓧ I'd like to double check the full code

import os
import requests

# Define the shell-command request the model can return.
run_command = {
    "type": "function",
    "name": "run_command",
    "description": "Run a shell command.",
    "parameters": {
        "type": "object",
        "properties": {"command": {"type": "string"}},
        "required": ["command"],
    },
}

# Ask the model to return one harmless command call.
response = requests.post(
    "https://openrouter.ai/api/v1/responses",
    headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
    json={
        "model": "openai/o4-mini",
        "input": "Use run_command to run pwd. Do not do anything else.",
        "tools": [run_command],
        "tool_choice": {"type": "function", "name": "run_command"},
    },
)
response.raise_for_status()

# Print the model's structured tool request without executing it.
print(response.json()["output"])

This reference shows the complete saved contents of agent.py for comparison.

Run the harmless tool request

Before you run the script, what do you expect the model to return for the run_command tool?

macOS

  • Run the agent from the terminal you used earlier by entering this command:
python3 agent.py

What does this command do?

Python runs agent.py with access to the key stored in this terminal. The script sends one model request.

Windows

  • Run the agent from the PowerShell session you used earlier by entering this command:
python agent.py

What does this command do?

Python runs agent.py with access to the key stored in this PowerShell session. The script sends one model request.

You’ll see a response item with function_call as its type. The item names run_command with arguments containing pwd.

Why is the command only printed?

A client-defined tool call is a structured suggestion from the model. Your application controls whether that request is executed.

No Armor plugin is enabled in this request. This unguarded call becomes the baseline for the rules you add next.

Request failed or no tool call?

  • Run the script from the same terminal where you stored OPENROUTER_API_KEY.
  • Compare the run_command schema with the full-code reference.
  • Confirm the model ID is openai/o4-mini.

Still stuck? Help me diagnose my OpenRouter tool-calling request without asking me to share my API key.

That’s your first working baseline. The model now returns a shell-command request that your next guardrail can inspect.

  • Scroll the terminal until the API key command is outside the screenshot area.

Your unguarded agent is returning structured shell requests. Next up, you’ll add the first Armor rule to block destructive file deletion.

Block Destructive Commands

The harmless tool call from the last step proved that your agent can propose a shell command. The same route could carry a file-deletion request.

In this step, you will place OpenRouter Armor between the model and your local tool. A hard-deny rule will stop destructive calls before your code can execute them.

In this step, get ready to:
  • Write a hard-deny rule for file deletion.
  • Enable Armor in the agent request.
  • Trigger a destructive tool call to confirm Armor blocks it.
Write the hard-deny rule

A hard-deny rule describes an action that must never reach your application. Armor reads the rule as an action in plain English.

The rule lives in a JSON classifier context. Its name will also make the eventual block result easy to recognise.

  • Switch back to the editor containing your agent script.
  • Create the Armor rules file beside the agent script by using your editor's new-file control.
  • Add the hard-deny classifier context by pasting this configuration:
{
  "v": 1,
  "auto_mode": {
    "hard_deny": [
      {
        "id": "destructive",
        "name": "Destructive",
        "instructions": "Deleting files or directories"
      }
    ]
  }
}

What does this rule do?

  • The v field selects the classifier-context format.
  • The auto_mode object holds the rules Armor evaluates for each tool call.
  • The hard_deny list contains actions that must always be blocked.
  • The instructions value describes the protected action instead of naming one shell command.
  • Save the Armor rules file.
  • Confirm the Armor rules file appears beside the agent script in your editor's file sidebar.

Rules file showing an error?

  • Check that every property name uses double quotes.
  • Check that the final property in each object has no trailing comma.
  • Compare each opening brace with its closing brace.

Still stuck? Help me fix the JSON in my Armor rules file.

✔️ Awesome, I've got everything!

Great. Your Armor rules file now contains one hard-deny rule for file deletion.

ⓧ I'd like to double check the full code

{
  "v": 1,
  "auto_mode": {
    "hard_deny": [
      {
        "id": "destructive",
        "name": "Destructive",
        "instructions": "Deleting files or directories"
      }
    ]
  }
}
Connect Armor to the agent

The rules file defines the boundary. Your agent request must send that classifier context through the plugins array so Armor can inspect each proposed run_command call.

Why check calls before local code?

A local command filter only sees calls after they reach your application. Armor evaluates the proposed call before OpenRouter releases it to your code.

This keeps blocked arguments outside the normal tool-execution path.

  • In the agent script from earlier, locate the output handling that prints the returned run_command tool call.
  • Expand the output handling so it prints the complete returned output plus any safeguard_results data.
  • Save the agent script.
  • Rerun the agent with the harmless prompt by using the same command from the previous step.

You should still see the harmless run_command tool call in the returned output.

  • Locate the request body that already includes the run_command tool definition.
  • Configure that request to load the parsed Armor rules file as classifier_context for an armor entry in plugins.
  • Save the agent script.

Before you rerun the harmless request, do you think Armor will block a call that does not match the deletion rule?

  • Rerun the agent with the harmless prompt by using the same command from the previous step.

You should see the ordinary tool call again. The safeguard_results data should report not_flagged for that call.

Missing the Armor verdict?

  • Confirm the armor entry sits inside the request body's plugins array.
  • Confirm the parsed rules object is assigned directly to classifier_context.
  • Confirm the agent prints the complete response instead of printing only the first tool call.

Need another pair of eyes? Help me find why my Armor verdict is missing.

Test the destructive request

This test sounds risky. Your script only displays the returned request, so no deletion command is executed locally.

Before you run the agent, do you expect the destructive call to appear as an executable tool call or as a blocked result?

  • Replace the harmless prompt with an explicit request to delete every file in the agent folder.
  • Save the agent script.
  • Run the agent by using the same command from the previous step.

You should see a blocked result naming the Destructive rule. The corresponding safeguard_results verdict should report flagged.

That safety boundary is working. The destructive request has been removed from the executable tool-call path before it can reach your local run_command implementation.

What proves the call was blocked?

  • The response identifies the hard-deny rule that matched.
  • The verdict reports flagged for the proposed call.
  • The destructive request is unavailable as an ordinary executable run_command tool call.

Destructive call still appears executable?

  • Confirm the rule is nested under hard_deny inside auto_mode.
  • Confirm the request sends the parsed rules object under classifier_context.
  • Confirm the Armor plugin is using its default enforce mode instead of audit mode.

If the deletion request is still returned as a normal tool call, help me trace why my hard-deny rule did not fire.

Your agent now blocks file-deletion requests before local execution. Next, you will add a softer boundary for network downloads.

Flag Network Downloads

Your hard-deny rule already stops commands that could erase local files. That protects the project from one permanent form of damage.

A soft-deny rule gives OpenRouter Armor a reviewable boundary for network activity. In this step, you will flag an unapproved download before it reaches your local tool.

In this step, get ready to:
  • Add a soft-deny rule for network downloads.
  • Observe Armor flag a network-download attempt.
  • Confirm the file-deletion protection still works.
Add the network-download rule

A soft-deny match receives a flagged verdict unless an allow rule or exact user consent clears it. This tier fits actions that need context before they can run safely.

Use a recognisable rule

Use network-download as the rule's id.

Use Network Download as the rule's name.

Use Downloading files or data from a network destination that the user has not approved explicitly. as the rule's instructions.

  • Switch back to the Armor rules file from earlier.
  • Add the described rule object inside a new soft_deny list beside the existing hard_deny list.
  • Save the Armor rules file.

Your rules file now carries two boundaries. File deletion remains permanently blocked. Unapproved network downloads now require a reviewable verdict.

Why describe the action?

Armor evaluates the action described by instructions. An action-focused rule can recognise different shell commands that produce the same risky outcome.

Trigger a flagged download

Exact consent can clear a soft-deny match. This test requests an online archive without approving a specific destination.

  • Switch back to the terminal session containing your agent from earlier.
  • Enter Fetch the latest sample archive from the project's public download site using the shell tool. through the same prompt input method you used earlier.

Before you submit the prompt, do you expect Armor to release the network call or hold it for review?

  • Submit the prompt to the agent.

You should see a flagged verdict in safeguard_results for the attempted network call. The call should stay out of the local tool execution output.

Your second guardrail is now doing its job by holding an unapproved download before local execution.

Download call was not flagged?

  • Confirm soft_deny sits inside auto_mode with the existing rules.
  • Check that the rule describes downloading from a network destination.
  • Keep the destination unspecified in the test prompt so the request does not approve an exact target.

Still seeing the call released? Help me diagnose why my Armor soft-deny rule did not flag the network download.

Recheck both protections

Each risk tier keeps its own meaning. Adding a soft-deny rule must leave the existing hard-deny protection intact.

Before you repeat the deletion check, do you expect the new network rule to change the original verdict?

  • Repeat the destructive deletion prompt from the previous step through the same agent input.

You should see the deletion attempt blocked by the original hard-deny rule. The command should remain absent from the local tool execution output.

  • Compare the matched rule names in the deletion result with the network result.

The deletion result should identify the hard-deny rule. The network result should identify Network Download with a flagged verdict.

Your agent now blocks destructive deletion attempts while holding unapproved downloads for review. Next up, you will define a narrow exception for trusted network traffic.

Allow a Trusted Download Domain

Your last test proved that OpenRouter Armor can flag network-download calls before they reach run_command.

A blanket download rule also catches sources you trust. This step adds one narrow exception while preserving both existing safeguards.

In this step, get ready to:
  • Define one trusted download domain.
  • Add an allow rule for that domain.
  • Verify the trusted, untrusted, and destructive test outcomes.
Add the trusted-domain exception

An allow rule defines a specific exception to a matching soft_deny rule. A narrow domain match keeps the exception tied to one destination.

  • Choose a download domain you explicitly trust for this agent.
  • Record the domain here as your trusted download domain.
  • Switch back to the Armor rules file from earlier in your editor.
  • Locate the auto_mode object containing the existing hard_deny and soft_deny lists.
  • Add one plain-English entry under an allow list that permits network downloads only when the destination host exactly matches your trusted download domain.
  • Save the Armor rules file.

You should now see all three rule tiers inside auto_mode. The new allow entry should name only your trusted domain.

How Does the Exception Work?

An allow rule clears a matching soft_deny result. The permitted call reaches your tool implementation unchanged.

A matching hard_deny result remains final. Your file-deletion safeguard keeps its full protection.

Compare trusted and untrusted downloads

The strongest proof comes from testing both sides of the boundary. The same download action should produce a different verdict when only the destination changes.

  • Switch back to the agent script from earlier.
  • Replace the untrusted host in the previous download test request with your trusted download domain.
  • Save the agent script.

Before you run this test, do you expect the trusted download to stay flagged or reach run_command?

  • Return to the terminal panel from earlier.
  • Press Up Arrow to recall the previous agent command.
  • Press Enter to run the trusted-domain test.

You should see the download call reported as not_flagged. The run_command implementation should receive the allowed call.

Your exception is working. The trusted destination now passes through the same boundary that caught the earlier download.

  • Switch back to the agent script.
  • Restore the untrusted destination from the previous step.
  • Save the agent script.

Before you run this comparison, do you expect the untrusted destination to receive the same exception?

  • Return to the terminal panel.
  • Press Up Arrow to recall the previous agent command.
  • Press Enter to run the untrusted-domain test.

You should see the untrusted download reported as flagged. The call should stop before reaching the run_command implementation.

Download Results Look Reversed?

  • Confirm the allow rule names the exact host stored as your trusted download domain.
  • Confirm the untrusted request uses a different destination host.
  • Confirm the trusted exception sits under allow inside auto_mode.

Still seeing the wrong verdict? Help me troubleshoot my trusted-domain Armor rule.

Confirm the hard-deny rule still wins

Hard-deny precedence is the final safety backstop. An allow match cannot clear a file-deletion rule from the hard_deny tier.

  • Switch back to the agent script.
  • Restore the destructive deletion test request from earlier.
  • Save the agent script.

Before you run the regression test, do you expect the trusted-domain exception to weaken the deletion block?

  • Return to the terminal panel.
  • Press Up Arrow to recall the previous agent command.
  • Press Enter to run the deletion test.

You should see the deletion attempt blocked again. The call should never reach the run_command implementation.

Deletion Attempt Reached the Tool?

  • Confirm the original file-deletion rule remains under hard_deny.
  • Confirm the trusted-domain exception appears only under allow.
  • Confirm the deletion test still requests the destructive action from the earlier step.

Need another pair of eyes? Help me restore hard-deny precedence in my Armor rules.

Your saved setup should now match one of these states.

✔️ Awesome, I've got everything!

Your trusted download reaches the tool implementation. Your untrusted download stays flagged.

Your deletion attempt remains blocked. The exception is narrow enough to preserve the original safety boundary.

ⓧ I'd like to double check the full setup

  • The Armor rules file contains a hard-deny rule blocking file-deletion commands, a soft-deny rule flagging network-download commands, and an allow rule permitting downloads from one explicitly trusted domain.
  • The OPENROUTER_API_KEY environment variable remains stored locally. Its secret value is absent from project files.
  • The agent script calls an OpenRouter model with the run_command tool. It enables the Armor plugin and displays allowed, blocked, and flagged results.
  • The run_command tool remains defined for the agent request. Armor checks calls before the tool implementation receives allowed calls.

Your agent now distinguishes a trusted exception from a broad network risk. Next, you will compare how Armor presents blocked calls in different output formats.

Compare Armor Output Modes

Your policy now blocks deletion attempts. It also preserves the trusted-domain exception from the previous step.

A blocked call can have different response shapes depending on the OpenRouter Armor output mode. In this step, you will capture two formats so you can choose the response shape your agent can process safely.

In this step, get ready to:
  • Capture a blocked deletion response in text mode.
  • Capture the same blocked deletion response in custom_item mode.
  • Compare both formats without changing your Armor rules.
Capture the text-mode block

The text output mode removes the blocked tool call from the executable response. Armor places an <openrouter_armor_blocked> marker inside the assistant text instead.

  • Switch back to the agent script from the previous step.
  • Set the Armor plugin's output_mode field to text.
  • Save the agent script.

Before you run the agent, do you think the deletion attempt will remain available as an executable run_command call?

  • Run the agent script with the same command you used earlier.
  • Submit the deletion prompt that triggered your hard-deny rule earlier.

You should see no executable deletion tool call. The assistant text should contain an <openrouter_armor_blocked> marker instead.

Good, your first capture now shows how text mode neutralizes a blocked call.

Reading the Text-Mode Result

  • The <openrouter_armor_blocked> marker records the blocked call inside assistant text.
  • The safeguard_results field records the Armor verdict for the evaluated tool call.
  • Your application receives no executable deletion call from this response.
  • Keep the complete text response available for the comparison.

Missing the Blocked Marker?

  • Confirm that output_mode belongs to the existing Armor plugin configuration.
  • Confirm that its value is exactly text.
  • Use the same deletion prompt that matched your hard-deny rule earlier.

Still seeing an executable call? Help me inspect my text-mode Armor response.

Capture the custom-item block

The custom_item mode is supported by the Responses API. It replaces the blocked function_call with a typed openrouter:armor_blocked output item.

  • Return to the Armor plugin configuration in your agent script.
  • Change the output_mode value from text to custom_item.
  • Save the agent script.

Before you rerun the deletion attempt, where do you think the blocked command will appear this time?

  • Run the agent script with the same command you used for the first capture.
  • Submit the same deletion prompt from the text test.

You should see an openrouter:armor_blocked item where the executable function_call would have appeared. The item should preserve the blocked tool name plus its arguments.

That gives you the second sample. Both responses enforce the same hard-deny rule through different structures.

Reading the Custom-Item Result

  • The openrouter:armor_blocked type lets your application identify a block without parsing assistant text.
  • The arguments field preserves the command proposed by the model.
  • The explanation field identifies the Armor rule that fired.
  • The executable function_call remains absent from the response.
  • Keep the complete custom_item response available beside the first capture.

Missing the Custom Item?

  • Confirm that the script sends its request through the Responses API.
  • Check that the output_mode value is exactly custom_item.
  • Confirm that the deletion attempt still matches the hard-deny rule in your unchanged Armor rules file.

Need help reading the response? Help me find the blocked item in my Armor output.

Compare the responses side by side

Both tests use the same deletion prompt. The output mode is the only configuration value that changes between them.

  • Place the complete text response on the left side of your screen.
  • Place the complete custom_item response on the right side of your screen.
  • Locate the <openrouter_armor_blocked> marker in the left response.
  • Locate the openrouter:armor_blocked item in the right response.
  • Confirm that neither response exposes an executable deletion call to run_command.

Before the final run, do you expect the output mode change to affect your trusted-domain allow rule?

  • Switch back to the agent script.
  • Run the agent script with the same command you used earlier.
  • Submit the trusted-domain download prompt from the previous step.

You should see the trusted-domain download reach the run_command tool implementation. This proves that changing the response format left your hard-deny rule plus your soft-deny rule plus your allow exception intact.

You have now compared both blocked formats without weakening the policy that protects your agent.

Your agent can now expose Armor decisions in two useful formats. Next up, you will record allowed calls plus blocked calls in an audit log.

Write an Agent Audit Log

Your OpenRouter Armor comparison now shows how one blocked request appears across the supported output modes. Each snapshot captures one moment.

A local audit log preserves every attempt after terminal output scrolls away. In this step, you will record the attempted command plus Armor's decision.

In this step, get ready to:
  • Add a shared audit writer to the agent's decision handling.
  • Capture allowed, blocked, and flagged tool-call attempts.
  • Verify the complete audit trail with the three existing scenarios.
Add the audit writer

One shared writer gives every attempt the same record shape. The allowed path provides the first visible test because it still reaches the tool implementation.

What belongs in each record?

  • Store the attempted run_command input so the log shows what the model requested.
  • Store Armor's decision so the log shows how the request was handled.
  • Append one record per attempt so earlier activity remains available.
  • Switch back to the agent script from earlier.
  • Locate the existing allowed path that hands an approved call to the tool implementation.
  • Replace that direct handoff with a shared audit writer that records the attempt before the tool implementation receives it.
  • Save the agent script.
  • Rerun the trusted-domain download scenario from earlier.

You should see the local audit log appear. Its first record shows the trusted-domain command with an allowed decision.

Good progress. Your agent now preserves allowed activity without changing whether the trusted command reaches the tool.

Audit log missing?

  • Check that the audit writer runs after Armor returns its decision.
  • Check that the writer appends a record before the allowed call reaches the tool implementation.
  • Check that the agent can write to the local audit log location.

Still missing the record? Help me trace the allowed audit path.

Capture blocked and flagged attempts

Blocked or flagged attempts stop before the tool implementation. Their records must come from the agent's Armor response handling.

Why log at the decision layer?

The decision layer can see allowed, blocked, and flagged outcomes. Logging there preserves attempts that never reach the tool implementation.

  • Route the existing hard-deny path through the shared audit writer.
  • Save the agent script.
  • Rerun the file-deletion scenario from earlier.

You should see a second audit record for the destructive command. Its decision is blocked.

  • Route the existing soft-deny path through the shared audit writer.
  • Save the agent script.
  • Rerun the network-download scenario from earlier.

You should see a third audit record for the network command. Its decision is flagged.

Only allowed calls appear?

  • Check that blocked response handling reaches the audit writer before returning its result.
  • Check that flagged response handling reaches the same writer before returning its result.
  • Keep the tool implementation outside both denied paths.

Need another pair of eyes? Help me trace the missing decision path.

Verify the complete audit trail

Before you run the final check, do you expect all three decisions to appear in the order you trigger them?

  • Run the trusted-domain allow scenario from earlier.
  • Run the hard-deny file-deletion scenario from earlier.
  • Run the soft-deny network-download scenario from earlier.
  • Return to the local audit log.

You should see a short sequence of records. One record is allowed. One record is blocked. One record is flagged.

The attempted command appears beside Armor's decision in every record. The allowed call still reaches the tool implementation.

✔️ Awesome, I've got everything!

Your agent now appends allowed, blocked, and flagged attempts to one local audit log.

ⓧ I'd like to double check the full code

Compare your agent's complete flow against these final behaviors.

  • Confirm the existing OpenRouter request still includes the run_command tool.
  • Confirm the existing request still enables Armor with the rules from earlier.
  • Confirm every decision path reaches the shared audit writer.
  • Confirm each record contains the attempted command plus Armor's decision.
  • Confirm allowed calls continue to reach the tool implementation.
  • Confirm blocked or flagged calls stay out of the tool implementation.

Strong finish. Your guarded agent now leaves a local record of every command it attempted plus the decision Armor made.

Secret mission

Audit Your Armor Policy Evidence

Your agent has enforcement rules backed by an audit trail. Cross-check each rule tier against its recorded decision. Then prove the blocked result stays consistent across output modes.

Clean Up Your Resources

Clean Up Your Resources

No cloud process keeps running after you stop the agent. Future requests can still incur OpenRouter model-token charges.

Check Pricing After Beta

Armor evaluations are not billed during the beta. Pricing may change after the beta ends.

Resources you used:

  • Your local agent script.
  • Your local Armor rules file.
  • Your local agent audit log.
  • Your captured Armor output comparison.
  • The OPENROUTER_API_KEY environment variable stored locally.
  • The OpenRouter API key created in the dashboard.

Keep everything running

This option keeps the guarded agent ready for more tests.

  • Keep the agent script in its current local location.
  • Keep the Armor rules file for future agents.
  • Keep the agent audit log as a record of previous tool-call decisions.
  • Keep the captured Armor output comparison with your project records.
  • Keep the OpenRouter API key active only while you plan to send more requests.
  • Review current OpenRouter pricing before using Armor after the beta.

Pause - I'll come back to this later

This option keeps your local work without sending more model requests.

  • Stop any active agent process in the terminal panel from earlier.
  • Leave the agent script in its current local location.
  • Leave the Armor rules file in its current local location.
  • Leave the agent audit log in its current local location.
  • Leave the captured Armor output comparison with your project records.
  • Avoid sending new OpenRouter requests while the project is paused.
  • Keep the dashboard key only if you plan to resume testing.

Delete - I don't want to use this again

Deleting the key is permanent. Your OpenRouter account remains available.

The dashboard key and the local environment variable both provide access to the same credential.

  • Return to the OpenRouter dashboard from earlier.
  • Go to the API keys area where you created the project key.
  • Locate the key created for this project.
  • Use that key's deletion control.
  • Approve the deletion confirmation.
  • Confirm the project key no longer appears in the API keys area.

The local environment can retain the key after its dashboard copy is deleted.

  • Remove the entry that sets OPENROUTER_API_KEY from the same local environment configuration you used earlier.
  • Close each terminal session that already loaded the variable.
  • Confirm a fresh terminal session no longer contains OPENROUTER_API_KEY.

The remaining artifacts live in the local location you used throughout the project.

  • Return to that local location with your file manager.
  • Select the agent script.
  • Select the Armor rules file.
  • Select the agent audit log.
  • Select the captured Armor output comparison.
  • Use your file manager's deletion option.
  • Confirm the selected project artifacts no longer appear in that location.

Cleanup complete. This agent no longer has its project key or local artifacts.

Nice Work!

Nice Work!

You did it! Your guarded AI agent now uses OpenRouter Armor to screen shell tool calls before they reach your code.

You've learned how to:

  • Sent an agent request with the run_command tool. You observed the model return a tool call.
  • Wrote hard-deny rules for file deletion. Added soft-deny handling for network downloads. Scoped an allow exception to one trusted domain.
  • Compared Armor output modes side by side. Logged every allowed, blocked, or flagged attempt in an audit log.
  • Completed the optional Secret Mission to extend your agent safety skills.

Ready to quiz yourself?