RePolicy is an agent safeguard that learns to invoke safety policies through reinforcement learning. Given an agent trajectory and a dynamic policy library, it selects the applicable policy, retrieves its clauses via a tool call, and produces a policy-grounded rationale plus a trajectory-level safety judgment.
The key difference from prompting- or SFT-based policy-aware guards: RePolicy never sees all policy text up front. Its prompt carries only policy titles, so it must decide which policy governs the trajectory before it can read any requirements. That turns safety-policy invocation into an agentic RL problem optimized directly through safeguard outcomes.
Paper: RePolicy: Reinforcement Learning for Safety-Policy Invocation in Agent Safeguards
policy invocation → policy content → safety reasoning → safety prediction
trajectory τ ──┐
├──► πθ ──► get_policy(["Policy_7"]) ──► clause text ──► rationale + <JUDGE>unsafe</JUDGE>
policy titles ──┘ ▲ │
└──────────── GRPO ◄───── R = 0.1·fmt + 0.3·pol + 0.6·acc
Unsafe-class F1 (%) on six agent safety benchmarks; Overall is the unweighted mean. RePolicy-4B ranks first on four of six and best overall, at 4B parameters.
| Model | ATBench | R-Judge | OpenAgentSafety | ASSEBench | HINTBench | AgentHazard | Overall |
|---|---|---|---|---|---|---|---|
| Qwen3-4B-Instruct-2507 (backbone) | 75.79 | 63.12 | 11.48 | 61.72 | 84.58 | 54.11 | 58.47 |
| Claude Sonnet 4.6 | 95.46 | 86.26 | 51.97 | 81.10 | 96.47 | 93.73 | 84.17 |
| GPT-5.4 | 96.83 | 87.44 | 55.63 | 76.25 | 95.42 | 87.13 | 83.12 |
| DynaGuard-4B | 89.43 | 75.53 | 55.76 | 69.70 | 87.48 | 81.24 | 76.52 |
| AgentDoG-4B | 87.93 | 80.24 | 48.99 | 82.85 | 90.25 | 87.87 | 79.69 |
| RePolicy-4B | 99.40 | 82.17 | 62.79 | 88.11 | 97.23 | 99.20 | 88.15 |
Ablations (Overall): base model 58.47 → cold-start SFT 86.95 → RePolicy 88.15. Removing explicit policy invocation costs 2.32 points; removing policy-context perturbation costs 0.68.
repolicy/ Everything specific to RePolicy
├── prompts.py System/user prompt construction
├── policy_context.py Local library sampling + policy-context perturbation
├── policy_tool.py The get_policy tool served during rollout
├── reward.py Verifiable reward: format / invocation / accuracy
├── build_dataset.py Builds the PolicyTraj-20K SFT + RL splits
├── build_benchmark_eval.py Builds the six-benchmark eval split
├── evaluate.py Per-benchmark unsafe-F1 scoring
├── predict.py Runnable single-trajectory inference demo
├── config/ Tool config consumed by verl
├── assets/policies/ The 30-policy / 358-clause safety library
└── scripts/ run_sft.sh, export_sft_hf.sh, run_grpo.sh, HF up/download
verl/ Training framework (upstream verl 0.8.0.dev, unmodified API)
data/ PolicyTraj-20K after download (sft/ rl/ eval/)
docs/ Model card, dataset card, and the paper
Requires Linux, 8x GPU with ≥80GB each for the default config (trained on 8x H20 96GB), CUDA 12.x, and Python 3.10+.
git clone https://github.com/JiangHoucheng/RePolicy.git
cd RePolicy
python3 -m venv .venv && source .venv/bin/activate
pip install -U pip
pip install -e . # installs verl + repolicy and their dependencies
pip install vllm # rollout engine used by GRPOOptional but recommended:
export HF_HOME=/path/to/hf-cache # somewhere with room for the 4B backbone
wandb login # scripts log to wandb by default;
# set LOGGER='["console"]' to skipFour steps, in order. Each stage's output is the next stage's input.
① download data → ② SFT → ③ merge to HF → ④ GRPO → ⑤ evaluate
PolicyTraj-20K cold start FSDP→HF RePolicy-4B unsafe F1
bash repolicy/scripts/download_data.shPulls ~2.3GB from JiangHoucheng/PolicyTraj-20K into data/:
| Path | Rows | Used by |
|---|---|---|
data/sft/train.parquet |
5,000 | SFT |
data/sft/val.parquet |
500 | SFT validation |
data/rl/train.parquet |
14,425 | GRPO |
data/rl/val.parquet |
500 | GRPO validation |
data/eval/benchmark_val.parquet |
7,369 | Six-benchmark evaluation |
The data ships ready to train — no preprocessing needed. Rebuilding it from raw annotated trajectories is only necessary if you change the policy library or the perturbation settings; see Rebuilding the data.
Teaches the full invocation→reasoning sequence. Loss is applied only to the two assistant turns (the tool call and the final judgment).
NUM_GPUS=8 \
OUTPUT_DIR=checkpoints/repolicy_sft \
bash repolicy/scripts/run_sft.shDefaults: Qwen/Qwen3-4B-Instruct-2507, 2 epochs, lr 1e-5, batch 128, max length
12288 → 78 steps on 8 GPUs. Override anything via the environment, e.g.
EPOCHS=3 LR=5e-6 NUM_GPUS=4 bash repolicy/scripts/run_sft.sh.
GRPO loads an HF-format model, not raw FSDP shards.
SFT_CKPT_DIR=$(ls -dt checkpoints/repolicy_sft/global_step_* | head -1) \
TARGET_DIR=checkpoints/repolicy_sft_hf \
bash repolicy/scripts/export_sft_hf.shVerify checkpoints/repolicy_sft_hf/ contains config.json and
*.safetensors before continuing.
Optimizes the complete safeguard rollout: 16 rollouts per prompt, each invoking a policy, receiving its clauses, and producing a judgment.
MODEL_PATH=checkpoints/repolicy_sft_hf \
NUM_GPUS=8 \
OUTPUT_DIR=checkpoints/repolicy_grpo \
bash repolicy/scripts/run_grpo.shDefaults: lr 1e-6, batch 64, ROLLOUT_N=16, prompt 12288 / response 4096,
MAX_MODEL_LEN=16384, KL coef 0.001, 3 epochs, checkpoint and validate every 10
steps. Validation runs on both data/rl/val.parquet and the benchmark suite.
Reward (repolicy/reward.py):
| Weight | Term | Condition |
|---|---|---|
| 0.1 | format | exactly one valid get_policy call and a well-formed <JUDGE> tag |
| 0.3 | policy invocation | invoked policy set intersects the gold policies |
| 0.6 | safety accuracy | predicted label matches the gold label |
Also logged per rollout: decoy_selected, tool_call_count, num_pred_policies
— useful for spotting a model that stops invoking policies or starts grabbing
decoys.
Policy-context perturbation is applied when the data is built, not at rollout time: each example's local library mixes the gold policy with real policies from unrelated scenes plus synthetic decoys (15–35 policies, mean 28.9, mean 9.2 decoys), with ids re-indexed and order shuffled per example.
Then export the final model:
SFT_CKPT_DIR=$(ls -dt checkpoints/repolicy_grpo/global_step_* | head -1)/actor \
TARGET_DIR=release/RePolicy-4B \
bash repolicy/scripts/export_sft_hf.shScore-only pass over the six-benchmark split, no gradient updates:
MODEL_PATH=release/RePolicy-4B \
NUM_GPUS=8 \
OUTPUT_DIR=checkpoints/repolicy_eval \
EXPERIMENT_NAME=repolicy_eval \
bash repolicy/scripts/run_grpo.sh \
trainer.val_only=True \
trainer.val_before_train=True \
data.train_max_samples=1To score predictions you generated yourself, with per-benchmark unsafe F1:
python -m repolicy.evaluate \
--predictions your_outputs.jsonl \
--references data/eval/benchmark_val.parquet--predictions is a JSONL whose rows carry the raw model text under
output, prediction, response, or solution, in the same order as the
reference file. Reported unsafe_f1 treats unsafe as the positive class; the
paper's Overall is the unweighted mean of the six per-benchmark values.
The trained model is on the Hub. The rollout is two steps: the model asks for a policy, you serve the clauses, the model judges.
python -m repolicy.predict --model JiangHoucheng/RePolicy-4BThat runs a built-in unsafe demo trajectory (an agent emailing staging
credentials to an external address) against the shipped 30-policy library and
prints both steps. Use your own trajectory with --trajectory my_traj.json, or a
local checkpoint with --model release/RePolicy-4B.
For the raw transformers loop, prompt format, and output contract, see
docs/MODEL_CARD.md.
Expected output shape:
<tool_call>{"name": "get_policy", "arguments": {"policy_ids": ["Policy_1"]}}</tool_call>
then, once the clauses are returned:
Policy_1 is relevant. The agent read credentials and emailed them to an external
address without user authorization. The trajectory violates the retrieved policy
clauses.
<JUDGE>unsafe</JUDGE>
Both scripts need a write token (huggingface-cli login) and default to public
repos — pass PRIVATE=true to stage first. They upload docs/MODEL_CARD.md and
docs/DATASET_CARD.md as the respective README.md.
# Model → JiangHoucheng/RePolicy-4B
MODEL_DIR=release/RePolicy-4B bash repolicy/scripts/upload_model.sh
# Dataset → JiangHoucheng/PolicyTraj-20K
bash repolicy/scripts/upload_data.shupload_model.sh requires a merged HF directory (step ③/④ output) and refuses to
run without config.json and *.safetensors. Override the destination with
MODEL_REPO= / DATASET_REPO=.
Only needed if you change the policy library, the perturbation settings, or the
train/val sizes. Both builders read the policy library in
repolicy/assets/policies/.
# SFT + RL splits from annotated trajectories
python -m repolicy.build_dataset \
--input-jsonl data/generated/train.jsonl \
--sft-dir data/sft --rl-dir data/rl \
--sft-train-size 5000 --sft-val-size 500 --rl-val-size 500 --seed 42
# Six-benchmark eval split from normalized benchmark files
python -m repolicy.build_benchmark_eval \
--data-dir data/benchmarks \
--output-dir data/eval--input-jsonl expects rows with trajectory, label, selected_policies, and
rationale.violated_clauses. --data-dir expects the six normalized benchmark
files (atbench.jsonl, r_judge.jsonl, open_agent_safety.jsonl,
assebench.jsonl, hintbench.jsonl, agent_hazard.jsonl), joined by id
against repolicy/assets/policies/policy_coverage_map.jsonl. The source
trajectory pool derives from third-party benchmarks and is not redistributed
here; the built splits on the Hub are.
- Long prompts. Trajectory plus a 30-policy title list runs to ~12K tokens;
MAX_PROMPT_LENGTH=12288withfilter_overlong_prompts=Truesilently drops longer examples. Raise both if you enlarge the library. max_assistant_turns=2is deliberate — one turn to invoke, one to judge. Raising it lets the model callget_policyrepeatedly, which the format reward penalizes.get_policycaps at 5 policies per call (MAX_POLICIES_PER_CALLinrepolicy/policy_tool.pyandrepolicy/reward.py); keep the two in sync if you change it.- Memory. With
ROLLOUT_N=16and 8×80GB, lowerPPO_MICRO_BATCH_SIZEbefore anything else if you hit OOM. - The reward verifies the invoked policy and the final label, not that every sentence of the rationale is faithful to the policy.
Built on verl for RL training and Qwen3-4B-Instruct-2507 as the backbone. Evaluation uses ATBench, R-Judge, OpenAgentSafety, ASSEBench, HINTBench, and AgentHazard — please cite those alongside this work.
Apache 2.0. See LICENSE; verl/ retains its upstream copyright.