F008: Review Decision Endpoint
| Field | Value |
|---|---|
| Feature ID | F008 |
| Status | Implemented |
| Priority | P1 |
| Depends On | F007 (Record Decision) |
| Blocks | F009 (Calibration) |
| Decision | 30d70c34 |
Summary
Section titled “Summary”Add cstp.reviewDecision JSON-RPC method to record outcomes for existing decisions, enabling the feedback loop for calibration and learning.
- Add outcome data to existing decisions
- Support partial updates (don’t require all fields)
- Trigger re-indexing with outcome metadata
- Track review timestamps
- Enable lessons capture
Non-Goals
Section titled “Non-Goals”- Decision deletion (future)
- Bulk review (future)
- Automatic outcome detection (future enhancement)
API Specification
Section titled “API Specification”Method
Section titled “Method”cstp.reviewDecision
Request
Section titled “Request”{ "jsonrpc": "2.0", "method": "cstp.reviewDecision", "params": { "id": "fddb416c", "outcome": "success", "actualResult": "Redis caching reduced latency by 40%", "lessons": "Should have considered cluster mode from the start", "notes": "Will apply clustering in next iteration" }, "id": 1}Parameters
Section titled “Parameters”| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✅ | Decision ID to review |
outcome |
string | ✅ | Outcome: success, partial, failure, abandoned |
actualResult |
string | ❌ | What actually happened |
lessons |
string | ❌ | Lessons learned for future |
notes |
string | ❌ | Additional notes |
affectedKpis |
object | ❌ | KPI impacts: {"latency": -0.4, "cost": 0.1} |
Response (Success)
Section titled “Response (Success)”{ "jsonrpc": "2.0", "result": { "success": true, "id": "fddb416c", "path": "decisions/2026/02/2026-02-05-decision-fddb416c.yaml", "status": "reviewed", "reviewedAt": "2026-02-12T14:30:00Z", "reindexed": true }, "id": 1}Response (Error - Decision Not Found)
Section titled “Response (Error - Decision Not Found)”{ "jsonrpc": "2.0", "error": { "code": -32004, "message": "Decision not found", "data": {"id": "invalid123"} }, "id": 1}Implementation Plan
Section titled “Implementation Plan”Phase 1: Decision Lookup (~1h)
Section titled “Phase 1: Decision Lookup (~1h)”1.1 Add find_decision function
Section titled “1.1 Add find_decision function”async def find_decision(decision_id: str, decisions_path: str | None = None) -> tuple[Path, dict] | None: """Find a decision by ID.
Searches decisions directory for matching ID. Returns (path, data) or None if not found. """ base = Path(decisions_path or DECISIONS_PATH)
# Search pattern: decisions/YYYY/MM/*-decision-{id}.yaml for year_dir in base.iterdir(): if not year_dir.is_dir(): continue for month_dir in year_dir.iterdir(): if not month_dir.is_dir(): continue for file in month_dir.glob(f"*-decision-{decision_id}.yaml"): with open(file) as f: data = yaml.safe_load(f) return (file, data)
return NonePhase 2: Review Service (~1.5h)
Section titled “Phase 2: Review Service (~1.5h)”2.1 Add review models
Section titled “2.1 Add review models”@dataclassclass ReviewDecisionRequest: id: str outcome: str # success, partial, failure, abandoned actual_result: str | None = None lessons: str | None = None notes: str | None = None affected_kpis: dict[str, float] | None = None
@dataclassclass ReviewDecisionResponse: success: bool id: str path: str status: str reviewed_at: str reindexed: bool2.2 Add review_decision function
Section titled “2.2 Add review_decision function”async def review_decision( request: ReviewDecisionRequest, decisions_path: str | None = None,) -> ReviewDecisionResponse: """Add outcome data to an existing decision."""
# Find decision result = await find_decision(request.id, decisions_path) if not result: raise ValueError(f"Decision not found: {request.id}")
path, data = result now = datetime.now(UTC)
# Update decision data data["status"] = "reviewed" data["outcome"] = request.outcome data["reviewed_at"] = now.isoformat()
if request.actual_result: data["actual_result"] = request.actual_result if request.lessons: data["lessons"] = request.lessons if request.notes: data["review_notes"] = request.notes if request.affected_kpis: data["affected_kpis"] = request.affected_kpis
# Write updated YAML with open(path, "w") as f: yaml.dump(data, f, default_flow_style=False, sort_keys=False)
# Re-index with outcome metadata reindexed = await reindex_decision(request.id, data)
return ReviewDecisionResponse( success=True, id=request.id, path=str(path), status="reviewed", reviewed_at=now.isoformat(), reindexed=reindexed, )Phase 3: Dispatcher Integration (~30m)
Section titled “Phase 3: Dispatcher Integration (~30m)”async def _handle_review_decision(params: dict[str, Any], agent_id: str) -> dict[str, Any]: request = ReviewDecisionRequest.from_dict(params)
valid_outcomes = {"success", "partial", "failure", "abandoned"} if request.outcome not in valid_outcomes: raise ValueError(f"outcome must be one of {valid_outcomes}")
response = await review_decision(request) return response.to_dict()
# Registerdispatcher.register("cstp.reviewDecision", _handle_review_decision)Phase 4: Tests (~1h)
Section titled “Phase 4: Tests (~1h)”Test cases:
- Review existing decision successfully
- Decision not found error
- Invalid outcome error
- Partial update (only required fields)
- Full update (all fields)
- Re-indexing updates metadata
Files to Create/Modify
Section titled “Files to Create/Modify”| File | Action | Description |
|---|---|---|
a2a/cstp/decision_service.py |
Modify | Add find_decision, review_decision |
a2a/cstp/models.py |
Modify | Add ReviewDecisionRequest/Response |
a2a/cstp/dispatcher.py |
Modify | Register new method |
tests/test_decision_service.py |
Modify | Add review tests |
tests/test_f008_review_decision.py |
Create | Integration tests |
YAML Schema After Review
Section titled “YAML Schema After Review”id: fddb416csummary: "Use Redis for caching"decision: "Use Redis for caching"category: architectureconfidence: 0.85stakes: highstatus: reviewed # Changed from "pending"date: "2026-02-05T00:48:00Z"context: "Choosing cache layer"reasons: - type: analysis text: "Fast lookups needed" strength: 0.9
# Added by reviewoutcome: success # success | partial | failure | abandonedreviewed_at: "2026-02-12T14:30:00Z"actual_result: "Latency reduced 40%"lessons: "Should have considered clustering"review_notes: "Will add clustering next sprint"affected_kpis: latency: -0.4 cost: 0.1Estimated Effort
Section titled “Estimated Effort”| Phase | Time |
|---|---|
| Decision Lookup | 1h |
| Review Service | 1.5h |
| Dispatcher Integration | 30m |
| Tests | 1h |
| Total | ~4h |