How opfor delivers conversation context to an HTTP agent target across turns, and how it threads a session id. One model, shared by opfor run (config file, wizard, SDK), the MCP server tool, and opfor hunt.
There are two independent decisions.
1. Stateless vs stateful — who holds the history
2. Client-owned vs server-owned — who mints the session id (stateful only)
Does your target return a session id in its response (body field, header, or Set-Cookie)? That’s server-owned. Does it just expect you to supply an id it keys on? That’s client-owned. Raw LLM API with no session concept? That’s stateless.
The session config
- Presence of
receive selects server-owned mode; its absence is client-owned.
send is always required when session is set, and name on it is required.
send.in: "body" → name is a dot-path in the request body; "header" → an HTTP header name.
receive.in: "body" or "header" both require a non-empty name. "set-cookie" is the only receive location where name is optional — it captures the returned cookie pair, echoed back via a Cookie header (set send to { "in": "header", "name": "Cookie" }).
- Legacy alias:
sessionIdField: "x" (CLI) / sessionField: "x" (SDK) is shorthand for session: { send: { in: "body", name: "x" } } — client-owned, body. Still honored; prefer session for new configs.
Server-owned turn-1 flow
- Turn 1 — the request goes out with no session id.
- opfor reads the returned id via
receive and remembers it.
- Turns 2+ — that id is echoed via
send.
If a server-owned target returns no id, opfor falls back to a client-minted id and logs a warning.
Per-surface syntax
Examples
Client-owned (body)
Server-owned (body)
Other target types
- Local-script (
target.type: "local-script"): sessionId is always included in the stdin JSON on multi-turn attacks; your script owns the history. session / stateful do not apply.
- Browser extension: drives a live chat UI in the DOM — there is no session id to configure.
For opfor hunt, each attack thread invents its own id for client-owned targets. For server-owned targets, each thread captures the target’s returned id independently — and because the session belongs to the target, forking a thread starts a new server session (opfor can’t fork a session it doesn’t own).