How Do Agents Push Data to an Enterprise Controller?

Claire Rousseau7 min read
Application NoteHMI / SCADAOther Manufacturer
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

The Agent records local activity in custom tables, but the Controller cannot build a current cross-Agent summary without repeatedly polling every Agent. Place the callable service on the Controller and have each Agent invoke it only after publishing new data. This reverses the communication direction while keeping aggregation and Controller database writes centralized.

Data ownership and push architecture

Before anything else, confirm that every Agent writes its local activity successfully and that the Controller owns the consolidated summary. The custom tables may share the database selected by the Audit Data source, but they remain application data with their own synchronization rules.

Pattern Trigger Consequence Use here
Controller polling A Controller schedule contacts every Agent Calls continue when no new records exist No
Agent notification only An Agent announces that data exists The Controller still needs a second request to retrieve it Only if payload transfer is impractical
Agent push An Agent calls a Controller service when data becomes available Traffic follows actual data creation Preferred
  1. Retain local capture and storage on each Agent.
  2. Create the receiving service in the Controller module.
  3. Call that service from the Agent after local data becomes eligible for publication.
  4. Aggregate accepted Agent data and write the result to the Controller database.

Services can reside on either side of this architecture and can be called by the other side. The service location does not have to match the side that initiates communication. Do not move on until a test Agent can reach a no-op Controller service without any Controller polling task.

Controller-side service contract

Define the receiving contract before changing the Agent. The Controller must be able to identify the source Agent, distinguish one delivery from another, validate the payload, and decide whether the request is new or repeated. Use stable delivery identity rather than arrival time; retries can arrive later or out of order.

  1. Specify which Agent identity accompanies each delivery and validate that identity through the module platform's authentication and authorization facilities.
  2. Choose whether the payload contains raw activity rows or an Agent-level partial summary. Raw rows preserve auditability but transfer more data. Partial summaries reduce traffic but must carry enough grouping context for correct consolidation.
  3. Include a stable source range or delivery identity in the logical contract. The specific field names belong to the module implementation; do not substitute database row counts or timestamps unless they uniquely identify committed source data.
  4. Validate required values before modifying Controller tables. Reject malformed or unauthorized requests without acknowledging them as accepted.
  5. Make repeated delivery safe. A retry of an already accepted delivery must return success without applying its contribution a second time.

The acknowledgment boundary matters: an Agent may mark data as delivered only after the Controller has durably accepted it. A network response sent before the database commit can lose data if the commit then fails. Do not move on until the service accepts one valid test payload, rejects an invalid payload, and leaves the database unchanged when the valid payload is repeated.

Agent-side publication trigger

Connect publication to the local capture path rather than a Controller schedule. The trigger must run after the Agent's local database transaction commits; otherwise, the Agent could send data that is later rolled back locally.

  1. Commit the captured local activity to the Agent database.
  2. Record that committed data as pending delivery using a durable state associated with the source data.
  3. Invoke the Controller service with the pending data or partial summary.
  4. After a positive Controller acknowledgment, mark the corresponding source range as delivered.
  5. If the call fails or no definitive acknowledgment arrives, retain the pending state for retry.

A durable pending state separates capture from transport. Local activity remains recorded when the Controller is unavailable, the network path is interrupted, or the Agent restarts. Avoid a memory-only flag because restart would erase knowledge of unsent records. Also avoid clearing the pending state merely because the request left the Agent; transmission is not proof of acceptance.

Do not move on until creating one local activity record produces one pending delivery only after the local commit, followed by a delivered state only after the Controller acknowledges acceptance.

Delivery, acknowledgment, and retry

Agent-driven delivery removes idle polling but does not remove failure handling. Treat delivery as at least once: the same data may reach the Controller more than once when an acknowledgment is lost. Controller-side idempotency converts those repeated attempts into one logical update.

Observed condition Agent action Controller protection
Controller accepts and acknowledges Mark the source range delivered Commit delivery identity with its data
Connection fails before acceptance Keep pending and retry later No database change
Commit succeeds but acknowledgment is lost Retry the same delivery Recognize the duplicate and do not recount it
Payload fails validation Retain it for correction or explicit handling Reject the complete delivery

Use bounded batches if activity can accumulate while the Controller is offline. A batch should be small enough for the service and database transaction to complete reliably, while preserving a deterministic boundary between delivered and pending data. Read platform diagnostics and database limits to select the batch size; no fixed value applies to every installation.

Do not acknowledge a partially stored batch as fully accepted. Either commit the defined delivery atomically or return a failure that causes the Agent to resend the same delivery identity. Do not move on until a deliberately interrupted call can be retried without losing or duplicating its contribution.

Controller summary transaction

Keep cross-Agent aggregation on the Controller because that database owns the final summary. The receiving operation must separate delivery acceptance from summary calculation clearly enough to recover after interruption.

  1. Start the Controller database transaction.
  2. Check whether the delivery identity from that Agent has already been accepted.
  3. If it is new, store the incoming data or accepted contribution.
  4. Update or rebuild the affected summary using all required Agent dimensions.
  5. Record the delivery as accepted in the same transaction.
  6. Commit, then return the positive acknowledgment.

Recording acceptance and changing the summary in separate uncoordinated commits creates two failure windows: a delivery can be marked accepted without affecting the summary, or it can affect the summary without being marked accepted. Where the database supports transactions across the relevant tables, commit both results together. If the summary is rebuilt asynchronously, first commit the incoming contribution and delivery identity together, then track summary processing independently.

Agent identity must be part of the aggregation key wherever two Agents can produce overlapping local identifiers. Otherwise, records from different gateways can collide or one Agent's delivery can be mistaken for another's retry. Do not move on until two Agent identities can submit overlapping local data without overwriting or double-counting each other.

End-to-end commissioning

  1. Start with no pending Agent data and record the Controller summary baseline.
  2. Create known local activity on one Agent and confirm that its custom table receives the record.
  3. Confirm that the Agent creates a pending delivery and calls the Controller service without a Controller polling cycle.
  4. Confirm that the Controller accepts the delivery, commits the summary change, and returns an acknowledgment.
  5. Confirm that the Agent changes the same source range from pending to delivered.
  6. Resend the identical delivery and verify that the summary does not change again.
  7. Repeat with the Controller temporarily unavailable. Confirm that local capture continues, pending data remains durable, and delivery completes after connectivity returns.
  8. Publish data from multiple Agents and reconcile the Controller summary against the known contributions from each Agent.

Do not declare commissioning complete until database records, module diagnostics, and the calculated summary all show one accepted contribution per logical delivery, with no scheduled Controller polling required.

FAQ

Can I put the service on the Controller instead of every Agent?

Yes. Create the receiving service on the Controller and let each Agent call it when locally committed data becomes available.

Does Agent push eliminate all scheduled work?

It eliminates periodic Controller polling for new Agent data. The Agent still needs a retry mechanism for pending deliveries after network or Controller failures.

Can I send only a notification that new Agent data exists?

Yes, but notification alone requires a second retrieval operation. Sending the data or an Agent-level partial summary in the service call avoids that extra round trip.

Does retrying a failed call duplicate the Controller summary?

It must not. Resend the same stable delivery identity, then verify that the Controller acknowledges the duplicate without applying its contribution again.

Back to blog