Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ Each PR ships independently green (`ruff` + `mypy minx_mcp` + `pytest`), with mi

- One spec doc under `docs/superpowers/specs/YYYY-MM-DD-slice6X-*.md` (adversarially reviewed before implementation).
- One plan doc under `docs/superpowers/plans/YYYY-MM-DD-slice6X-*.md` (step-by-step execution checklist).
- Sequentially numbered migration (next filename: `027_*.sql` after `026_memory_capture_fts.sql`). Slice 9 investigations should claim the next available migration.
- Sequentially numbered migration (next filename: `028_*.sql` after `027_investigations.sql`).
- Implemented-slices row appended to the table above, with LOC / date / verification block.
- Operator post-upgrade step added under "Post-Upgrade Operator Steps" if the migration is not fully reversible from application-level data (6g needed a backfill; 6i will need a one-shot FTS5 rebuild; 6l will need a one-shot embedding backfill and a cost-ceiling env var).
- After deploying `026_memory_capture_fts.sql`, run `python -m scripts.rebuild_memory_fts /path/to/minx.db` so any pre-existing `captured_thought` rows are indexed by `payload.text` and `payload.capture_type`.
Expand Down
21 changes: 12 additions & 9 deletions docs/superpowers/specs/2026-04-19-slice9-agentic-investigations.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ Reasons:

| Surface | Why agentic | Indicative trajectory |
| --------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `minx_investigate(question)` | Causal/exploratory questions. LLM decides whether to drill into merchants, categories, meals, goals. | `finance_categories` → `finance_transactions(...)` → maybe `meals_list` → maybe `get_insight_history` → compose |
| `minx_investigate(question)` | Causal/exploratory questions. LLM decides whether to drill into merchants, categories, meals, goals. | `finance_query` → maybe `meals_list` → maybe `get_insight_history` → compose |
| `minx_plan(objective)` | Scheduling/planning across domains. Depends on what it finds. | `goal_list` → `get_goal_trajectory` → `training_list` → `meals_list` → draft → revise |
| `minx_retro(period, subject)` | Causal analysis across months. LLM picks which detectors to replay, which transactions to sample. | `get_insight_history` → `goal_trajectory` → sampling tools → synthesize |
| `minx_retro(period, subject)` | Causal analysis across months. LLM picks which detectors to replay, which transactions to sample. | `get_insight_history` → `get_goal_trajectory` → sampling tools → synthesize |
| `minx_onboard_entity(kind, name)` | Hydrates an entity/pattern page from scratch. Branches on what it finds. | `finance_transactions(merchant=...)` → `memory_list(subject=...)` → maybe `persist_note` |


Common shape: **one question in, one report out, unpredictable middle.**

## 5) Schema (Core)

Migration filename: use the next available sequential migration when this slice lands. As of 2026-04-27, Slice 6i-6l are expected to ship before investigations, so this spec no longer pre-claims `021_investigations.sql`.
Migration filename: use the next available sequential migration when this slice lands. After `026_memory_capture_fts.sql`, this should be `027_investigations.sql` unless another migration lands first.

```sql
CREATE TABLE investigations (
Expand Down Expand Up @@ -86,7 +86,7 @@ CREATE INDEX idx_investigations_kind_started ON investigations(kind, started_at
CREATE INDEX idx_investigations_running ON investigations(status) WHERE status = 'running';
```

**Trajectory storage policy:** `trajectory_json` stores a **digest** per step (tool name, arg hash, result row count / bytes, latency). It does NOT store full tool outputs — those can be large and contain PII. Full outputs are reconstructable by replaying the tools against the DB at investigation time.
**Trajectory storage policy:** `trajectory_json` stores a **digest** per step (tool name, arg hash, result row count / bytes, latency). It does NOT store full tool outputs — those can be large and contain PII. Some outputs may be approximately reproducible by re-querying domain tools, but replay is not a durable audit guarantee because data, code, and time-dependent results can change.

**Render storage policy:** `response_template` and `response_slots_json` store the latest lifecycle event so read APIs can expose a stable render surface without parsing trajectory text. Step-level render events are stored inside `trajectory_json` step entries.

Expand All @@ -103,16 +103,18 @@ complete_investigation(
investigation_id,
status, # 'succeeded' | 'failed' | 'cancelled' | 'budget_exhausted'
answer_md,
citation_refs, # optional list of typed references used by the harness answer
tool_call_count,
token_input,
token_output,
cost_usd,
error_message,
) -> {"investigation_id": int, "response_template": "investigation.completed|investigation.failed|investigation.cancelled|investigation.budget_exhausted", "response_slots": {...}}
log_investigation(...) # convenience wrapper with the same logging role as log_playbook_run;
# MCP return shape follows the render-contract amendment
log_investigation(kind, question, context_json, harness, trajectory_json, status, answer_md, citation_refs, ...)
# convenience wrapper with the same logging role as log_playbook_run;
# MCP return shape follows the render-contract amendment

investigation_history(kind=None, since=None, days=30, limit=100) -> {"runs": [...], "truncated": bool}
investigation_history(kind=None, harness=None, status=None, since=None, days=30, limit=100) -> {"runs": [...], "truncated": bool}
investigation_get(investigation_id) -> {"run": {...}} # includes trajectory and latest response_template/response_slots
```

Expand Down Expand Up @@ -180,7 +182,8 @@ Ship order: 9a → 9b → 9d (first usable surface) → 9c + 9e + 9f + 9g in any
- Concurrent starts with different kinds don't collide; same-kind concurrent is allowed (investigations are user-initiated, no cron contention).
- `append_investigation_step` rejects steps after terminal status.
- Trajectory digest: `result_digest` never contains raw tool output bytes; `context_json` goes through a redaction pass for known PII fields (email, phone, account numbers).
- `investigation_history` pagination/filter matches `playbook_history` semantics.
- `investigation_history` pagination/filter matches `playbook_history` semantics for `kind`, `harness`, `status`, `since`, `days`, and `limit`.
- `complete_investigation` and `log_investigation` persist typed `citation_refs` separately from `answer_md`.

### Harness tests (9d–9f, outside this repo)

Expand All @@ -190,6 +193,6 @@ Ship order: 9a → 9b → 9d (first usable surface) → 9c + 9e + 9f + 9g in any

## 11) Relationship to Other Slices

- **Slice 6 (Memory):** investigations can cite memories by id in `answer_md`; `memory_get`, `memory_list`, FTS5 search, memory graph edges, and embeddings/hybrid retrieval are primary inputs.
- **Slice 6 (Memory):** investigations can cite memories by id in structured `citation_refs`; `memory_get`, `memory_list`, FTS5 search, memory graph edges, and embeddings/hybrid retrieval are primary inputs.
- **Slice 8 (Playbooks):** audit pattern (two-phase + convenience wrapper) is lifted directly. A sibling to `playbook_reconcile_crashed` can be added later if crashed-running investigations need automated reconciliation; specify that tool explicitly before shipping it.
- **Slice 5 (Harness Adaptation):** a second harness would reimplement the loop against the same Core API. The agent-loop pattern is harness-specific; the tool surface is portable.
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Completion should return:
"kind": "investigate",
"status": "succeeded",
"tool_call_count": 8,
"citation_count": 4,
"cited_memory_count": 3,
"cost_usd": 0.12
}
Expand All @@ -80,6 +81,8 @@ Completion should return:

Hermes may render those as "Investigation complete" or a richer explanation, but the final prose lives outside Core.

`complete_investigation` and `log_investigation` should accept optional `citation_refs`, stored in `citation_refs_json`, so references used by `answer_md` are available without parsing prose.

## Schema Adjustments

The existing Slice 9 spec stores `answer_md`. Under this update:
Expand All @@ -101,7 +104,18 @@ Where:

- `response_template` stores the latest lifecycle/event template key.
- `response_slots_json` stores JSON slots for that latest event.
- `citation_refs_json` stores references such as memory ids, investigation ids, tool result digests, or vault paths used by the harness answer.
- `citation_refs_json` stores references used by the harness answer. It is a JSON list of typed objects:

```json
[
{"type": "memory", "id": 123},
{"type": "investigation", "id": 42},
{"type": "vault_path", "path": "Minx/Reviews/2026-04-28.md"},
{"type": "tool_result_digest", "tool": "finance_query", "digest": "9a2f1c4e7b8d..."}
]
```

Allowed reference `type` values are `memory`, `investigation`, `vault_path`, and `tool_result_digest`. Unknown reference types should be rejected until a follow-up spec defines them.

Use these columns for the initial Core implementation so `investigation_history` and `investigation_get` have a stable latest-event surface. Step-level events still live in `trajectory_json` entries. A trajectory-only storage approach should be a deliberate later simplification, and only if history/get tools continue exposing `response_template`, `response_slots`, and step event fields without parsing prose.

Expand All @@ -114,21 +128,31 @@ Use these columns for the initial Core implementation so `investigation_history`
"step": 3,
"event_template": "investigation.step_logged",
"event_slots": {
"tool": "finance_query",
"result_digest": "sha256:9a2f1c4e7b8d",
"latency_ms": 182,
"row_count": 12
},
"tool": "finance_query",
"args_digest": "sha256:6f12b4c8d901",
"result_digest": "sha256:9a2f1c4e7b8d",
"args_digest": "6f12b4c8d901...",
"result_digest": "9a2f1c4e7b8d...",
"latency_ms": 182
}
```

Required fields: `step`, `event_template`, `event_slots`, `tool`, `args_digest`, `result_digest`, and `latency_ms`.

Allowed `event_slots` values are structured digests, counts, enum-like labels, ids, booleans, numbers, and short normalized strings. No raw tool output should be stored in `event_slots`; use `result_digest`, `row_count`, `byte_count`, or citation ids instead.
Digest fields are raw lowercase SHA-256 hex strings over canonical JSON or canonical text, without a `sha256:` prefix. This matches the existing Core fingerprint helper style.

Allowed `event_slots` values are structured digests, counts, enum-like labels, ids, booleans, numbers, and short normalized strings. No raw tool output should be stored in `event_slots`; use `result_digest`, `row_count`, `byte_count`, or citation ids instead. `event_slots` may include render-relevant summaries, but it does not need to duplicate top-level `tool`, `args_digest`, `result_digest`, or `latency_ms`.

Validation rules:

- `step` must be a positive integer.
- `event_template` must be one of the investigation template keys listed above.
- `tool` must be a non-empty normalized tool name.
- `args_digest` and `result_digest` must match `[0-9a-f]{64}`.
- `latency_ms` must be a non-negative integer.
- `event_slots` must be a JSON object with at most 32 top-level keys, max nesting depth 4, and string leaves capped at 1024 UTF-8 bytes.
- The serialized `step_json` must be capped at 16 KiB.
- Reject raw-output keys such as `raw_output`, `tool_output`, `result_json`, `result_rows`, `transcript`, and `messages`.

## Confirmations

Expand All @@ -146,7 +170,16 @@ If an investigation needs user confirmation before a risky step, Core should sto
}
```

`append_investigation_step` may return `response_template == "investigation.needs_confirmation"` instead of `investigation.step_logged` when the appended step records a proposed risky action that is waiting on the user. Hermes renders the prompt and records the user's decision by calling the appropriate Core/domain tool. Core should not invent the confirmation wording.
`append_investigation_step` returns `response_template == "investigation.needs_confirmation"` only when the appended step's `event_template` is exactly `investigation.needs_confirmation`; otherwise it returns the step's render event, normally `investigation.step_logged`. The investigation status remains `running`. Hermes renders the prompt, records the user's decision by calling the appropriate Core/domain tool, and should append a later `investigation.step_logged` step that records the decision digest. Core should not invent the confirmation wording or treat confirmation slots as authorization for a domain mutation.

## Redaction And Blocking

Core should apply deterministic local secret handling before persistence:

- Redact redactable secret-shaped values in `question`, `answer_md`, `error_message`, JSON string leaves in `context_json`, JSON string leaves in `citation_refs`, and `event_slots`.
- Block non-redactable secret-shaped values, such as private key blocks, with `INVALID_INPUT`.
- Do not scan digest fields as secrets.
- Preserve structured fields when redacting JSON leaves; do not collapse structured objects into prose.

## Testing

Expand All @@ -155,7 +188,10 @@ Core tests should cover:
- `start_investigation` returns `response_template == "investigation.started"`.
- `append_investigation_step` stores step `event_template` and JSON-safe `event_slots`.
- `complete_investigation` returns `investigation.completed`, `investigation.failed`, `investigation.cancelled`, or `investigation.budget_exhausted` based on status.
- Stored slots never include raw tool output.
- Stored slots never include raw tool output and reject raw-output key names.
- Digest fields reject non-hex or prefixed digest strings.
- `citation_refs` accept only the typed reference schema and are exposed by history/get without prose parsing.
- `question`, `context_json`, `answer_md`, `error_message`, `citation_refs`, and `event_slots` follow the redaction/blocking policy.
- `answer_md`, if present, is accepted as harness-authored content and not generated by Core.
- history/get tools expose structured event/template data without requiring prose parsing.

Expand All @@ -167,7 +203,7 @@ Hermes tests, outside this repo, should cover:

## Read Surfaces

`investigation_history` should expose the latest lifecycle render event for each run:
`investigation_history(kind=None, harness=None, status=None, since=None, days=30, limit=100)` should expose the latest lifecycle render event for each run:

```json
{
Expand All @@ -188,7 +224,7 @@ Hermes tests, outside this repo, should cover:
}
```

`investigation_get` should include the same latest `response_template` / `response_slots` plus the trajectory entries with each step's `event_template` / `event_slots`. `log_investigation` is a convenience wrapper over the same lifecycle storage rules; it should return the terminal lifecycle render hint that matches the logged status.
`investigation_get` should include the same latest `response_template` / `response_slots`, `citation_refs`, and the trajectory entries with each step's `event_template` / `event_slots`. `log_investigation` is a convenience wrapper over the same lifecycle storage rules; it should return the terminal lifecycle render hint that matches the logged status.

## Relationship To The Existing Slice 9 Spec

Expand Down
Loading
Loading