OpenRouter Armor with Jev

10月16日、OpenRouter Armor のベータ版が公開されました。
Armor は TypeSafe AI の Jev モデルを利用して、エージェントがツールを実行する前に、ポリシー違反のツール呼び出しを防御します。Claude Code の自動分類器を上回るパフォーマンスを、ごくわずかなコストで実現します。

OpenRouter Armor with Jev

作成 : Masashi Okumura (@ClassCat)
作成日時 : 10/20/2026

* 本記事は openrouter.ai/docs の以下のページを参考にしています :

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。

 

 

Openrouter Armor with Jev

10月16日、OpenRouter Armor のベータ版が公開されました。

ツール呼び出しによってエージェントは外部世界とやり取りできるようになりますが、正規表現ベースのフィルタリングでは望ましくないツール呼び出しを十分に検知できないことが多々あります。

Armor は TypeSafe AI の Jev モデルを利用して、エージェントがツールを実行する前に、ポリシー違反のツール呼び出しを防御します。Claude Code の自動分類器を上回るパフォーマンスを、ごくわずかなコストで実現します。

 
モデルとアプリケーションの間で機能する、ツール呼び出し向けのセーフティ分類器

2026年10月よりベータ版提供

シェルコマンドを実行できるコーディングエージェントは、main ブランチへのプッシュ、ディレクトリの削除、あるいは .env ファイルを外部サーバーへ送信してしまう可能性も持ち合わせています。従来のハーネスの多くは、ユーザーに各コマンドの承認を求めることでこれに対処してきました。

Armor は、モデルとアプリケーションの間に介在し、モデルが発行するすべてのツール呼び出しを評価する分類器です。ルールは平易な英語で記述します: モデルが決して行ってはならないこと、通常は行うべきではないこと、許可されること。もし呼び出しがルールに抵触した場合、Armor はそれをブロックしてモデルに理由を伝え、モデルは残りの作業を続行します。

 

仕組み

モデルがツール呼び出しを行うたびに、以下の処理が行われます:

  • OpenRouter は、その呼び出しをまだアプリケーションには転送しません。

  • OpenRouter は、ツール呼び出し、ユーザのルール、およびこれまでの会話履歴を決定 (decision) モデルへと送信します。

  • 各ルールについて、決定モデルはその呼び出しがルールにマッチする確率を返します。

  • Armor はそれらの結果を統合し、flagged(フラグ付き/要対処)または not_flagged のいずれか1つの判定 (verdict) を行います。

 
ルールには3つの種類があります:

  • hard-deny ルールは、決して実行してはならないアクションを記述します。ルールにマッチした場合、その呼び出しはブロックされ、これを解除 (クリア) する手段はありません。

  • soft-deny ルールは、通常は誤り (wrong) であるものの、正当な理由で要求されることもあるアクションを記述します。ルールにマッチした場合にはその呼び出しはブロックされますが、allow ルールにもマッチしている場合や、あるいは会話内でユーザーがまさにその操作を要求していた場合は例外として許可されます。

  • allow ルールは、例外 (exception) を記述します。これが何らかの処理をブロックすることは決してありません; soft-deny によるマッチを解除するためだけに存在します。

Writing rules covers each in detail.

 
判定結果 (verdict) が flagged の場合 :

  • 該当するツール呼び出しは実行をスキップするようにマークされ、同時にルールの名前付けや同じ引数で再試行しない旨の指示といったメタデータが付与されます。

  • モデルは、通常のツール呼び出し失敗時と同様にこれを処理します: より限定的な (narrower) 呼び出しを行うか、あるいは状況をユーザーに説明します。

判定結果が not_flagged の場合:

  • その呼び出しは変更されることなく、そのままアプリケーションへと渡されます。

  • 唯一確認できる違いは、判定結果を記録した、レスポンス内の safeguard_results フィールドだけです。

 

Claude Code auto モードとの併用

OpenRouter Armor は、Claude Code 互換 API を実装しています。OpenRouter の Ori メタハーネス経由で推論を行う場合、Claude Code の auto モードを有効化するための追加設定は一切不要です :

ori claude --model moonshotai/kimi-k2-0905 -- --permission-mode auto

Armor は Claude Code の safeguards エントリを読み込み、Anthropic の純正実装が想定するフォーマットで応答しますので、OpenRouter が提供するあらゆるモデルで auto モードが機能します :

  • どのルールにもマッチしない呼び出しは正常に実行されます。

  • ルールにマッチした呼び出しは、flagged 判定が付与されて返されます。Claude Code はその実行を拒絶し、トランスクリプトに拒否理由を記録したうえで、モデルは処理を継続します。

plugins のエントリは不要です。Ori を介して Claude Code を起動する詳細については、Ori guide を参照してください。

本ページのこれ以降の内容はカスタムエージェント向けとなっており、独自の ‘auto’ モードを構築する方法について説明しています。

 

例

カスタムエージェントが plugins に armor エントリを追加し、classifier_context 下にルールを定義する構成です。この Responses API 向けのリクエストでは、モデルに bash ツールとプッシュ操作を禁じる1つの hard-deny ルールを付与しています。ブロックされた呼び出しが型定義されたアイテム (typed item) として返されるよう custom_item 出力を要求したうえで、モデルに対してプッシュを実行するよう指示しています :

curl https://openrouter.ai/api/v1/responses \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4.1",
    "input": "Lint is fixed. Push the branch with git push origin main.",
    "tools": [
      {
        "type": "function",
        "name": "bash",
        "description": "Run a shell command in the project directory",
        "parameters": {
          "type": "object",
          "properties": { "command": { "type": "string" } },
          "required": ["command"]
        }
      }
    ],
    "plugins": [
      {
        "id": "armor",
        "output_mode": "custom_item",
        "classifier_context": {
          "v": 1,
          "auto_mode": {
            "environment": ["Trusted repository: github.com/acme/app"],
            "hard_deny": [
              {
                "id": "git-push",
                "name": "Git Push",
                "instructions": "Running any git push command. The user reviews every change before it is pushed."
              }
            ]
          }
        }
      }
    ]
  }'

モデルは `git push origin main` を指定して bash を呼び出そうとしますが、Armor がこれをブロックします。レスポンスには function_call は含まれません; 代わりに該当の呼び出しと適用された (fired) ルールを保持する openrouter:armor_blocked アイテム、続いてモデル自身のテキスト、そして safeguard_results はその呼び出しの id に紐づく形で判定結果が記録します :

{
  "output": [
    {
      "type": "openrouter:armor_blocked",
      "id": "st_tmp_6t8hpnhyrui",
      "status": "completed",
      "call_id": "call_EhpTnmfpU1Zx1Iqfl0JZgHqc",
      "name": "bash",
      "arguments": "{\"command\":\"git push origin main\"}",
      "reason": "Tool call \"bash\" was blocked by OpenRouter Armor by the rule \"Git Push\" and was not executed. Do not retry it with the same arguments. If a narrower, safer call would still do what the user asked, make that call instead; otherwise explain the situation to the user.",
      "explanation": "[Git Push]"
    },
    {
      "type": "message",
      "role": "assistant",
      "status": "completed",
      "content": [
        {
          "type": "output_text",
          "text": "I'm unable to run the git push command due to security restrictions in this environment. If you need to push your changes, please run git push origin main directly in your terminal."
        }
      ]
    }
  ],
  "safeguard_results": [
    {
      "type": "dangerous_tool_use",
      "status": {
        "type": "available",
        "tool_uses": {
          "call_EhpTnmfpU1Zx1Iqfl0JZgHqc": {
            "type": "evaluated",
            "outcome": "flagged",
            "explanation": "[Git Push]"
          }
        }
      }
    }
  ]
}

プロンプトを “list the files in ./reports” に変更すると、同じ bash の呼び出しは解放されます (released): これは output[] 内に通常の function_call として出力され、safeguard_results には not_flagged と報告されます。次のターンでは、その openrouter:armor_blocked アイテムを変更せずそのまま input[] に含めて送り返してください; OpenRouter 側でそれがどのように扱われるかについては、custom_item で説明されています。

 

ルールの書き方

Armor による判定 (decisions) の精度は、提供するルールの正確さに直接左右されますので、決定モデルがルールをどのように解釈するかを把握しておくことが重要です :

{
  "v": 1,
  "auto_mode": {
    "environment": [
      "Trusted repository: github.com/acme/app",
      { "name": "Deploy target", "value": "staging only" }
    ],
    "hard_deny": [
      {
        "id": "exfil",
        "name": "Exfiltration",
        "instructions": "Sending files, environment variables or credentials to any destination outside the trusted repository."
      }
    ],
    "soft_deny": [
      {
        "id": "destructive",
        "name": "Destructive",
        "instructions": "Deleting files or directories, or rewriting git history."
      },
      "Reading credential files such as .env or ~/.aws/credentials."
    ],
    "allow": [
      {
        "id": "tests",
        "name": "Test Suite",
        "instructions": "Running the project test suite, including its cleanup of temporary directories."
      }
    ]
  }
}

 

Environment

environment はルールではなくファクト (事実, facts) のためです: ここには、信頼できるリポジトリ・ドメイン・バケットはどれか、デプロイ先はどこか、ユーザーは誰かなど、決定モデルが true として扱うべきファクトを指定します。各エントリは “Name: value” 文字列、または { “name”, “value” } オブジェクトです。environment は、armor 呼び出しにおける「システムプロンプト」として考えることができます。

ルールはこれらのファクトを参照します。たとえば “sending data outside the trusted repository” (信頼されたリポジトリの外部へデータを送信する) というルールは、決定モデルがどのリポジトリが信頼されているかを知っていなければ判定できません。environment の情報が古ければ、それを参照するすべてのルールの判定精度が低下します。

カスタムエージェントは、リクエストを送信するたびに、自身が動作している環境に関して把握している情報をすべて environment に含める必要があります: 具体的には、作業ディレクトリと所属リポジトリ、現在のブランチ (および保護ブランチかどうか)、リモート情報とそれらのホスト、使用中のクラウドアカウントやプロジェクト、デプロイ先、ユーザーのアイデンティティやロール 等です。

 

3 つの階層 (Tiers)

すべてのルールは、以下の 3 つのタイプのいずれかに分類されます :

hard_deny は、ユーザーの指示内容にかかわらず、決して実行してはならないアクションを指定します。hard_deny ルールにマッチした場合、その呼び出しはブロックされ、(allow ルールやプロンプト内でのユーザーのアクション要求など) いかなる手段でもクリア (解除) されることはありません。認証情報の不正流出 (exfiltration) などがこれに該当します。

soft_deny は、通常は不適切 (wrong) であるものの、正当な理由で要求される場合もある操作を指定します。soft-deny のマッチは、以下の2つの条件のいずれかを満たさない限り、呼び出しをブロックします: allow ルールにも一致している場合か、ユーザーが会話内で、その正確な対象に対するその正確な操作を求めている場合です。ディレクトリの削除などがこれに該当します。“Clean up the old drafts” という指示では `rm -rf /` を実行する呼び出しは解除されません; しかし “delete the ./reports/drafts directory, I’m sure” は ./reports/drafts を削除する呼び出しをブロック解除します。

allowは、何かをブロックすることは一切ありません。soft_deny ルールに対する 例外 を定義するために使用します。たとえば、テストスイートが自身の一時ディレクトリを削除する場合、これは “deleting directories” という soft_deny にマッチしますが、“running the project test suite” という allow ルールがあればブロックが解除されます。

 
これらを踏まえると優先関係は自ずと決まります: hard-deny へのマッチは最終決定 (final) です; soft-deny への一致は allow へのマッチまたはユーザーの合意によって解除されます。また、Armor には (“don’t push until I review” のような) ユーザーが会話内で設定しまだ解除していない制約に対する、単一の組み込みの soft_deny があらかじめ備わっているため、個別にルールを書かなくても自動的に保護されます。通常の soft_deny と同様、allow ルールや明示的なユーザーの合意があれば解除されます。

 

ルールの記述形式

ルールは文字列またはオブジェクトです。文字列は instructions (指示) のみです; その ID はその位置から自動的に導出され (soft_deny:1)、ルールが適用された際に判定結果 (verdict) が報告するのはその ID です。判定結果の中でどのルールかを明確に識別したい場合は、オブジェクトを使用してください :

フィールド 必須 備考
id yes 英小文字、数字、-、_ を使用、最大64文字。3 つの階層 (tiers) 全体で一意。ルールはこの値で報告されます
name no 最大 120 文字。判定結果およびモデルに返されるエラー内で [name] と表示されます。省略時は id が使用されます
instructions yes 最大 8,000 文字。このルールが何にマッチするかを、決定モデル向けに記述した指示文
threshold no 0 〜 1。ルールがマッチしたと見なす確率です。デフォルトは deny ルールが 0.6、allow ルールが 0.7

これら 2 つのデフォルト値が異なるのは意図的なものです。これらは Claude Code の auto モードのテストセットに基づいて設定されました。allow ルールはすでにマッチした deny ルールを解除するものなので、より強いエビデンスを必要とします; 0.7 未満では、部分的にしか該当しない allow ルールが強制プッシュ(force push)やリポジトリ削除まで解除してしまうことがありました。

 

指示文 (instructions) の書き方

  • コマンドではなく、アクションを記述する。“Rewriting git history that has been pushed” (プッシュ済みの git 履歴を書き換える) の記述は、`git push –force`、リベース後のプッシュ、そして filter-branch にマッチします。“Running git push –force” は一つの表記にしかマッチしません。

  • ルールが何を保護しているのかを具体的に明示します (name)。“Sending the contents of .env to a host outside the trusted repository” (.env の内容を信頼されたリポジトリ外部のホストへ送信すること) と記述すれば、決定モデルはデータの送信先まで追跡できるようになります。curl に関するルールは、curl にしかマッチしません。

  • 例外は allow に記述します。末尾に “except when running tests” (テスト実行時を除く) と付けた deny ルールは、deny ルールに加えて allow ルールを併用する場合よりも判定が難しくなります。

  • environment を最新の状態に保つ。ファクトを参照するルールは、そのファクトと同程度にしか正確になり得ません。

 

Claude Code 互換ルール

`use_cc_style_default_rules: true` を設定すると、分類器 (classifier) のコンテキストに Claude Code 互換のルールセットがデフォルトで配置されます。ユーザのルールはその後に追加され、組み込みルールを削除することはできません。

デフォルト設定は、Claude Code が使用する API の Messages API では true、Chat Completions および Responses API では false です。Claude Code ではリストエントリ “$defaults” を用いて同じ指定を行いますが、Armor もこれを受け付けます。

 

以上