generated from coulomb/repo-seed
Some checks failed
Test / test (push) Has been cancelled
Marks all Phase 5 tasks and workplan done in file. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
421 lines
16 KiB
Markdown
421 lines
16 KiB
Markdown
---
|
||
id: IHUB-WP-0005
|
||
type: workplan
|
||
title: "IHF Phase 5 — Agent-Assisted Distillation and Suggestion"
|
||
domain: inter_hub
|
||
repo: inter-hub
|
||
status: done
|
||
owner: custodian
|
||
topic_slug: inter_hub
|
||
created: "2026-03-29"
|
||
updated: "2026-03-29"
|
||
state_hub_workstream_id: "535a6479-9852-4386-8ad0-f86397a018c5"
|
||
---
|
||
|
||
# IHF Phase 5 — Agent-Assisted Distillation and Suggestion
|
||
|
||
## Goal
|
||
|
||
Introduce bounded AI support into the interaction-governance loop. Phase 4
|
||
established the antifragility loop — outcome signals, regression detection,
|
||
recurrence tracking. Phase 5 adds AI capability to reduce the cognitive burden
|
||
on human reviewers: cluster summaries, drafted requirement candidates, duplicate
|
||
detection, policy-sensitivity flagging, and implementation path proposals.
|
||
|
||
All AI outputs are **attributable** (model_ref recorded), **reviewable**
|
||
(AgentReviewRecord), and **reversible** (proposals can be rejected). No
|
||
autonomous final decisions. No silent requirement promotion.
|
||
|
||
## Background
|
||
|
||
Phase 1 (IHUB-WP-0001) delivered the Minimal Interaction Core. Phase 2
|
||
(IHUB-WP-0002) delivered Structured Feedback and Triage. Phase 3
|
||
(IHUB-WP-0003) delivered Governance and Decision Linkage. Phase 4
|
||
(IHUB-WP-0004) delivered Outcome Observation and Antifragility. All Phase 4
|
||
exit criteria are met.
|
||
|
||
Phase 5 is the fifth of eight phases in the IHF specification
|
||
(`specs/InteractionHubFrameworkSpecification_v0.1.md`, §14 Phase 5).
|
||
|
||
**Technology stack:** IHP v1.5 (Haskell, Nix), PostgreSQL, Anthropic API
|
||
(claude-sonnet-4-6). Agent calls are synchronous HTTP in controller actions.
|
||
API key from `IHP_ANTHROPIC_API_KEY` environment variable.
|
||
|
||
Reference: `docs/ihp-overview.md`, `docs/ihp-data-and-queries.md`,
|
||
`docs/ihp-controllers-views-forms.md`, `docs/ihp-realtime.md`.
|
||
|
||
## Phase 5 Exit Criteria (from IHF spec §14 Phase 5)
|
||
|
||
- AI assistance reduces triage and synthesis burden
|
||
- Human reviewers remain in control
|
||
- AI outputs are auditable and attributable
|
||
|
||
## Data Artifacts Introduced (Phase 5)
|
||
|
||
`AgentProposal`, `AgentReviewRecord`, `ConfidenceAnnotation`
|
||
|
||
---
|
||
|
||
## Tasks
|
||
|
||
### T01 — Schema: AgentProposal, AgentReviewRecord, ConfidenceAnnotation
|
||
|
||
```task
|
||
id: IHUB-WP-0005-T01
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "6e1a9d31-a7e9-4d71-a726-44eaf739371c"
|
||
```
|
||
|
||
Add Phase 5 tables to `Application/Schema.sql` and write migration:
|
||
|
||
```sql
|
||
CREATE TABLE agent_proposals (
|
||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||
proposal_type TEXT NOT NULL,
|
||
-- proposal_type values: summary | requirement_draft | duplicate_flag |
|
||
-- policy_flag | impl_proposal
|
||
source_widget_id UUID REFERENCES widgets(id) ON DELETE SET NULL,
|
||
source_candidate_id UUID REFERENCES requirement_candidates(id) ON DELETE SET NULL,
|
||
source_thread_id UUID REFERENCES annotation_threads(id) ON DELETE SET NULL,
|
||
source_decision_id UUID REFERENCES decision_records(id) ON DELETE SET NULL,
|
||
content TEXT NOT NULL,
|
||
model_ref TEXT NOT NULL, -- e.g. "claude-sonnet-4-6"
|
||
confidence NUMERIC CHECK (confidence BETWEEN 0 AND 1),
|
||
status TEXT NOT NULL DEFAULT 'pending',
|
||
-- status values: pending | accepted | rejected | superseded
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
|
||
);
|
||
|
||
CREATE INDEX agent_proposals_proposal_type_idx ON agent_proposals (proposal_type);
|
||
CREATE INDEX agent_proposals_status_idx ON agent_proposals (status);
|
||
CREATE INDEX agent_proposals_source_widget_id_idx ON agent_proposals (source_widget_id);
|
||
CREATE INDEX agent_proposals_created_at_idx ON agent_proposals (created_at DESC);
|
||
|
||
-- One review record per proposal (human decision on AI output)
|
||
CREATE TABLE agent_review_records (
|
||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||
proposal_id UUID NOT NULL REFERENCES agent_proposals(id) ON DELETE CASCADE,
|
||
reviewer_id UUID REFERENCES users(id),
|
||
decision TEXT NOT NULL, -- accepted | rejected | modified
|
||
notes TEXT,
|
||
reviewed_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
|
||
UNIQUE (proposal_id)
|
||
);
|
||
|
||
CREATE INDEX agent_review_records_proposal_id_idx ON agent_review_records (proposal_id);
|
||
|
||
-- Confidence annotations — per-dimension breakdown of AI confidence
|
||
CREATE TABLE confidence_annotations (
|
||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||
proposal_id UUID NOT NULL REFERENCES agent_proposals(id) ON DELETE CASCADE,
|
||
dimension TEXT NOT NULL,
|
||
-- dimension values: accuracy | relevance | completeness | policy_alignment
|
||
score NUMERIC NOT NULL CHECK (score BETWEEN 0 AND 1),
|
||
explanation TEXT,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
|
||
);
|
||
|
||
CREATE INDEX confidence_annotations_proposal_id_idx ON confidence_annotations (proposal_id);
|
||
```
|
||
|
||
- `agent_proposals.status` transitions: `pending → accepted | rejected | superseded`
|
||
- `agent_review_records` has UNIQUE on `proposal_id` — one review per proposal
|
||
- Add `IHP_ANTHROPIC_API_KEY` to CLAUDE.md required env vars table
|
||
|
||
**Exit criteria:** `migrate` runs cleanly; all Phase 5 types available in GHCi.
|
||
|
||
---
|
||
|
||
### T02 — AgentProposalsController and views
|
||
|
||
```task
|
||
id: IHUB-WP-0005-T02
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "5a9b9d51-dcdf-4dad-b449-08a7091e4563"
|
||
```
|
||
|
||
1. Scaffold `AgentProposalsController`
|
||
2. Actions: `index`, `show`, `AcceptProposalAction`, `RejectProposalAction`
|
||
(no update/delete — proposals are immutable audit artifacts)
|
||
3. Index: table filterable by `proposal_type` and `status`:
|
||
- type badge (color per type), source widget name, confidence bar, status,
|
||
created_at
|
||
4. Show: content panel + confidence dimension breakdown + review form
|
||
(decision select + notes textarea) + attribution footer (model_ref, created_at)
|
||
5. After accept/reject: redirect to proposal show with success message
|
||
6. Accept/reject is idempotent — if review record already exists, redirect
|
||
with "Already reviewed" message
|
||
7. Add nav link "Agent" to global nav in `FrontController.hs`
|
||
|
||
**Proposal type colors:**
|
||
- `summary` → blue
|
||
- `requirement_draft` → indigo
|
||
- `duplicate_flag` → orange
|
||
- `policy_flag` → red
|
||
- `impl_proposal` → green
|
||
|
||
**Exit criteria:** Proposals can be listed, viewed, accepted, and rejected;
|
||
review record created correctly; idempotent guard works.
|
||
|
||
---
|
||
|
||
### T03 — Cluster summarization via Claude API
|
||
|
||
```task
|
||
id: IHUB-WP-0005-T03
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "630d8d95-a009-4406-82e2-27f62fabcd3c"
|
||
```
|
||
|
||
1. `SummarizeClusterAction { widgetId }` (POST from widget show page)
|
||
2. Fetch last 20 annotations + annotation threads for the widget
|
||
3. Build prompt:
|
||
- **System:** "You are a distillation assistant for a governed interaction
|
||
hub. Summarize the following user feedback cluster into a concise,
|
||
actionable summary (2–4 sentences). Be factual and neutral."
|
||
- **User:** formatted annotation text
|
||
4. Call Anthropic Messages API (`claude-sonnet-4-6`, `max_tokens=300`)
|
||
5. Create `AgentProposal`:
|
||
- `proposal_type = "summary"`
|
||
- `source_widget_id = widgetId`
|
||
- `model_ref = "claude-sonnet-4-6"`
|
||
- `content = response text`
|
||
- `confidence = NULL` (summaries have no numeric confidence)
|
||
6. On widget show page: "Summarize Feedback" button → POST; after POST
|
||
redirect back, show latest summary proposal in a collapsible panel
|
||
7. HTTP client: use `http-conduit` + `aeson`; API key from
|
||
`IHP_ANTHROPIC_API_KEY` env var (error if absent)
|
||
8. Handle API errors gracefully — set error flash, redirect back
|
||
|
||
**Exit criteria:** Summary proposal created and visible on widget show page;
|
||
API errors produce a user-visible error message (not a 500).
|
||
|
||
---
|
||
|
||
### T04 — AI-drafted requirement candidate
|
||
|
||
```task
|
||
id: IHUB-WP-0005-T04
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "4c9d23f7-1744-48c8-90b5-71854d9b7daf"
|
||
```
|
||
|
||
1. `DraftRequirementAction { widgetId }` (POST from widget show page)
|
||
2. Fetch last 20 annotations for the widget
|
||
3. Build prompt:
|
||
- **System:** "You are a requirements analyst. Given these friction
|
||
annotations, draft a single structured requirement candidate. Respond
|
||
with JSON: {\"title\": \"...\", \"description\": \"...\"}."
|
||
- **User:** formatted annotation text
|
||
4. Parse JSON response; create `AgentProposal`:
|
||
- `proposal_type = "requirement_draft"`
|
||
- `content = raw JSON string from response`
|
||
5. On `AcceptProposalAction` for a `requirement_draft` proposal:
|
||
- Parse `content` as JSON
|
||
- Create `RequirementCandidate`:
|
||
- `title` and `description` from parsed JSON
|
||
- `source_widget_id = proposal.source_widget_id`
|
||
- `category = "friction"`
|
||
- `status = "open"`
|
||
- Update proposal `status = "accepted"`
|
||
- Create `AgentReviewRecord` (decision = "accepted")
|
||
- Set success flash: "Requirement candidate created from AI draft"
|
||
6. "Draft Requirement" button on widget show page (only when ≥ 3 annotations)
|
||
|
||
**Exit criteria:** Draft proposal created; acceptance creates a
|
||
`RequirementCandidate`; review record present; no candidate created without
|
||
human acceptance.
|
||
|
||
---
|
||
|
||
### T05 — Duplicate candidate detection
|
||
|
||
```task
|
||
id: IHUB-WP-0005-T05
|
||
status: done
|
||
priority: medium
|
||
state_hub_task_id: "969b7c7f-c3ba-4892-a1d0-faedf536d1c6"
|
||
```
|
||
|
||
1. `DetectDuplicatesAction { requirementCandidateId }` (POST from candidate
|
||
show page)
|
||
2. Fetch the candidate + all other `RequirementCandidate` records
|
||
3. Build prompt:
|
||
- **System:** "You are a deduplication assistant. Given a target candidate
|
||
and a list of existing candidates, identify likely duplicates. Respond
|
||
with JSON: {\"duplicates\": [{\"id\": \"uuid\", \"reason\": \"...\"}]}."
|
||
- **User:** target candidate + list of candidates (id, title, description)
|
||
4. Parse response; create `AgentProposal`:
|
||
- `proposal_type = "duplicate_flag"`
|
||
- `source_candidate_id = requirementCandidateId`
|
||
- `content = JSON string of duplicates array`
|
||
5. On candidate show page: "Check Duplicates" button → POST; show "Possible
|
||
Duplicates" panel with links to flagged candidates and rationale
|
||
6. Informational only — no automated merging or status changes
|
||
|
||
**Exit criteria:** Duplicate proposal created; flagged candidates rendered as
|
||
links on candidate show page; empty duplicates array handled gracefully.
|
||
|
||
---
|
||
|
||
### T06 — Policy-sensitive issue detection
|
||
|
||
```task
|
||
id: IHUB-WP-0005-T06
|
||
status: done
|
||
priority: medium
|
||
state_hub_task_id: "475290e0-7842-4336-a57c-04fa62652094"
|
||
```
|
||
|
||
1. `DetectPolicySensitivityAction { requirementCandidateId }` (POST from
|
||
candidate show page)
|
||
2. Fetch candidate + widget `policy_scope` + existing `PolicyReference`
|
||
constraint notes for linked decisions
|
||
3. Build prompt:
|
||
- **System:** "You are a policy compliance assistant. Analyse this
|
||
requirement candidate for potential policy concerns. Valid scopes:
|
||
internal, external, regulatory, contractual, architectural. Respond
|
||
with JSON: {\"concerns\": [{\"scope\": \"...\", \"note\": \"...\"}],
|
||
\"severity\": \"low|medium|high\"}."
|
||
- **User:** candidate title/description + policy context
|
||
4. Create `AgentProposal`:
|
||
- `proposal_type = "policy_flag"`
|
||
- `content = JSON string`
|
||
- `confidence = severity mapped to numeric (low=0.3, medium=0.6, high=0.9)`
|
||
5. Create one `ConfidenceAnnotation` per concern scope dimension
|
||
6. On candidate show page: "Policy Check" panel — amber badge if concerns,
|
||
green badge if clean
|
||
|
||
**Exit criteria:** Policy proposal created with confidence annotation;
|
||
concern severity rendered correctly; clean result (empty concerns) handled.
|
||
|
||
---
|
||
|
||
### T07 — Implementation path proposal
|
||
|
||
```task
|
||
id: IHUB-WP-0005-T07
|
||
status: done
|
||
priority: medium
|
||
state_hub_task_id: "7ee1274e-fa1b-4ae8-a360-4abafc1773f0"
|
||
```
|
||
|
||
1. `ProposeImplementationAction { decisionRecordId }` (POST from decision
|
||
show page)
|
||
2. Fetch decision + linked requirement + existing `ImplementationChangeReference`
|
||
records
|
||
3. Build prompt:
|
||
- **System:** "You are a traceability-aware implementation analyst. Propose
|
||
1–3 concrete implementation paths for this decision. Each path should
|
||
include a work_item_ref (e.g. PROJ-123), a system (github|linear|jira),
|
||
and a rationale. Respond with JSON: {\"proposals\": [{\"work_item_ref\":
|
||
\"...\", \"system\": \"...\", \"rationale\": \"...\"}]}."
|
||
- **User:** decision title, rationale, outcome, requirement description,
|
||
existing impl refs
|
||
4. Create `AgentProposal`:
|
||
- `proposal_type = "impl_proposal"`
|
||
- `source_decision_id = decisionRecordId`
|
||
- `content = JSON string`
|
||
5. On decision show page: "Propose Implementation" button; accepted proposals
|
||
pre-fill the AddImplementationRef form (first proposal in the array)
|
||
6. Surface as collapsed "AI Suggestions" panel on decision show page
|
||
|
||
**Exit criteria:** Impl proposal created; acceptance pre-fills the impl ref
|
||
form; multiple proposal paths rendered clearly.
|
||
|
||
---
|
||
|
||
### T08 — Agent attribution and audit dashboard (AutoRefresh)
|
||
|
||
```task
|
||
id: IHUB-WP-0005-T08
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "53b58abb-cb50-4985-a1c0-b05da17dfc3f"
|
||
```
|
||
|
||
1. Add `AgentAuditDashboardAction { hubId }` to `HubsController` wrapped
|
||
with `autoRefresh do`
|
||
2. Dashboard panels:
|
||
- **KPI row**: total proposals / pending / acceptance rate / rejection rate
|
||
- **Proposals by type**: count breakdown per `proposal_type`
|
||
- **Unreviewed queue**: proposals with `status = "pending"`, oldest first,
|
||
with "Review" links
|
||
- **Recent proposals** (last 20): type badge, source widget, status,
|
||
confidence, age
|
||
- **Attribution log**: `model_ref × proposal_type` count matrix
|
||
3. Link from hub Show page alongside Triage / Governance / Antifragility
|
||
4. Add "Agent" link to global nav
|
||
|
||
**Exit criteria:** Dashboard live-updates on proposal/review changes. All five
|
||
panels render with correct data.
|
||
|
||
---
|
||
|
||
### T09 — Phase 5 gate: tests, consistency, docs
|
||
|
||
```task
|
||
id: IHUB-WP-0005-T09
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "c7b8aef0-b241-4038-99c0-494c45f226a6"
|
||
```
|
||
|
||
1. **Integration tests** (`Test/`):
|
||
- AgentProposal create + fetch (all fields)
|
||
- AcceptProposalAction for `requirement_draft` → creates `RequirementCandidate`
|
||
- RejectProposalAction → sets `status = "rejected"`, creates review record
|
||
- Review record idempotency (second accept/reject → "Already reviewed")
|
||
- ConfidenceAnnotation create + link to proposal
|
||
- Duplicate detection: proposal with empty duplicates array
|
||
- Agent audit dashboard data fetch: compiles and returns correct counts
|
||
2. **Consistency sync** via State Hub MCP:
|
||
`check_repo_consistency(repo_slug="inter-hub", fix=True)`
|
||
3. **Documentation updates:**
|
||
- Update `SCOPE.md` current state section: Phase 5 complete
|
||
- Write `docs/phase5-summary.md`: what was built, governance constraints
|
||
upheld, known limitations, Phase 6 readiness
|
||
4. **Smoke test checklist:**
|
||
- Summarize feedback cluster on a widget with annotations
|
||
- Draft a requirement from the summary
|
||
- Accept the draft → verify RequirementCandidate created
|
||
- Check duplicates on a candidate
|
||
- Run policy check on a candidate
|
||
- Confirm agent audit dashboard shows all panels
|
||
|
||
**Exit criteria:** All tests pass; consistency sync reports no errors; smoke
|
||
test completed; SCOPE.md updated.
|
||
|
||
---
|
||
|
||
## Phase 5 Dependencies
|
||
|
||
- Phase 4 schema stable (Phase 4 tables needed as context for impl proposals)
|
||
- `http-conduit` and `aeson` available (already in IHP's Haskell ecosystem)
|
||
- `IHP_ANTHROPIC_API_KEY` set in environment
|
||
- Schema (T01) before all controller work (T02–T08)
|
||
- `AgentProposalsController` (T02) before all agent action tasks (T03–T07)
|
||
- All feature tasks (T01–T08) before gate (T09)
|
||
|
||
## Notes
|
||
|
||
- **All AI outputs are attributed.** `model_ref` is recorded on every
|
||
`AgentProposal`. Reviewers see exactly which model produced the output.
|
||
- **No silent promotion.** A `requirement_draft` proposal only becomes a
|
||
`RequirementCandidate` when a human explicitly accepts it via
|
||
`AcceptProposalAction`. The controller enforces this.
|
||
- **Review record is idempotent.** UNIQUE constraint on
|
||
`agent_review_records.proposal_id`. Second accept/reject redirects with
|
||
"Already reviewed" message.
|
||
- **API errors do not crash.** All Claude API calls are wrapped in error
|
||
handling; failures produce a user-visible flash message and redirect.
|
||
- **Confidence is optional for summaries.** `AgentProposal.confidence` is
|
||
nullable — summary proposals do not produce a numeric confidence score.
|
||
Policy flag proposals derive confidence from severity (low/medium/high).
|
||
- **No ML infrastructure.** All intelligence is delegated to the Anthropic
|
||
API. Phase 5 adds no local model serving, no embeddings storage.
|