diff --git a/docs/learn/devices/epics-put-completion.md b/docs/learn/devices/epics-put-completion.md
new file mode 100644
index 0000000..5376cec
--- /dev/null
+++ b/docs/learn/devices/epics-put-completion.md
@@ -0,0 +1,80 @@
+---
+related:
+ - title: set() vs put() in ophyd
+ url: learn/devices/set-vs-put.md
+ - title: EPICS Signal Variants
+ url: learn/devices/epics-signals.md
+---
+
+# EPICS put completion
+
+`EpicsSignal` supports EPICS put completion, which means EPICS reports back when the write request itself has completed.
+
+This is an EPICS-specific detail that matters because it changes how `set()` decides that a write is finished.
+
+## Configuring `put_complete`
+
+The relevant option is `put_complete`, which is configured on the signal definition. The default value is `False`.
+
+```python
+from ophyd import Component as Cpt, Device, EpicsSignal
+
+
+class MyDevice(Device):
+ my_signal = Cpt(EpicsSignal, "PV:NAME", put_complete=True)
+```
+
+## How it changes `set()`
+
+For `EpicsSignal.set()`, `put_complete` changes the completion condition:
+
+
+
+
+
+
+
+
+
+ | Configuration |
+ Completion behavior |
+ What the caller gets |
+
+
+
+
+ put_complete=False |
+ set() writes the value, then waits until the readback reaches the target. |
+ A Status that completes when the readback matches. |
+
+
+ put_complete=True |
+ set() calls put(..., use_complete=True) internally and finishes when EPICS reports put completion. |
+ A Status that completes on EPICS put completion. |
+
+
+
+
+!!! info "How `put_complete` changes `set()`"
+ - `put_complete=False`: "wait until the signal value reaches the requested target"
+ - `put_complete=True`: "wait until EPICS says the write request has completed"
+
+This can be useful for EPICS signals where the EPICS put-completion callback is the right completion signal for the write.
+
+## What it does not change
+
+`put_complete` changes how `set()` finishes, but it does not change the role of `put()`.
+
+Even when `put_complete=True` is enabled on the signal:
+
+- `put()` still performs a direct write
+- `put()` still returns immediately
+- `put()` still does not return a `Status`
+
+That is why `set()` remains the normal ophyd API when the caller needs structured completion tracking.
+
+!!! info "What to remember"
+ - `put_complete` is an `EpicsSignal` option that changes how `set()` decides the write is finished.
+ - With `put_complete=False`, `set()` waits for the readback to reach the target.
+ - With `put_complete=True`, `set()` finishes when EPICS reports put completion.
+ - `put_complete` does not turn `put()` into a completion-tracked API: `put()` still writes directly and returns immediately.
diff --git a/docs/learn/devices/set-vs-put.md b/docs/learn/devices/set-vs-put.md
new file mode 100644
index 0000000..f61150a
--- /dev/null
+++ b/docs/learn/devices/set-vs-put.md
@@ -0,0 +1,110 @@
+---
+related:
+ - title: Introduction to ophyd
+ url: learn/devices/introduction-to-ophyd.md
+ - title: Change Config Signals from the BEC IPython Client
+ url: how-to/devices/change-config-signals-from-the-bec-ipython-client.md
+ - title: EPICS put completion
+ url: learn/devices/epics-put-completion.md
+---
+
+# `set()` vs `put()` in `ophyd`
+
+In ophyd, both `set()` and `put()` can write a new value to a writable signal, but they serve different purposes.
+
+The short version is:
+
+- use `put()` for a direct low-level write
+- use `set()` when you want a write operation with completion tracking
+
+That distinction matters in BEC because user-facing device operations often need clear completion semantics, while device-internal helper signals often just need to publish a new value immediately.
+
+## What `put()` does
+
+`put()` is the low-level write method on a signal.
+
+It sends a value to the signal and returns immediately. It does not return a `Status` object and is therefore not the right abstraction when the caller needs to wait for completion in a structured way.
+
+`put()` is a direct wrapper around the underlying control layer, i.e. in the case of pyepics (default for ophyd_devices), it is calling `epics.PV.put()`.
+
+Typical uses of `put()` include:
+
+- updating a soft signal inside device implementation code
+- writing trigger-like signals that should be pushed directly
+- direct signal writes where completion tracking is not needed or not meaningful
+
+Example:
+
+```python
+my_signal.put(5)
+```
+
+## What `set()` does
+
+`set()` is the higher-level write method.
+
+It starts a write operation and returns a `Status` object that tells the caller when the operation is considered complete or has failed. This makes `set()` a better fit for coordinated device actions, plans, and user-facing control flows.
+
+Example:
+
+```python
+status = my_signal.set(5)
+status.wait()
+```
+
+In other words, `set()` is not just about sending a value. It is about sending a value and giving the caller a standard way to observe completion.
+Under the hood, `set()` uses `put()` to perform the actual write, but it adds the completion tracking layer on top.
+
+## The practical difference
+
+The key difference is the API contract:
+
+
+
+
+
+
+
+
+ | Method |
+ Behavior |
+
+
+
+
+ put() |
+ Perform a direct write. No Status object is returned. |
+
+
+ set() |
+ Perform a write operation with completion tracking and return a Status. |
+
+
+
+
+That means the choice is usually driven by what the caller needs:
+
+- If you only need to publish or write a value, `put()` is often enough.
+- If the caller needs to wait until the operation is done, use `set()`.
+
+## How `set()` decides it is done
+
+For a basic ophyd `Signal`, `set()` uses the lower-level write path and then waits until the signal readback reaches the target value within the configured tolerances.
+
+This is why `set()` is often described as a write-and-wait operation rather than just a write.
+
+For `EpicsSignal`, there is one extra detail worth knowing: `put_complete` changes how `set()` decides that the write has finished. That EPICS-specific behavior is covered separately in [EPICS put completion](../../learn/devices/epics-put-completion.md).
+
+
+## Choosing between them
+
+In practice, the choice is simple:
+
+- use `put()` when you only need to write a value
+- use `set()` when the caller needs a `Status` and clear completion semantics
+
+!!! info "What to remember"
+ - `put()` is the low-level write method for directly updating a signal value.
+ - `set()` is the higher-level write method that returns a `Status` so callers can wait for completion.
+ - For a basic ophyd `Signal`, `set()` writes the value and waits until the readback reaches the target.
+ - If you are working with `EpicsSignal`, `put_complete` adds EPICS-specific completion behavior for `set()`.
diff --git a/zensical.toml b/zensical.toml
index 93e940f..5de82f2 100644
--- a/zensical.toml
+++ b/zensical.toml
@@ -118,10 +118,12 @@ nav = [
{ "Managing YAML Configs" = "learn/devices/managing-yaml-configs.md" },
{ "EPICS Motor Variants" = "learn/devices/epics-motors.md" },
{ "EPICS Signal Variants" = "learn/devices/epics-signals.md" },
+ { "set() vs put()" = "learn/devices/set-vs-put.md" },
] },
{ "Development" = [
{ "Pseudo Positioners" = "learn/devices/pseudo-positioners.md" },
{ "BEC Signals" = "learn/devices/bec-signals.md" },
+ { "EPICS put completion" = "learn/devices/epics-put-completion.md" },
{ "Simulated Devices" = "learn/devices/simulated-devices.md" },
] },
] },