ideas

Backpressure - Rejecting Negative Hallucinations in Programming Assistants

6 min read programming-assistants, negative-hallucinations, code, backpressure

Translated from the Spanish original with AI assistance. The original was written entirely by hand, no AI involved.

In this fifth and final installment of the “Fundamental Principles for Using Programming Assistants” series we are going to make peace with — even embrace — the fact that no matter how many instructions and examples (steering) we hand an LLM, there is no way to keep it from generating code that is no good, that is: negative hallucinations.

In earlier articles in this series we established that hallucination is the fundamental feature of LLMs, explored the mathematics of the value generated by these interactions, presented a systems view of AI-assisted programming, and detailed steering as a mechanism for favoring positive hallucinations.

Now we are going to define Backpressure as the process and mechanisms that let us signal to the assistant the presence of negative hallucinations that must be corrected by the system. And with this we introduce the fundamental feedback loop these systems need so that the state of the codebase converges toward positive hallucinations.

Why separate Backpressure and leave it as just a signal? Because if some part of the system already has the capability to generate the code, adding correction on top of detection creates a duplication that will automatically compromise the task’s Context, overloading it with information and multiple responsibilities.

From this point of view, type systems, compilers, linters, and automated tests are all deterministic backpressure mechanisms that have traditionally signaled to us that something is not right — in other words, we are not inventing anything new. In this context, whenever we can attack a problem deterministically we should seize that option, because it is highly effective and efficient.

But in this article we are going to concentrate on the process built on the very LLM that sits at the core of programming assistants.

Many people are skeptical about the possibility of fixing the errors an assistant introduced into the codebase with that same assistant, and we have all laughed at the memes to that effect. And indeed, one of the fundamental principles I have proposed in this series is that the probability of errors occurring when a model “slings code” is never going to be zero, so it logically follows that using a model to fix errors implicitly means introducing new errors.

But, if we have good (deterministic) mechanisms and a backpressure process, these should shrink the solution space of error correction enormously compared to the original process of modifying the code, minimizing the chance of introducing new negative hallucinations. And if we apply this reduction of the solution space through an iterative process, the system should in general converge toward a state where negative hallucinations are minimized.

So let’s describe an Interaction (i) with a Programming Assistant (ap) in an Environment (e) (codebase, etc.) with a Context (c) through a Prompt (p) as follows:

i(ap,e,c,p) = (e',c',r)

In the resulting tuple, e’ is the modified environment, usually with changes to the codebase, and c’ is the mutated context that led to the modification of the original environment e according to the prompt p, plus a response r that we will distinguish from the mutation of the context.

That environment e’ can be described as:

e' = e ∪ A₊ ∪ A₋ 

That is, the resulting environment e’ equals the environment the interaction i was applied to, joined with the set of all the positive hallucinations A₊ (which add value for us) and the set of all the negative hallucinations A₋ (which subtract value from us).

Now, let’s postulate that any prompt can be expressed through the following structure:

p = "Following Instructions I, perform Task T"

Then I can always formulate a family of backpressure prompts based on a prompt p, where each one has the form:

p̃ = "Given that Task T was performed
     following Instructions I,
     Now follow instructions I'
     to verify F
     "

Where F is the Focus of the verification, because backpressure can, and should, be applied along multiple dimensions.

There are two canonical examples of F; the first verifies the completeness of the task entrusted to the ap:

p̃ = "Given that Task T was performed
     following Instructions I,
     Now follow instructions I'
     to verify THAT THE TASK WAS PERFORMED IN ITS ENTIRETY
     "

The second verifies the correctness of the implementation:

p̃ = "Given that Task T was performed
     following Instructions I,
     Now follow instructions I'
     to verify THAT THE TASK WAS PERFORMED FOLLOWING THE INSTRUCTIONS
     "

The exact form does not matter, what matters is that the formulation implies the detection of a negative hallucination along one of the dimensions where these could have occurred in our environment and context. That is:

i(ap,e,c,p̃) = (e',c',r)

Where r ⊆ A₋, meaning the response of the ap to the verification prompt is a subset of the negative hallucinations introduced in the previous interaction. r should shrink the solution space, giving the ap a focus through a very specific context where the error detection has already happened.

So now we can describe the backpressure process as the sequence:

❶ i(ap,e,c,p₁) = (e',c',r)
❷ i(ap,e',c',p̃₁(p₁)) = (e',c'',r')
❸ i(ap,e',c'',p₂(r')) = (e'',c''',r'')
❹ i(ap,e'',c''',p̃₂(p₂)) = (e'',c'ᵛ,r''')
❺ i(ap,e'',c'ᵛ,p₃(r''')) = (e'',cᵛ,r'ᵛ)

Moment ❶ represents the initial implementation of task T given the instructions I. Immediately after, in ❷ we apply a verification prompt that yields a set of “errors” (negative hallucinations) in r’.

This information is returned as feedback to the system (backpressure) in ❸ through p₂(r'), which is a prompt formulated to correct r’ — but we already know this interaction is liable to be incomplete or wrong, and that is why it is necessary to iterate.

In ❹ we once again apply a verification prompt p̃₂(p₂) to determine how far we are from a state where we have eliminated the negative hallucinations, and in ❺ we apply correctives again.

As in any iterative process, there has to be a stopping condition, and the reality is that after a few iterations the value of continuing the process should be marginal, or even generate noise if the prompting does not provide an “escape hatch” that allows reporting that everything is “fine”.

In this example we have been operating on a mutated context; this is not a recommendation, and often an iterative process like the one we described will be much more efficient applying the family of prompts on a clean context.

And to wrap up, let’s think about all the mechanisms we named at the beginning, which can and should be integrated into this process because they contribute a great deal of information for generating the corrective steering after detection.


  1. Hallucination is the Fundamental Feature of LLMs
  2. The Mathematics of AI-Assisted Code
  3. A Systems View of AI-Assisted Programming
  4. Steering - Favoring Positive Hallucinations in Programming Assistants