You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/design/human-in-the-loop/human-in-the-loop.md
+85-7Lines changed: 85 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -173,8 +173,10 @@ CREATE INDEX IF NOT EXISTS idx_approval_requests_expires
173
173
**YAML config example:**
174
174
175
175
```yaml
176
-
# Global approval TTL (seconds)
177
-
approval_ttl_seconds: 300# Default: 5 minutes
176
+
# Approval settings
177
+
approvals:
178
+
ttl_seconds: 300# How long approvals stay pending (default: 300)
179
+
retention_days: 30# How long to retain decided approvals (default: 30)
178
180
179
181
mcp_servers:
180
182
# Example 1: Require approval for all tools
@@ -204,8 +206,8 @@ mcp_servers:
204
206
**Configuration class:**
205
207
206
208
```python
207
-
from typing import Literal, Optional
208
-
from pydantic import Field, model_validator
209
+
from typing import Literal
210
+
from pydantic import Field, PositiveInt, model_validator
209
211
from typing_extensions import Self
210
212
211
213
class ApprovalFilter(ConfigurationBase):
@@ -238,6 +240,26 @@ class ApprovalFilter(ConfigurationBase):
238
240
return self
239
241
240
242
243
+
class ApprovalsConfiguration(ConfigurationBase):
244
+
"""Configuration for human-in-the-loop approvals.
245
+
246
+
Attributes:
247
+
ttl_seconds: How long approval requests remain pending before expiring.
248
+
retention_days: How long to retain decided approvals for audit purposes.
249
+
"""
250
+
251
+
ttl_seconds: PositiveInt = Field(
252
+
300,
253
+
title="Approval TTL",
254
+
description="Seconds before pending approval requests expire",
255
+
)
256
+
retention_days: PositiveInt = Field(
257
+
30,
258
+
title="Retention period",
259
+
description="Days to retain decided approvals before cleanup",
260
+
)
261
+
262
+
241
263
class ModelContextProtocolServer(ConfigurationBase):
242
264
"""Model context protocol server configuration."""
243
265
@@ -257,6 +279,15 @@ class ModelContextProtocolServer(ConfigurationBase):
257
279
)
258
280
```
259
281
282
+
Main configuration adds:
283
+
```python
284
+
approvals: ApprovalsConfiguration = Field(
285
+
default_factory=ApprovalsConfiguration,
286
+
title="Approvals configuration",
287
+
description="Settings for human-in-the-loop approval workflow",
288
+
)
289
+
```
290
+
260
291
### API changes
261
292
262
293
**New endpoints:**
@@ -392,6 +423,52 @@ For streaming, emit an event:
392
423
5.**Rate limiting**: Approval endpoints should be rate-limited to prevent
393
424
abuse (handled by existing rate limiting infrastructure).
394
425
426
+
### Data retention
427
+
428
+
Approval records are retained for audit purposes but purged after a
429
+
configurable retention period to prevent database bloat.
430
+
431
+
**Retention policy:**
432
+
433
+
1.**Completed records** (approved/denied): Retained for a configurable period
434
+
(default: 30 days from decision), then purged by a background cleanup task
435
+
2.**Expired records**: Purged after 24 hours past expiration
436
+
3.**Pending records**: Subject to TTL expiration, then treated as expired
437
+
438
+
**Configuration:**
439
+
440
+
```yaml
441
+
approvals:
442
+
retention_days: 30# How long to keep decided approvals (default: 30)
443
+
```
444
+
445
+
**Cleanup queries:**
446
+
447
+
SQLite:
448
+
```sql
449
+
DELETE FROM approval_requests
450
+
WHERE (status IN ('approved', 'denied')
451
+
AND datetime(decided_at) < datetime('now', '-30 days'))
452
+
OR (status = 'expired'
453
+
AND datetime(expires_at) < datetime('now', '-1 day'));
454
+
```
455
+
456
+
PostgreSQL:
457
+
```sql
458
+
DELETEFROM approval_requests
459
+
WHERE (status IN ('approved', 'denied')
460
+
AND decided_at < NOW() - INTERVAL '30 days')
461
+
OR (status ='expired'
462
+
AND expires_at < NOW() - INTERVAL '1 day');
463
+
```
464
+
465
+
**Implementation options:**
466
+
467
+
-**Scheduled task**: Run cleanup periodically (e.g., hourly or daily)
468
+
-**Lazy cleanup**: Trigger cleanup during read operations when record count
469
+
exceeds a threshold
470
+
-**Database-level**: Use PostgreSQL's `pg_cron` or application-level scheduler
471
+
395
472
### Migration / backwards compatibility
396
473
397
474
-**No breaking changes**: Default `require_approval="never"` maintains current
@@ -407,10 +484,10 @@ For streaming, emit an event:
407
484
408
485
| File | What to do |
409
486
|------|------------|
410
-
|`src/models/config.py`| Add `ApprovalFilter` class, add `require_approval` to `ModelContextProtocolServer`, add `approval_ttl_seconds` to main config|
487
+
|`src/models/config.py`| Add `ApprovalFilter` class, add `require_approval` to `ModelContextProtocolServer`, add `ApprovalsConfiguration` class with `ttl_seconds` and `retention_days`|
411
488
|`src/utils/responses.py`| Modify `get_mcp_tools()` to pass `require_approval` from config |
412
-
|`src/cache/sqlite_cache.py`| Add `approval_requests` table and CRUD methods |
413
-
|`src/cache/postgres_cache.py`| Add `approval_requests` table and CRUD methods |
489
+
|`src/cache/sqlite_cache.py`| Add `approval_requests` table, CRUD methods, and cleanup method|
490
+
|`src/cache/postgres_cache.py`| Add `approval_requests` table, CRUD methods, and cleanup method|
414
491
|`src/app/endpoints/approvals.py`| New file: `/approvals` endpoint handlers |
0 commit comments