diff --git a/.sqlx/query-cc4b885c31f965013ccaa6f8ae33f6c8d68898654fc0873682c8212339088306.json b/.sqlx/query-cc4b885c31f965013ccaa6f8ae33f6c8d68898654fc0873682c8212339088306.json
new file mode 100644
index 0000000..438dda0
--- /dev/null
+++ b/.sqlx/query-cc4b885c31f965013ccaa6f8ae33f6c8d68898654fc0873682c8212339088306.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "SQLite",
+ "query": "DELETE FROM agent_devices WHERE last_seen_unix < ?1",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Right": 1
+ },
+ "nullable": []
+ },
+ "hash": "cc4b885c31f965013ccaa6f8ae33f6c8d68898654fc0873682c8212339088306"
+}
diff --git a/docs/config-durations.md b/docs/config-durations.md
index 8daa721..97a1d94 100644
--- a/docs/config-durations.md
+++ b/docs/config-durations.md
@@ -31,12 +31,25 @@ Reconnect scheduling retains its existing 100ms minimum backoff.
command_timeout = "30s"
enroll_token_ttl = "1d"
history_retention = "30d"
+device_retention = "60d"
```
All three settings must be positive. Enrollment TTL and history retention must
resolve to whole seconds. Command timeout can use subsecond values.
`history_retention` deletes old audit events and alert transitions, not device
-snapshots or saved identities. Device retention is a separate follow-up.
+snapshots or saved identities.
+
+`device_retention` expires agent-collected device rows and their child endpoints,
+addresses, names and facts. The default is `"60d"`; `"0d"` disables expiry.
+It must resolve to whole seconds. Age is measured from the last CC-received
+snapshot that included the device, not endpoint timestamps or online presence.
+An offline device included in new snapshots therefore stays retained. Saved
+devices and their manually attached identifiers are never deleted by this cleanup.
+Complete snapshots still remove devices omitted by that agent immediately;
+retention handles old rows left behind when an agent stops syncing.
+Expired rows are removed by the Unix daemon's existing maintenance loop every
+five minutes, including an initial tick at startup. Restart CC to apply changes
+to this setting; the state-only reload does not reload configuration.
## Legacy Files
diff --git a/docs/roadmap.md b/docs/roadmap.md
index e9f121f..9e71b39 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -36,7 +36,8 @@ of remembered identifiers based on observation expiry.
## Telemetry Follow-Ups
-- [x] Human-readable agent/CC config durations with unit-aware legacy loading, stderr deprecation warnings, and canonical init-config output. See [configuration durations](config-durations.md). History retention remains separate from future device expiry.
+- [x] Human-readable agent/CC config durations with unit-aware legacy loading, stderr deprecation warnings, and canonical init-config output. See [configuration durations](config-durations.md).
+- [x] CC device retention by last snapshot inclusion (`device_retention = "60d"`, zero disables), independent of audit history and preserving saved identities.
- [x] Separate console, OTLP log, and trace filters using per-layer filtering. Explicit signal config overrides `RUST_LOG`, then CLI verbosity supplies defaults. Unconfigured defaults remain info-level; exporter-failure logs stay local. Live validation pending.
- [ ] Record hostname-resolution success/failure without exporting hostnames or resolved addresses. Current spans show timing but do not explicitly classify the result.
diff --git a/wakey-control-plane/.sqlx/query-cc4b885c31f965013ccaa6f8ae33f6c8d68898654fc0873682c8212339088306.json b/wakey-control-plane/.sqlx/query-cc4b885c31f965013ccaa6f8ae33f6c8d68898654fc0873682c8212339088306.json
new file mode 100644
index 0000000..438dda0
--- /dev/null
+++ b/wakey-control-plane/.sqlx/query-cc4b885c31f965013ccaa6f8ae33f6c8d68898654fc0873682c8212339088306.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "SQLite",
+ "query": "DELETE FROM agent_devices WHERE last_seen_unix < ?1",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Right": 1
+ },
+ "nullable": []
+ },
+ "hash": "cc4b885c31f965013ccaa6f8ae33f6c8d68898654fc0873682c8212339088306"
+}
diff --git a/wakey-control-plane/src/config/init.rs b/wakey-control-plane/src/config/init.rs
index 85c470b..4eb46b6 100644
--- a/wakey-control-plane/src/config/init.rs
+++ b/wakey-control-plane/src/config/init.rs
@@ -103,6 +103,8 @@ pub fn write_init_config(args: &InitConfigArgs) -> Result