149 lines
5.4 KiB
Markdown
149 lines
5.4 KiB
Markdown
# Decision Service Reference
|
||
|
||
## Overview
|
||
|
||
`DecisionService` provides the application-layer interface for recording
|
||
decisions during plan execution, retrieving decision histories, and
|
||
managing context snapshots. It lives in
|
||
`src/cleveragents/application/services/decision_service.py`.
|
||
|
||
## Dual-Mode Persistence
|
||
|
||
| Mode | UnitOfWork | Storage |
|
||
|------------|------------|------------------------------------------------|
|
||
| In-memory | `None` | Internal dicts (`_decisions`, `_plan_decisions`) |
|
||
| Persisted | provided | DB via `DecisionRepository` + in-memory cache |
|
||
|
||
When a `UnitOfWork` is wired, mutations are written to the database
|
||
first, then the in-memory cache is updated (write-through).
|
||
|
||
## Constructor
|
||
|
||
```python
|
||
DecisionService(
|
||
settings: Settings | None = None,
|
||
unit_of_work: UnitOfWork | None = None,
|
||
)
|
||
```
|
||
|
||
- **settings** – Application settings (optional for in-memory mode).
|
||
- **unit_of_work** – When provided, decisions are persisted via
|
||
`DecisionRepository`.
|
||
|
||
## Recording
|
||
|
||
### `record_decision()`
|
||
|
||
```python
|
||
svc.record_decision(
|
||
plan_id="01HV...",
|
||
decision_type=DecisionType.STRATEGY_CHOICE, # or a string
|
||
question="Which approach?",
|
||
chosen_option="Build a REST API",
|
||
parent_decision_id=None, # optional
|
||
alternatives_considered=None, # list[str]
|
||
confidence_score=None, # 0.0-1.0
|
||
rationale="", # human-readable
|
||
actor_reasoning=None, # raw LLM trace
|
||
context_snapshot=None, # auto-captured if omitted
|
||
artifacts_produced=None, # list[ArtifactRef]
|
||
is_correction=False,
|
||
corrects_decision_id=None,
|
||
correction_reason=None,
|
||
)
|
||
```
|
||
|
||
- Auto-assigns a monotonically increasing **sequence number** per plan.
|
||
- Auto-generates a **ULID** for `decision_id`.
|
||
- Auto-captures a **context snapshot** (SHA-256 hash) when no explicit
|
||
snapshot is provided.
|
||
- Stores the snapshot in the built-in `SnapshotStore`.
|
||
|
||
**Raises:**
|
||
|
||
- `ValidationError` – required fields missing or empty.
|
||
- `DuplicateDecisionError` – decision ID already exists.
|
||
|
||
## Retrieval
|
||
|
||
| Method | Description |
|
||
|---------------------------------------|--------------------------------------------|
|
||
| `get_decision(decision_id)` | Single decision by ULID |
|
||
| `list_decisions(plan_id)` | All decisions for a plan, ordered by seq |
|
||
| `list_by_type(plan_id, decision_type)`| Filtered by type, ordered by seq |
|
||
|
||
All raise `DecisionNotFoundError` when the target decision does not
|
||
exist (where applicable).
|
||
|
||
## Tree Operations
|
||
|
||
| Method | Description |
|
||
|-------------------------------|------------------------------------------|
|
||
| `get_tree(plan_id)` | BFS from root(s), level by level |
|
||
| `get_path_to_root(decision_id)` | Walk from a decision up to the root |
|
||
|
||
## Superseded Decisions
|
||
|
||
| Method | Description |
|
||
|-----------------------------------------------|---------------------------------------|
|
||
| `mark_superseded(decision_id, new_decision_id)` | Mark a decision as superseded |
|
||
| `get_superseded(plan_id)` | List all superseded decisions |
|
||
|
||
## Delete
|
||
|
||
```python
|
||
svc.delete_decision(decision_id) # -> True
|
||
```
|
||
|
||
Removes from both persistence and the snapshot store. Raises
|
||
`DecisionNotFoundError` if not found.
|
||
|
||
## Snapshot Store
|
||
|
||
`SnapshotStore` manages `ContextSnapshot` objects keyed by decision ID
|
||
with hash-based deduplication.
|
||
|
||
| Method | Description |
|
||
|---------------------------------|----------------------------------------------|
|
||
| `get_snapshot(decision_id)` | Retrieve snapshot for a decision |
|
||
| `get_snapshots_for_plan(plan_id)` | All snapshots for a plan's decisions |
|
||
| `snapshots.get_by_hash(hash)` | Decision IDs sharing the same context hash |
|
||
| `snapshots.store(id, snap)` | Store a snapshot (internal) |
|
||
| `snapshots.remove(id)` | Remove a snapshot |
|
||
|
||
## Statistics
|
||
|
||
| Method | Description |
|
||
|-------------------------------|----------------------------------|
|
||
| `count_decisions(plan_id)` | Number of decisions for a plan |
|
||
| `get_next_sequence(plan_id)` | Next available sequence number |
|
||
|
||
## Custom Exceptions
|
||
|
||
| Exception | Base | Description |
|
||
|--------------------------|--------------------------|-------------------------------------|
|
||
| `DuplicateDecisionError` | `BusinessRuleViolation` | Decision ID already exists |
|
||
| `DecisionNotFoundError` | `ResourceNotFoundError` | Decision not found |
|
||
| `SequenceConflictError` | `BusinessRuleViolation` | Sequence number already used |
|
||
|
||
## Running Tests
|
||
|
||
```bash
|
||
# Behave BDD tests
|
||
nox -s unit_tests -- features/decision_recording.feature
|
||
|
||
# Robot Framework integration smoke tests
|
||
nox -s integration_tests
|
||
|
||
# ASV benchmarks
|
||
nox -s benchmark
|
||
```
|
||
|
||
## Related
|
||
|
||
- Domain model: `src/cleveragents/domain/models/core/decision.py`
|
||
- Persistence: `src/cleveragents/infrastructure/database/repositories.py`
|
||
(`DecisionRepository`)
|
||
- Database schema: `docs/reference/database_schema.md`
|
||
- ADR: `docs/adr/ADR-033-decision-recording-protocol.md`
|