## On this page

Each feature that the Agent supports, such as the `Agent/$push` operation, `Agent/$status`, `Agent/$reload-config`, etc. all require minimum versions of both Medplum Server and Medplum Agent in order to work. The matrix of feature, Medplum Server version, and Medplum Agent version looks like this:

| Feature | Description | Medplum Server | Medplum Agent |
| --- | --- | --- | --- |
| `Prefer: respond-async` | Allows asynchronous calls to `Agent/$push` via the `Prefer` header. The header tells the server to return a `202 Accepted` HTTP response and allows the client to poll for the completion of the `Agent/$push` operation asynchronously without keeping the client waiting for an HTTP response. Useful when a response from a target device can take several seconds or even minutes to come back. | "> 3.1.5" | "> 3.1.5" |
| `Agent/$reload-config` | Allows for dynamically reloading the config of the `Agent` from Medplum Server via the `Agent/$reload-config` FHIR operation. Useful for pushing changes to an Agent via the `Agent` resource without restarting the Agent service. | "> 3.1.6" | "> 3.1.6" |
| `Agent.status` | Allows using `Agent.status` and `Agent.channel.endpoint.status` to disable an Agent or a particular channel via the `Agent` resource. Useful for stopping traffic during Agent maintenance or for debugging of particular channels. | "> 3.1.6" | "> 3.1.6" |
| Agent `keepAlive` setting | Allows you to tell the Agent to keep TCP connections alive for both outgoing traffic (via `Agent/$push`) and incoming traffic (via configured Agent channels) by setting the `keepAlive` setting to `true` on the `Agent` resource. Useful when you want to reduce number of connections to the Agent or if a particular device configuration expects the connection not to close. | "> 3.1.9" | "> 3.1.10" |
| `Agent/$upgrade` | Allows for remotely upgrading the Agent version via the `Agent/$upgrade` FHIR operation. | "> 3.1.9" | "> 3.1.10" |
| Channel `enhanced` mode | Allows agent to acknowledge receipt of a message by enabling **enhanced mode**. In enhanced mode, the Agent will send a [Commit Accept `ACK`](https://hl7-definition.caristix.com/v2/HL7v2.3/Tables/0008) response _before_ the `Bot` has completed processing. Useful when receiving high volumes of messages. <br>Set by adding the `enhanced=true` query parameter in the channel endpoint url (e.g. `mllp://0.0.0.0:2500?enhanced=true`) | "> 3.1.9" | "> 4.1.8" |
| Statistics logging | Allows for enabling periodic stats logging on the agent, which includes stats such as: internal message queue depth, number of active HL7 connections, etc.<br>Set by adding the `logStatsFreqSecs` setting to the `Agent.setting` array, with a valid `valueInteger` value, representing the number of seconds between each statistics log line. | "> 3.1.9" | "> 4.3.4" |
| Channel `messagesPerMin` | Adds a minimum time between processing messages so that at maximum `messagesPerMin` messages can be processed in a minute. Useful when receiving high volumes of messages, especially when using `enhanced` mode.<br>Set by adding the `messagesPerMin=<integer>` query parameter in the channel endpoint url (e.g. `mllp://0.0.0.0:2500?enhanced=true&messagesPerMin=60`).<br>Note: Setting `messagesPerMin` tries to smooth out load, and is not just a hard cap on messages per minute. For example, `messagesPerMin=60` will make it so that time between processing messages is at least 1 second. | "> 3.1.9" | "> 4.3.10" |
| `Agent/$fetch-logs` | Allows for remotely fetching the Agent logs via the [`Agent/$fetch-logs` FHIR operation](/content/docs/agent/fetch-logs/index.html). | "> 4.3.14" | "> 4.3.14" |
| Channel `appLevelAck` | Allows configuring application-level acknowledgment behavior for HL7 messages. Controls when application-level ACKs are sent to the remote system based on the ACK code and enhanced mode status.<br>Valid values: `AL` (Always - default), `ER` (Error/Reject only), `NE` (Never), `SU` (Success only).<br>Set by adding the `appLevelAck=<value>` query parameter in the channel endpoint url (e.g. `mllp://0.0.0.0:2500?enhanced=true&appLevelAck=ER`). | "> 3.1.9" | "> 5.0.4" |
| Channel `assignSeqNo` | Allows the Agent to automatically assign sequence numbers to incoming HL7 messages in the MSH-13 field. Useful when the sending system does not provide sequence numbers or when you need consistent sequence numbering.<br>Set by adding the `assignSeqNo=true` query parameter in the channel endpoint url (e.g. `mllp://0.0.0.0:2500?assignSeqNo=true`). | "> 3.1.9" | "> 5.0.4" |
| Agent `maxClientsPerRemote` | Allows configuring the maximum number of concurrent HL7 client connections per remote host. Controls connection pooling behavior when sending messages via `Agent/$push`.<br>Default is 5, or 1 when `keepAlive` is enabled (unless explicitly set).<br>Set by adding the `maxClientsPerRemote` setting to the `Agent.setting` array, with a valid `valueInteger` value. | "> 3.1.9" | "> 5.0.4" |
| AA-only enhanced ACK mode (`enhanced=aa`) | A variant of enhanced mode that sends an [Application Accept (`AA`)](https://hl7-definition.caristix.com/v2/HL7v2.3/Tables/0008) immediately instead of Commit Accept (`CA`). Provides enhanced throughput without requiring the remote system to support or be configured for the two-step enhanced acknowledgement handshake.<br>**Trade-off:** Application-level ACKs (AA/AE/AR) are not forwarded after processing, so the remote system cannot receive asynchronous feedback about processing success or failure.<br>Set by adding the `enhanced=aa` query parameter in the channel endpoint url (e.g. `mllp://0.0.0.0:2500?enhanced=aa`). See [AA Mode documentation](/content/docs/agent/acknowledgement-modes#aa-mode-simplified-enhanced-mode/index.html) for details. | "> 3.1.9" | "> 5.0.11" |
| `returnAck` parameter | Allows configuring which ACK message the Agent returns when sending HL7 messages via `Agent/$push`. Valid values:<br>- `first` (default): Returns the first ACK message received (e.g., a Commit ACK `CA` when the remote uses enhanced mode)<br>- `application`: Waits for and returns the application-level ACK (`AA`, `AE`, or `AR`), skipping any commit-level ACKs<br>Can be configured at two levels:<br>- **Per-device default**: Set via the `defaultReturnAck` query parameter on the `Device.url` (e.g., `mllp://192.168.1.100:2575?defaultReturnAck=application`)<br>- **Per-message override**: Set via the `returnAck` parameter on `Agent/$push` operation or `pushToAgent()` method<br>Priority order: per-message `returnAck` > device `defaultReturnAck` > `first` (global default) | "> 3.1.9" | "> 5.0.13" |
| `Agent/$stats` operation | Allows for remotely fetching runtime statistics from the Agent via the [`Agent/$stats` FHIR operation](/content/docs/agent/stats/index.html). Includes connection counts, queue depths, RTT metrics, and overall agent health. See also `Statistics logging` for similar functionality without the need for the operation. | "> 5.1.9" | "> 5.1.9" |
| Byte stream channel `autoRespond` | Allows a byte stream channel to answer a link-level handshake itself, writing a fixed byte sequence back to the socket as soon as a configured sequence is seen, without waiting on framing or the `Bot`. Useful for devices that expect an immediate `ACK` (`0x06`) after an `ENQ` (`0x05`) before they will send a message.<br>Set by adding an `autoRespond=<pattern>:<response>` query parameter in the channel endpoint url (e.g. `tcp://0.0.0.0:9001?startChar=%02&endChar=%03&autoRespond=%05:%06`). Repeat the parameter or comma-separate for more rules; patterns may span several bytes and match across TCP chunk boundaries, per connection.<br>Byte sequences use the same `%XX` form as `startChar`/`endChar`; since percent-decoding is UTF-8, a byte >= `0x80` needs its UTF-8 form (`%C3%A9` for `0xE9`).<br>For the rules an ASTM analyzer expects, see [ASTM Channels](/content/docs/agent/astm-channels#link-level-acknowledgement/index.html). | Any | "> 5.1.28" |
| Byte stream channel body filtering | Allows a byte stream channel to clean up a message body before it is sent to the `Bot`.<br>- `stripSequence=<sequence>` removes every whole occurrence of a byte sequence; repeat the parameter or comma-separate for more<br>- `stripControlChars=true` removes any remaining C0 control byte (`0x00`-`0x1F`), including the `startChar`/`endChar` framing<br>- `keepControlChars=<bytes>` exempts bytes from that sweep. Record-oriented protocols need their terminators to survive it — an ASTM body stripped of `CR` is one run-on line the receiver can no longer split into records, so such a channel wants `keepControlChars=%0D%0A`.<br>- `bodyEncoding=hex` (default) or `utf-8` controls how the body is encoded on the wire to and from the server<br>Set via query parameters in the channel endpoint url (e.g. `tcp://0.0.0.0:9004?startChar=%05&endChar=%04&stripControlChars=true&keepControlChars=%0D%0A&bodyEncoding=utf-8`). All are off by default, so existing channels keep sending hex-encoded bodies with their framing intact.<br>Filtering works on bytes, not on frames, so anything printable in a device's framing still reaches the `Bot`. An ASTM body arrives carrying each frame's leading sequence number and trailing checksum, which the `Bot` has to strip itself — see [ASTM Channels](/content/docs/agent/astm-channels#what-the-bot-receives/index.html). | Any | "> 5.1.28" |
| Byte stream channel `ignoreResponse` | Makes a byte stream channel one-way: the `Bot`'s response is discarded instead of being written back to the device socket. Messages still reach the `Bot` as usual, and `autoRespond` still answers link-level handshakes — only the response leg is dropped.<br>Use it for devices that never read a reply, such as an analyzer that considers a session complete once its own link-level exchange ends. Without it every message draws a write back to the device, and a `Bot` that returns nothing draws the server's fallback body — the `Bot`'s own execution logs — which arrives at the device as unframed junk.<br>Set by adding `ignoreResponse=true` in the channel endpoint url (e.g. `tcp://0.0.0.0:9004?startChar=%05&endChar=%04&ignoreResponse=true`). Off by default. | Any | "> 5.1.28" |
| DICOM channel `storage=dicomweb` | Sends each instance a DICOM channel receives via C-STORE to the server's DICOMweb STOW-RS endpoint (`POST /dicomweb/studies`), which files it into `DicomStudy`, `DicomSeries`, and `DicomInstance` resources, instead of uploading it as a FHIR `Binary`.<br>Set by adding the `storage=dicomweb` query parameter in the channel endpoint url (e.g. `dicom://0.0.0.0:8104?storage=dicomweb`). Defaults to `storage=binary`, which uploads a `Binary` and includes a reference to it in the `Bot` payload, so existing DICOM channels are unaffected. In `dicomweb` mode the payload has no `binary` field — the study is addressed through the DICOMweb resources the server created instead.<br>Requires a server with DICOMweb support; against an older server the STOW-RS request 404s and the C-STORE fails with a processing failure status. | "> 5.1.27" | "> 5.1.28" |
| Agent `durableQueue` | Routes inbound HL7 messages through an on-disk SQLite queue on the Agent before they reach the `Bot`. The commit `ACK` (`CA`, or `AA` in AA mode) is sent only once the message is committed to disk, so an ACKed message survives an Agent crash, restart, or upgrade; interrupted rows are recovered on startup.<br>Set by adding the `durableQueue` setting to the `Agent.setting` array with `valueBoolean: true`. Off by default. Related settings: `queueDbPath`, `queueRetentionDays`, `queueRetentionMaxMb`, `queueErroredRetentionDays`, `queueSweepIntervalSecs`.<br>Required for auto-retry and for logical channels. Also enables the `duplicateBehavior` channel parameter (`idempotent` / `reject`) for repeated `MSH-10` values. See the [High Throughput HL7 guide](/content/docs/agent/high-throughput-hl7#solution-3-the-durable-queue--makes-the-fast-ack-honest/index.html). | Any | "> 5.1.22" |
| Channel auto-retry | Retries queue-to-`Bot` delivery with exponential backoff and jitter when a message fails. Modes: `guaranteed` (default — retry indefinitely until upstream answers, accepting possible duplicate delivery), `normal` (transient failures only, capped at 10 attempts), and `none`.<br>Set agent-wide via the `channelRetryMode`, `channelAutoRetryBaseDelayMs`, `channelAutoRetryMaxDelayMs`, `channelAutoRetryMaxAttempts`, and `channelAutoRetryBackoffMultiplier` settings, or per channel via the `retryMode`, `autoRetryBaseDelayMs`, `autoRetryMaxDelayMs`, `autoRetryMaxAttempts`, and `autoRetryBackoffMultiplier` endpoint url parameters, which override the agent-wide settings field by field.<br>Requires `durableQueue`; without it the retry settings have no effect and the Agent warns. | Any | "> 5.1.25" |
| Channel logical channels | Partitions a single HL7 channel's queue into independent FIFO streams keyed on message content, so unrelated messages process concurrently instead of queueing behind each other. Order is strict within a partition and unconstrained across partitions — a feed keyed on patient ID keeps each patient's messages ordered while draining many patients in parallel.<br>Set the key via the `channelLogicalChannelKey` agent setting or the `logicalChannelKey` endpoint url parameter, as comma-separated HL7 field paths in `SEGMENT-field[.component[.subcomponent]]` notation (e.g. `PID-3.1` or `MSH-4,MSH-9.2`). Set the concurrency via the `channelMaxWorkers` setting or the `maxWorkers` parameter (default 1, max 500), e.g. `mllp://0.0.0.0:9001?enhanced=true&logicalChannelKey=PID-3.1&maxWorkers=64`.<br>Requires `durableQueue`; without it both have no effect and the Agent warns. See the [High Throughput HL7 guide](/content/docs/agent/high-throughput-hl7#solution-4-logical-channels--the-actual-throughput-fix/index.html). | Any | "> 5.1.29" |

## Major Bug Fixes

| Server Version | Agent Version | Description | Related PR |
| --- | --- | --- | --- |
| 3.2.10 | N/A | Fixes HL7 ACK messages from bots not making it back to the sending device. | [5212](https://github.com/medplum/medplum/pull/5212) |
| < 3.2.10 | 4.1.9 | Reverted backwards-incompatible change requiring `callback` field for `agent:transmit:response` messages. | [6793](https://github.com/medplum/medplum/pull/6793) |
| N/A | 5.0.13 | Reverted default `returnAck` behavior back to `first` for backwards compatibility. Previously changed to `application`, which broke workflows expecting the first ACK (e.g., Commit ACK `CA`) to be returned immediately. | [8279](https://github.com/medplum/medplum/pull/8279) |
| N/A | 5.1.15 | Improves the reliability of the `Agent/$upgrade` process. A behavior change in the underlying `@medplum/hl7` library in v5 introduced a downstream ordering issue during upgrade: the new Agent version would install successfully, but the new service could stall while waiting to acquire its ports because the old service had not yet released them. In practice this required manually stopping the old service to trigger cleanup and complete the upgrade. The shutdown of the old Agent is now signaled before the new Agent waits on its channels, allowing ports to be released in the correct order and making the upgrade process significantly more stable. | [9382](https://github.com/medplum/medplum/pull/9382) |

## Compatibility Between Versions

Aside from the features and bug fixes listed above, the majority of the core functionality of the Medplum Agent is broadly compatible across all versions; notably from `3.1.5` onward. This means the `Agent/$push` FHIR operation functions broadly the same across versions and should be compatible on Medplum Server and Medplum Agent versions `> 3.1.5`.

However, it is not advised to run older versions of the Medplum Agent or Medplum Server against each other if possible, and **we recommend to regularly update both Medplum Server and Medplum Agent in tandem.
