Repository navigation
Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
This was referenced Oct 1, 2026
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 1, 2026 21:53
95c3b23 to
d88d2c2
Compare
This was referenced Oct 1, 2026
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 1, 2026 22:05
d88d2c2 to
e297cd8
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 1, 2026 22:05
207f588 to
afee845
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 2, 2026 13:22
afee845 to
6a3c667
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 2, 2026 13:22
e297cd8 to
3bd2b9c
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 2, 2026 19:43
6a3c667 to
f3ade85
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 2, 2026 19:43
3bd2b9c to
2304606
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 2, 2026 21:17
f3ade85 to
ca27fd2
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 2, 2026 21:17
2304606 to
41588ad
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 2, 2026 23:32
ca27fd2 to
f98be7a
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
2 times, most recently
from
October 6, 2026 19:28
c9f8a0f to
e4c7644
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 6, 2026 19:28
f98be7a to
beab099
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 6, 2026 20:54
e4c7644 to
ae35d61
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 6, 2026 20:54
beab099 to
56eb43b
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 6, 2026 22:42
56eb43b to
e5966ac
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
2 times, most recently
from
October 13, 2026 17:55
592537f to
f39aa55
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
2 times, most recently
from
October 13, 2026 20:27
b12f9ea to
3aef285
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 13, 2026 20:27
f39aa55 to
17a509f
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 13, 2026 22:56
3aef285 to
875b5bc
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 13, 2026 22:56
17a509f to
3baed96
Compare
15 tasks
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 14, 2026 00:27
3baed96 to
726383b
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
2 times, most recently
from
October 14, 2026 17:33
7e44eb9 to
3a8cbc5
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 14, 2026 17:33
009480a to
a354cb3
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 14, 2026 20:36
3a8cbc5 to
f65c51a
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 15, 2026 13:21
3a8ddf3 to
dc6f470
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 15, 2026 13:21
f65c51a to
7134034
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 15, 2026 15:41
dc6f470 to
d83a4e0
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 15, 2026 15:41
7134034 to
28a5942
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 15, 2026 16:54
d83a4e0 to
9fd69f4
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 15, 2026 17:37
28a5942 to
e572248
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 15, 2026 17:37
9fd69f4 to
31ad088
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 15, 2026 18:16
e572248 to
73f22ef
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 15, 2026 18:16
31ad088 to
53aa609
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 15, 2026 18:55
73f22ef to
6d86c2d
Compare
ananthsub
force-pushed
the
ananthsub/partial-ckpt-e2e
branch
from
October 15, 2026 18:55
53aa609 to
9b5c297
Compare
The indirect prompt injection resources server keeps a mutable environment per cookie session, so it now declares checkpoint_mode "exported" and implements the three session hooks: export each live session's environment, restore a batch only after every state validates, and drop a retired session. Verification keeps the default "wait" mode because it deletes the session. Export leaves out a session the server already dropped, such as one whose verification raised, so a later commit does not fail on it. The environment is copied through JSON, which keeps its key order: tool outputs serialize the environment as stored, so a restored session answers byte for byte as the original would. Ported from #3548 onto the v2 checkpoint hooks. The execution identity map, header identity lookups, mutation receipts, revisions, and the verify identity check are not carried: the v2 participant keys sessions by cookie session ID and relies on ordering instead of receipts. Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
ananthsub
force-pushed
the
ananthsub/partial-ckpt-indirect-prompt-injection
branch
from
October 16, 2026 00:30
6d86c2d to
1568519
Compare
This was referenced Oct 19, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed and why
The indirect prompt injection resources server keeps a mutable environment for each session: tool calls update patient charts, send messages, and so on. Before this change it was restart-only by default, so every rollout with a live session started over from its input after a crash. Now a rollout interrupted by a crash can continue from its last checkpoint.
checkpoint_mode = "exported"and implements the three session hooks, keyed by the cookie session ID:export_session_statesreturns each live session's environment as anIPICheckpointState;restore_session_statesvalidates every state first, then installs them all, so an invalid state installs nothing;retire_session_statedrops the session.checkpoint_verifystays at the defaultwait, because/verifydeletes the session's environment and cannot be replayed./verifydrops the session in itsfinallyblock even when verification raises, and the participant only stops tracking a session on a successful/verify. Without this, the next commit after a failed verification would fail.How it works
Where this PR sits in the overall flow
The highlighted part is what this PR adds.
flowchart LR C["Controller<br/>NeMo RL, or rollout collection"] CO["Coordination<br/>prepare, commit, restore, resume,<br/>retire, forget"] K["Control plane on every server<br/>phases, lease, storage,<br/>retire: refuse, stop, free;<br/>forget"] subgraph G["One participant per Gym server"] E["Environment server<br/>episode steps"] M["Policy model<br/>held responses, generation cuts"] A["Agent<br/>sessions parked at boundaries"] R["Resources server<br/>session state"] end W["Inference worker<br/>stages cut prefixes"] D[("Checkpoint directory<br/>records, then manifest")] L[("Capture ledger<br/>retire and delete from #3938, #3939")] C --> CO --> K K --> E & M & A & R M --> W M --> L G --> D classDef this fill:#fde68a,stroke:#b45309,stroke-width:2px,color:#1f2937 class R thisOne checkpoint, a crash, and the restore, end to end:
This PR
Each session's environment, saved at a checkpoint. It is copied through JSON, which keeps its key order, so a restored session answers byte for byte as the original would.
Where this sits in the stack
This PR builds on the partial-rollout checkpointing stack: #3882 (core) through #3889 (end-to-end suite), plus #3893 (rollout collection). It is one of five PRs that port the environments the old stack checkpointed onto the new hooks. Each one is based on #3889 and can be reviewed on its own, except Blackjack, which builds on the Gymnasium fix:
ananthsub/partial-ckpt-workplace-assistant): feat(checkpoint): checkpoint Workplace Assistant sessionsananthsub/partial-ckpt-gymnasium): fix(gymnasium): build Gymnasium servers on the shared resources server setupananthsub/partial-ckpt-blackjack): feat(checkpoint): export Blackjack game state for partial-rollout checkpoints (on the Gymnasium PR)ananthsub/partial-ckpt-indirect-prompt-injection): feat(checkpoint): export indirect prompt injection session state (this PR)ananthsub/partial-ckpt-proof-refinement): feat(checkpoint): continue proof refinement agent turns from their boundariesRelationship to the old stack
This supersedes #3548, which added the same recovery on the old (v1) checkpoint stack. Ported from #3548 onto the v2 checkpoint hooks.
Carried over:
Not carried, because the v2 stack removes the mechanisms they served:
execution_to_sessionmap and the identity lookups from rollout headers, since v2 keys resources state by the cookie session ID;/verify;Issue
No separate issue. This is part of the partial-rollout checkpointing work, as the port of draft #3548.
Validation
Run on this branch, on the current
main:pytest -q resources_servers/indirect_prompt_injection/tests: passed, including the newtest_checkpoint.py. They drive the server through its real/ng-control/v1/checkpoint/*routes over ASGI:/verify. Tool outputs and the verify reply match an uncheckpointed run exactly. After verification, the restored server and its participant hold no session;pytest -q tests/unit_tests/test_checkpoint_*.py(withRAY_TMPDIR=/tmp): passed.ruff checkandruff formaton the server: clean.pre-commit run --fileson the changed files: passed.Rollout evidence
Setup
The model was
Qwen/Qwen3-4B-Instruct-2507, served by vLLM 0.29.0 (the version Gym'svllmextra pins, with torch 2.13.0+cu130) using thehermestool-call parser,--max-model-len 32768and--gpu-memory-utilization 0.45on one NVIDIA RTX 6000 Ada Generation (48 GB).Gym's
vllm_modelserver ran withuses_reasoning_parser=false, because the served model is an instruct model without a reasoning parser.Every rollout sampled at temperature 1.0, used the five tasks in
resources_servers/indirect_prompt_injection/data/example.jsonl, and ran Simple Agent withmax_steps: 5, behind the environment server that relays flat rows to the agent's own/runepisode.Each tree ran in its own worktree, with server venvs created by
gym env start.Uninterrupted baseline
This change (
156851958c) and its base (80ace9f0f9) each ran with checkpointing off, eight rollouts per task:gym env start --config resources_servers/indirect_prompt_injection/configs/indirect_prompt_injection.yaml --model-type vllm_model \ --model Qwen/Qwen3-4B-Instruct-2507 --model-url http://127.0.0.1:8100/v1 --model-api-key dummy \ ++policy_model.responses_api_models.vllm_model.uses_reasoning_parser=false gym eval run --no-serve --config resources_servers/indirect_prompt_injection/configs/indirect_prompt_injection.yaml --model-type vllm_model \ --model Qwen/Qwen3-4B-Instruct-2507 --model-url http://127.0.0.1:8100/v1 --model-api-key dummy \ ++policy_model.responses_api_models.vllm_model.uses_reasoning_parser=false \ --agent indirect_prompt_injection_simple_agent --input resources_servers/indirect_prompt_injection/data/example.jsonl \ --output rollouts.jsonl --num-repeats 8 --concurrency 16 --temperature 1.0Both trees gave the same result over 40 rollouts: a mean reward of 0.80, no injection followed, and every rollout safe.
Four tasks succeeded 8 of 8 times on both trees. The first task's user message in
example.jsonlis the placeholder..., so the model never calls the tools the task requires and its utility reward is 0 on both trees.Neither run had failed or masked rollouts or tracebacks in the server logs.
Checkpoint, crash and restore
The tree was this change, which already contains the process-level checkpoint suite.
A driver built on that suite (
tests/e2e/checkpoint/checkpoint_deployment.py) started the policy model, the indirect prompt injection resources server, Simple Agent and the flat-row environment server, each as its own process from its own venv.It sent 80 rollouts (16 per task) to the environment server's
/runand drove the checkpoint throughnemo_gym._checkpoint.coordination, as a training controller would:SIGKILL, start fresh processes from the same configs, restore the committed episodes and resume.Prepare returned in 36 ms and reported no restarts.
Simple Agent reported all 59 in-flight episodes at a boundary. The resources server exported 58 sessions: the remaining episode was parked after its last model call, ready to return, and the server no longer held its session.
28 episodes had already made a successful state-changing tool call before the checkpoint.
Seven had prescribed medication, 11 had moved candidate
C-4552to "Interview Scheduled", and 10 had moved candidateC-1002to "offered".Each export differs from the environment its task row seeds exactly where that call changed it. For example, a prescription episode's export differs only under
prescriptions.P-7742, and a candidate episode's export differs only incandidates.C-4552.status.Every exported environment keeps the seeded key order, and the second checkpoint exported every restored session unchanged.
After the restore, all 59 continued episodes resumed as attempt 1 and returned 200.
Each final trajectory starts with exactly the tool calls, arguments and outputs recorded at the checkpoint.
16 episodes made their state-changing call on the restored session after the restore: nine prescriptions and seven candidate status updates. Each call succeeded.
13 tool calls made after the restore have an uninterrupted baseline call with the same arguments, and each returned the same output, byte for byte.
The run's mean reward over all 80 rollouts was 0.80, the same as the uninterrupted baseline, with the same per-task results.
No server log had a traceback.
The runs only exercised commit, restore and resume. No episode was reported as a restart, so no retire ran.
Compatibility
schema_version: 1, the environment as a JSON object) is new and does not read checkpoints written by feat(checkpoint): add indirect prompt injection state recovery #3548.