diff --git a/src/ipa/rpi/controller/agc_status.h b/src/ipa/rpi/controller/agc_status.h
index 597eddd7..e5c4ee22 100644
--- a/src/ipa/rpi/controller/agc_status.h
+++ b/src/ipa/rpi/controller/agc_status.h
@@ -36,6 +36,7 @@ struct AgcStatus {
 	int floatingRegionEnable;
 	libcamera::utils::Duration fixedShutter;
 	double fixedAnalogueGain;
+	unsigned int channel;
 };
 
 struct AgcPrepareStatus {
diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
index 598fc890..58ba8839 100644
--- a/src/ipa/rpi/controller/rpi/agc.cpp
+++ b/src/ipa/rpi/controller/rpi/agc.cpp
@@ -22,7 +22,8 @@ LOG_DEFINE_CATEGORY(RPiAgc)
 
 Agc::Agc(Controller *controller)
 	: AgcAlgorithm(controller),
-	  activeChannels_({ 0 })
+	  activeChannels_({ 0 }),
+	  index_(0)
 {
 }
 
@@ -205,20 +206,113 @@ void Agc::setActiveChannels(const std::vector<unsigned int> &activeChannels)
 void Agc::switchMode(CameraMode const &cameraMode,
 		     Metadata *metadata)
 {
-	LOG(RPiAgc, Debug) << "switchMode for channel 0";
-	channelData_[0].channel.switchMode(cameraMode, metadata);
+	/*
+	 * We run switchMode on every channel, and then we're going to start over
+	 * with the first active channel again which means that this channel's
+	 * status needs to be the one we leave in the metadata.
+	 */
+	AgcStatus status;
+
+	for (unsigned int channelIndex = 0; channelIndex < channelData_.size(); channelIndex++) {
+		LOG(RPiAgc, Debug) << "switchMode for channel " << channelIndex;
+		channelData_[channelIndex].channel.switchMode(cameraMode, metadata);
+		if (channelIndex == activeChannels_[0])
+			metadata->get("agc.status", status);
+	}
+
+	status.channel = activeChannels_[0];
+	metadata->set("agc.status", status);
+	index_ = 0;
+}
+
+static void getChannelIndex(Metadata *metadata, const char *message, unsigned int &channelIndex)
+{
+	std::unique_lock<RPiController::Metadata> lock(*metadata);
+	AgcStatus *status = metadata->getLocked<AgcStatus>("agc.delayed_status");
+	if (status)
+		channelIndex = status->channel;
+	else
+		/* This does happen at startup, otherwise it would be a Warning or Error. */
+		LOG(RPiAgc, Debug) << message;
+}
+
+static void setChannelIndex(Metadata *metadata, const char *message, unsigned int channelIndex)
+{
+	std::unique_lock<RPiController::Metadata> lock(*metadata);
+	AgcStatus *status = metadata->getLocked<AgcStatus>("agc.status");
+	if (status)
+		status->channel = channelIndex;
+	else
+		/* This does happen at startup, otherwise it would be a Warning or Error. */
+		LOG(RPiAgc, Debug) << message;
 }
 
 void Agc::prepare(Metadata *imageMetadata)
 {
-	LOG(RPiAgc, Debug) << "prepare for channel 0";
-	channelData_[0].channel.prepare(imageMetadata);
+	/*
+	 * The DeviceStatus in the metadata should be correct for the image we
+	 * are processing. THe delayed status should tell us what channel this frame
+	 * was from, so we will use that channel's prepare method.
+	 *
+	 * \todo To be honest, there's not much that's stateful in the prepare methods
+	 * so we should perhaps re-evaluate whether prepare even needs to be done
+	 * "per channel".
+	 */
+	unsigned int channelIndex = activeChannels_[0];
+	getChannelIndex(imageMetadata, "prepare: no delayed status", channelIndex);
+
+	LOG(RPiAgc, Debug) << "prepare for channel " << channelIndex;
+	channelData_[channelIndex].channel.prepare(imageMetadata);
 }
 
 void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
 {
-	LOG(RPiAgc, Debug) << "process for channel 0";
-	channelData_[0].channel.process(stats, imageMetadata);
+	/*
+	 * We want to generate values for the next channel in round robin fashion
+	 * (i.e. the channel at location index_ in the activeChannel list), even though
+	 * the statistics we have will be for a different channel (which we find
+	 * again from the delayed status).
+	 */
+
+	/* Generate updated AGC values for this channel: */
+	unsigned int channelIndex = activeChannels_[index_];
+	AgcChannelData &channelData = channelData_[channelIndex];
+	/* Stats are from this channel: */
+	unsigned int statsIndex = 0;
+	getChannelIndex(imageMetadata, "process: no delayed status for stats", statsIndex);
+	LOG(RPiAgc, Debug) << "process for channel " << channelIndex;
+
+	/*
+	 * We keep a cache of the most recent DeviceStatus and stats for each channel,
+	 * so that we can invoke the next channel's process method with the most up to date
+	 * values.
+	 */
+	LOG(RPiAgc, Debug) << "Save DeviceStatus and stats for channel " << statsIndex;
+	DeviceStatus deviceStatus;
+	if (imageMetadata->get<DeviceStatus>("device.status", deviceStatus) == 0)
+		channelData_[statsIndex].deviceStatus = deviceStatus;
+	else
+		/* Every frame should have a DeviceStatus. */
+		LOG(RPiAgc, Error) << "process: no device status found";
+	channelData_[statsIndex].statistics = stats;
+
+	/*
+	 * Finally fetch the most recent DeviceStatus and stats for this channel, if both
+	 * exist, and call process(). We must make the agc.status metadata record correctly
+	 * which channel this is.
+	 */
+	if (channelData.statistics && channelData.deviceStatus) {
+		deviceStatus = *channelData.deviceStatus;
+		stats = channelData.statistics;
+	} else
+		/* Can also happen when new channels start. */
+		LOG(RPiAgc, Debug) << "process: channel " << channelIndex << " not seen yet";
+
+	channelData.channel.process(stats, &deviceStatus, imageMetadata);
+	setChannelIndex(imageMetadata, "process: no AGC status found", channelIndex);
+
+	/* And onto the next channel for the next call. */
+	index_ = (index_ + 1) % activeChannels_.size();
 }
 
 /* Register algorithm with the system. */
diff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h
index 24f0a271..ee85c693 100644
--- a/src/ipa/rpi/controller/rpi/agc.h
+++ b/src/ipa/rpi/controller/rpi/agc.h
@@ -18,6 +18,8 @@ namespace RPiController {
 
 struct AgcChannelData {
 	AgcChannel channel;
+	std::optional<DeviceStatus> deviceStatus;
+	StatisticsPtr statistics;
 };
 
 class Agc : public AgcAlgorithm
@@ -52,6 +54,7 @@ private:
 	int checkChannel(unsigned int channel) const;
 	std::vector<AgcChannelData> channelData_;
 	std::vector<unsigned int> activeChannels_;
+	unsigned int index_; /* index into the activeChannels_ */
 };
 
 } /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
index 7c1aba81..f1f19598 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
+++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
@@ -444,7 +444,7 @@ void AgcChannel::prepare(Metadata *imageMetadata)
 	}
 }
 
-void AgcChannel::process(StatisticsPtr &stats, Metadata *imageMetadata)
+void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata)
 {
 	frameCount_++;
 	/*
@@ -455,7 +455,7 @@ void AgcChannel::process(StatisticsPtr &stats, Metadata *imageMetadata)
 	/* Fetch the AWB status immediately, so that we can assume it's there. */
 	fetchAwbStatus(imageMetadata);
 	/* Get the current exposure values for the frame that's just arrived. */
-	fetchCurrentExposure(imageMetadata);
+	fetchCurrentExposure(deviceStatus);
 	/* Compute the total gain we require relative to the current exposure. */
 	double gain, targetY;
 	computeGain(stats, imageMetadata, gain, targetY);
@@ -567,18 +567,13 @@ void AgcChannel::housekeepConfig()
 			   << meteringModeName_;
 }
 
-void AgcChannel::fetchCurrentExposure(Metadata *imageMetadata)
+void AgcChannel::fetchCurrentExposure(const DeviceStatus *deviceStatus)
 {
-	std::unique_lock<Metadata> lock(*imageMetadata);
-	DeviceStatus *deviceStatus =
-		imageMetadata->getLocked<DeviceStatus>("device.status");
 	if (!deviceStatus)
 		LOG(RPiAgc, Fatal) << "No device metadata";
 	current_.shutter = deviceStatus->shutterSpeed;
 	current_.analogueGain = deviceStatus->analogueGain;
-	AgcStatus *agcStatus =
-		imageMetadata->getLocked<AgcStatus>("agc.status");
-	current_.totalExposure = agcStatus ? agcStatus->totalExposureValue : 0s;
+	current_.totalExposure = 0s; /* this value is unused */
 	current_.totalExposureNoDG = current_.shutter * current_.analogueGain;
 }
 
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h
index d5a5cf3a..24ee3491 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.h
+++ b/src/ipa/rpi/controller/rpi/agc_channel.h
@@ -85,13 +85,13 @@ public:
 	void disableAuto();
 	void switchMode(CameraMode const &cameraMode, Metadata *metadata);
 	void prepare(Metadata *imageMetadata);
-	void process(StatisticsPtr &stats, Metadata *imageMetadata);
+	void process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata);
 
 private:
 	bool updateLockStatus(DeviceStatus const &deviceStatus);
 	AgcConfig config_;
 	void housekeepConfig();
-	void fetchCurrentExposure(Metadata *imageMetadata);
+	void fetchCurrentExposure(const DeviceStatus *deviceStatus);
 	void fetchAwbStatus(Metadata *imageMetadata);
 	void computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,
 			 double &gain, double &targetY);
