Skip to content

Commit 07ee7ae

Browse files
committed
addressed comments
1 parent 263999f commit 07ee7ae

1 file changed

Lines changed: 85 additions & 7 deletions

File tree

docs/design/human-in-the-loop/human-in-the-loop.md

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,10 @@ CREATE INDEX IF NOT EXISTS idx_approval_requests_expires
173173
**YAML config example:**
174174

175175
```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)
178180

179181
mcp_servers:
180182
# Example 1: Require approval for all tools
@@ -204,8 +206,8 @@ mcp_servers:
204206
**Configuration class:**
205207
206208
```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
209211
from typing_extensions import Self
210212

211213
class ApprovalFilter(ConfigurationBase):
@@ -238,6 +240,26 @@ class ApprovalFilter(ConfigurationBase):
238240
return self
239241

240242

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+
241263
class ModelContextProtocolServer(ConfigurationBase):
242264
"""Model context protocol server configuration."""
243265

@@ -257,6 +279,15 @@ class ModelContextProtocolServer(ConfigurationBase):
257279
)
258280
```
259281

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+
260291
### API changes
261292

262293
**New endpoints:**
@@ -392,6 +423,52 @@ For streaming, emit an event:
392423
5. **Rate limiting**: Approval endpoints should be rate-limited to prevent
393424
abuse (handled by existing rate limiting infrastructure).
394425

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+
DELETE FROM 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+
395472
### Migration / backwards compatibility
396473

397474
- **No breaking changes**: Default `require_approval="never"` maintains current
@@ -407,10 +484,10 @@ For streaming, emit an event:
407484

408485
| File | What to do |
409486
|------|------------|
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` |
411488
| `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 |
414491
| `src/app/endpoints/approvals.py` | New file: `/approvals` endpoint handlers |
415492
| `src/app/endpoints/query.py` | Handle `mcp_approval_request`, return `requires_action` |
416493
| `src/app/endpoints/streaming_query.py` | Handle approval request events, emit `approval_required` |
@@ -495,6 +572,7 @@ Example config files go in `examples/`.
495572

496573
| Date | Change | Reason |
497574
|------|--------|--------|
575+
| 2026-04-13 | Added data retention policy section | Prevent database bloat from accumulated approval records |
498576
| 2026-04-01 | Initial version | LCORE-1589 spike |
499577

500578
## Appendix A: Llama Stack Types Reference

0 commit comments

Comments
 (0)