diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp
index fdeec09d..4784af00 100644
--- a/src/ipa/ipu3/algorithms/agc.cpp
+++ b/src/ipa/ipu3/algorithms/agc.cpp
@@ -187,7 +187,7 @@ void Agc::computeExposure(IPAContext &context, double yGain,
 			  double iqMeanGain)
 {
 	const IPASessionConfiguration &configuration = context.configuration;
-	IPAFrameContext &frameContext = context.frameContext;
+	IPAFrameContext &frameContext = context.frameContextQueue.front();
 	/* Get the effective exposure and gain applied on the sensor. */
 	uint32_t exposure = frameContext.sensor.exposure;
 	double analogueGain = frameContext.sensor.gain;
diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp
index 06eb2776..fb48bc9b 100644
--- a/src/ipa/ipu3/ipa_context.cpp
+++ b/src/ipa/ipu3/ipa_context.cpp
@@ -58,8 +58,8 @@ namespace libcamera::ipa::ipu3 {
  * \var IPAContext::configuration
  * \brief The IPA session configuration, immutable during the session
  *
- * \var IPAContext::frameContext
- * \brief The frame context for the frame being processed
+ * \var IPAContext::frameContextQueue
+ * \brief FIFO container of IPAFrameContext for all the frames being queued
  *
  * \var IPAContext::activeState
  * \brief The current state of IPA algorithms
@@ -182,6 +182,15 @@ namespace libcamera::ipa::ipu3 {
  * <linux/intel-ipu3.h> struct ipu3_uapi_gamma_corr_lut for further details.
  */
 
+/**
+ * \brief Construct a IPAFrameContext instance
+ */
+IPAFrameContext::IPAFrameContext(uint32_t frame)
+	: frame(frame)
+{
+	sensor = {};
+}
+
 /**
  * \var IPAFrameContext::sensor
  * \brief Effective sensor values that were applied for the frame
@@ -191,6 +200,9 @@ namespace libcamera::ipa::ipu3 {
  *
  * \var IPAFrameContext::sensor.gain
  * \brief Analogue gain multiplier
+ *
+ * \var IPAFrameContext::frame
+ * \brief The frame number associated with this IPAFrameContext
  */
 
 } /* namespace libcamera::ipa::ipu3 */
diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h
index 8d681131..20cccc97 100644
--- a/src/ipa/ipu3/ipa_context.h
+++ b/src/ipa/ipu3/ipa_context.h
@@ -8,6 +8,8 @@
 
 #pragma once
 
+#include <queue>
+
 #include <linux/intel-ipu3.h>
 
 #include <libcamera/base/utils.h>
@@ -71,17 +73,21 @@ struct IPAActiveState {
 };
 
 struct IPAFrameContext {
+	IPAFrameContext(uint32_t frame);
+
 	struct {
 		uint32_t exposure;
 		double gain;
 	} sensor;
+
+	uint32_t frame;
 };
 
 struct IPAContext {
 	IPASessionConfiguration configuration;
 	IPAActiveState activeState;
 
-	IPAFrameContext frameContext;
+	std::queue<IPAFrameContext> frameContextQueue;
 };
 
 } /* namespace ipa::ipu3 */
diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
index 268c8f61..1b566f14 100644
--- a/src/ipa/ipu3/ipu3.cpp
+++ b/src/ipa/ipu3/ipu3.cpp
@@ -350,6 +350,8 @@ int IPAIPU3::start()
  */
 void IPAIPU3::stop()
 {
+	while (!context_.frameContextQueue.empty())
+		context_.frameContextQueue.pop();
 }
 
 /**
@@ -458,7 +460,7 @@ int IPAIPU3::configure(const IPAConfigInfo &configInfo,
 
 	/* Clean IPAActiveState at each reconfiguration. */
 	context_.activeState = {};
-	context_.frameContext = {};
+	context_.frameContextQueue = {};
 
 	if (!validateSensorControls()) {
 		LOG(IPAIPU3, Error) << "Sensor control validation failed.";
@@ -510,6 +512,13 @@ void IPAIPU3::unmapBuffers(const std::vector<unsigned int> &ids)
 
 void IPAIPU3::frameCompleted([[maybe_unused]] const uint32_t frame)
 {
+	while (!context_.frameContextQueue.empty()) {
+		auto &fc = context_.frameContextQueue.front();
+		if (fc.frame <= frame)
+			context_.frameContextQueue.pop();
+		else
+			break;
+	}
 }
 
 /**
@@ -574,8 +583,18 @@ void IPAIPU3::processStatsBuffer(const uint32_t frame,
 	const ipu3_uapi_stats_3a *stats =
 		reinterpret_cast<ipu3_uapi_stats_3a *>(mem.data());
 
-	context_.frameContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
-	context_.frameContext.sensor.gain = camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());
+	auto &frameContext = context_.frameContextQueue.front();
+
+	/*
+	 * An assert might be too harsh here. We want to know the cases
+	 * where the front of the queue (implies the current frame in processing,
+	 * diverges from the frame parameter of this function
+	 * \todo Identify those cases - e.g. frame drop?
+	 */
+	ASSERT(context_.frameContextQueue.front().frame == frame);
+
+	frameContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
+	frameContext.sensor.gain = camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());
 
 	double lineDuration = context_.configuration.sensor.lineDuration.get<std::micro>();
 	int32_t vBlank = context_.configuration.sensor.defVBlank;
@@ -590,11 +609,11 @@ void IPAIPU3::processStatsBuffer(const uint32_t frame,
 	int64_t frameDuration = (vBlank + sensorInfo_.outputSize.height) * lineDuration;
 	ctrls.set(controls::FrameDuration, frameDuration);
 
-	ctrls.set(controls::AnalogueGain, context_.frameContext.sensor.gain);
+	ctrls.set(controls::AnalogueGain, frameContext.sensor.gain);
 
 	ctrls.set(controls::ColourTemperature, context_.activeState.awb.temperatureK);
 
-	ctrls.set(controls::ExposureTime, context_.frameContext.sensor.exposure * lineDuration);
+	ctrls.set(controls::ExposureTime, frameContext.sensor.exposure * lineDuration);
 
 	/*
 	 * \todo The Metadata provides a path to getting extended data
@@ -621,6 +640,8 @@ void IPAIPU3::queueRequest([[maybe_unused]] const uint32_t frame,
 			   [[maybe_unused]] const ControlList &controls)
 {
 	/* \todo Start processing for 'frame' based on 'controls'. */
+
+	context_.frameContextQueue.emplace(frame);
 }
 
 /**
