cd /news/developer-tools/openai-responses-api-previous-respon… · home topics developer-tools article
[ARTICLE · art-119710] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

OpenAI Responses API previous_response_id Instructions: Repeat Policy on Every Turn

A developer highlights a counterintuitive boundary in OpenAI's Responses API: the previous_response_id carries conversation state forward but does not carry the prior top-level instructions. The developer recommends a request builder that requires instructions on every policy-bound turn to prevent accidental omission, and provides a .NET 10 sample demonstrating the pattern.

read4 min views1 publishedSep 3, 2026

OpenAI Responses API previous_response_id instructions have a counterintuitive boundary: the response ID carries conversation state forward, but it does not carry the prior top-level instructions

. If my application uses that field for output format, tool rules, or safety constraints, turn two can quietly run without the policy I expected.

I prefer to make that boundary visible in request construction. A small guard can require instructions on every policy-bound turn, while still allowing the application to replace them deliberately.

The Responses API supports several state strategies. With previous_response_id

, a new response can continue from an earlier one without resending the full conversation. OpenAI's conversation state guide shows that chaining pattern directly.

That convenience does not make every request option persistent. The create response reference says that earlier instructions

are not carried into a request that uses previous_response_id

. The new request can supply the same instructions, different instructions, or none.

This is useful when I want to change behavior mid-thread. It is risky when the omission is accidental.

Consider an application that expects every answer to follow a JSON contract:

const string Policy =
    "Answer in JSON with keys summary and risks.";

var first = new
{
    model = "gpt-5.6",
    instructions = Policy,
    input = "Review the deployment plan.",
    store = true
};

A continuation that sends only the new input and the earlier response ID has conversation context, but not that top-level policy. I therefore build the second turn with the policy beside the continuation ID:

var second = new
{
    model = "gpt-5.6",
    instructions = Policy,
    input = "Now focus on rollback.",
    store = true,
    previous_response_id = firstResponseId
};

The distinction is simple: response lineage and request instructions solve different problems. I do not treat one as an implied copy of the other.

I encode the rule in one request builder instead of relying on every call site to remember it. The builder accepts the model, input, instructions, and one optional state reference:

internal sealed record ResponsesTurn(
    string Model,
    string Input,
    string Instructions,
    string? PreviousResponseId = null,
    string? ConversationId = null);

The guard rejects blank instructions before any transport code runs:

ArgumentException.ThrowIfNullOrWhiteSpace(turn.Model);
ArgumentException.ThrowIfNullOrWhiteSpace(turn.Input);
ArgumentException.ThrowIfNullOrWhiteSpace(turn.Instructions);

This is an application invariant, not an API requirement. The API's instructions

field is optional. My builder makes it mandatory because this particular application says every turn must carry policy.

I also reject a payload that combines previous_response_id

with conversation

:

if (turn.PreviousResponseId is not null &&
    turn.ConversationId is not null)
{
    throw new InvalidOperationException(
        "previous_response_id and conversation cannot be sent together.");
}

That mirrors the documented request contract and keeps the state strategy unambiguous. If I decide to move from response chaining to a durable Conversation object, I make that a deliberate code change rather than emitting both fields and waiting for an HTTP 400.

The merged sample on main is a dependency-free .NET 10 executable. It builds three fixed payloads: a first turn, a continuation that repeats the policy, and a continuation that intentionally replaces it.

Its verifier checks seven behaviors. It confirms that the first request carries instructions

and store: true

; the chained request carries both the policy and its fixture response ID; replacement instructions survive serialization; blank instructions and IDs fail locally; the two state mechanisms cannot be combined; and identical inputs produce byte-identical JSON.

The merged pull request records the exact validation commands and results. Restore, formatting, Release build, package inventory, and vulnerability audit pass. Five repeated verifier runs produce identical output. No account, API key, paid request, or model call is needed.

I like this test because it validates the part my code controls. A model call would add latency and output variability without proving that every future call site constructs the payload correctly. The offline fixture makes omission a deterministic build failure.

This sample is not an OpenAI SDK replacement. It writes a narrow JSON shape with string input, sets store: true

, and stops before HTTP. A production integration should use a current supported SDK or a complete HTTP client, handle API errors, and test its actual serialization boundary.

The guard also does not fit every state strategy. With store: false

, I need to replay the relevant returned items instead of chaining a stored response. A durable Conversation object has its own lifecycle and cannot be combined with previous_response_id

. Stored response objects also have retention and token-billing implications described in the conversation state guide, so state selection deserves an explicit product decision.

Finally, model instructions are not authorization. I still enforce permissions, tenant boundaries, validation, and destructive-action checks in application code. Repeating policy keeps the model request consistent; it does not turn prose into a security boundary.

What invariant would you add before a chained request leaves your code?

Happy coding!

── more in #developer-tools 4 stories · sorted by recency
── more on @openai 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/openai-responses-api…] indexed:0 read:4min 2026-09-03 ·