Adds bounded AI support to the IHF governance loop. All AI outputs are attributed (model_ref), reviewable (AgentReviewRecord), and reversible. No autonomous decisions; no silent requirement promotion. - T01: Schema — agent_proposals, agent_review_records, confidence_annotations (migration 1743379200) - T02: AgentProposalsController (index/show/accept/reject, idempotent review guard), global nav "Agent" link - T03: SummarizeClusterAction — Claude API cluster summary on widget show - T04: DraftRequirementAction — AI requirement draft; acceptance creates RequirementCandidate (human-gated) - T05: DetectDuplicatesAction — duplicate_flag proposal on candidate show - T06: DetectPolicySensitivityAction — policy_flag with ConfidenceAnnotations per concern scope - T07: ProposeImplementationAction — impl_proposal from decision show - T08: AgentAuditDashboardAction — autoRefresh; KPI row, unreviewed queue, recent proposals, attribution log matrix - T09: integration tests, SCOPE.md updated, phase5-summary.md, flake.nix adds http-conduit/aeson/string-conversions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
16 KiB
id, type, title, domain, repo, status, owner, topic_slug, created, updated, state_hub_workstream_id
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|---|
| IHUB-WP-0005 | workplan | IHF Phase 5 — Agent-Assisted Distillation and Suggestion | inter_hub | inter-hub | active | custodian | inter_hub | 2026-03-29 | 2026-03-29 | 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
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:
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.statustransitions:pending → accepted | rejected | supersededagent_review_recordshas UNIQUE onproposal_id— one review per proposal- Add
IHP_ANTHROPIC_API_KEYto CLAUDE.md required env vars table
Exit criteria: migrate runs cleanly; all Phase 5 types available in GHCi.
T02 — AgentProposalsController and views
id: IHUB-WP-0005-T02
status: done
priority: high
state_hub_task_id: "5a9b9d51-dcdf-4dad-b449-08a7091e4563"
- Scaffold
AgentProposalsController - Actions:
index,show,AcceptProposalAction,RejectProposalAction(no update/delete — proposals are immutable audit artifacts) - Index: table filterable by
proposal_typeandstatus:- type badge (color per type), source widget name, confidence bar, status, created_at
- Show: content panel + confidence dimension breakdown + review form (decision select + notes textarea) + attribution footer (model_ref, created_at)
- After accept/reject: redirect to proposal show with success message
- Accept/reject is idempotent — if review record already exists, redirect with "Already reviewed" message
- Add nav link "Agent" to global nav in
FrontController.hs
Proposal type colors:
summary→ bluerequirement_draft→ indigoduplicate_flag→ orangepolicy_flag→ redimpl_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
id: IHUB-WP-0005-T03
status: done
priority: high
state_hub_task_id: "630d8d95-a009-4406-82e2-27f62fabcd3c"
SummarizeClusterAction { widgetId }(POST from widget show page)- Fetch last 20 annotations + annotation threads for the widget
- 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
- Call Anthropic Messages API (
claude-sonnet-4-6,max_tokens=300) - Create
AgentProposal:proposal_type = "summary"source_widget_id = widgetIdmodel_ref = "claude-sonnet-4-6"content = response textconfidence = NULL(summaries have no numeric confidence)
- On widget show page: "Summarize Feedback" button → POST; after POST redirect back, show latest summary proposal in a collapsible panel
- HTTP client: use
http-conduit+aeson; API key fromIHP_ANTHROPIC_API_KEYenv var (error if absent) - 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
id: IHUB-WP-0005-T04
status: done
priority: high
state_hub_task_id: "4c9d23f7-1744-48c8-90b5-71854d9b7daf"
DraftRequirementAction { widgetId }(POST from widget show page)- Fetch last 20 annotations for the widget
- 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
- Parse JSON response; create
AgentProposal:proposal_type = "requirement_draft"content = raw JSON string from response
- On
AcceptProposalActionfor arequirement_draftproposal:- Parse
contentas JSON - Create
RequirementCandidate:titleanddescriptionfrom parsed JSONsource_widget_id = proposal.source_widget_idcategory = "friction"status = "open"
- Update proposal
status = "accepted" - Create
AgentReviewRecord(decision = "accepted") - Set success flash: "Requirement candidate created from AI draft"
- Parse
- "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
id: IHUB-WP-0005-T05
status: done
priority: medium
state_hub_task_id: "969b7c7f-c3ba-4892-a1d0-faedf536d1c6"
DetectDuplicatesAction { requirementCandidateId }(POST from candidate show page)- Fetch the candidate + all other
RequirementCandidaterecords - 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)
- Parse response; create
AgentProposal:proposal_type = "duplicate_flag"source_candidate_id = requirementCandidateIdcontent = JSON string of duplicates array
- On candidate show page: "Check Duplicates" button → POST; show "Possible Duplicates" panel with links to flagged candidates and rationale
- 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
id: IHUB-WP-0005-T06
status: done
priority: medium
state_hub_task_id: "475290e0-7842-4336-a57c-04fa62652094"
DetectPolicySensitivityAction { requirementCandidateId }(POST from candidate show page)- Fetch candidate + widget
policy_scope+ existingPolicyReferenceconstraint notes for linked decisions - 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
- Create
AgentProposal:proposal_type = "policy_flag"content = JSON stringconfidence = severity mapped to numeric (low=0.3, medium=0.6, high=0.9)
- Create one
ConfidenceAnnotationper concern scope dimension - 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
id: IHUB-WP-0005-T07
status: done
priority: medium
state_hub_task_id: "7ee1274e-fa1b-4ae8-a360-4abafc1773f0"
ProposeImplementationAction { decisionRecordId }(POST from decision show page)- Fetch decision + linked requirement + existing
ImplementationChangeReferencerecords - 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
- Create
AgentProposal:proposal_type = "impl_proposal"source_decision_id = decisionRecordIdcontent = JSON string
- On decision show page: "Propose Implementation" button; accepted proposals pre-fill the AddImplementationRef form (first proposal in the array)
- 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)
id: IHUB-WP-0005-T08
status: done
priority: high
state_hub_task_id: "53b58abb-cb50-4985-a1c0-b05da17dfc3f"
- Add
AgentAuditDashboardAction { hubId }toHubsControllerwrapped withautoRefresh do - 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_typecount matrix
- Link from hub Show page alongside Triage / Governance / Antifragility
- 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
id: IHUB-WP-0005-T09
status: todo
priority: high
state_hub_task_id: "c7b8aef0-b241-4038-99c0-494c45f226a6"
- Integration tests (
Test/):- AgentProposal create + fetch (all fields)
- AcceptProposalAction for
requirement_draft→ createsRequirementCandidate - 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
- Consistency sync via State Hub MCP:
check_repo_consistency(repo_slug="inter-hub", fix=True) - Documentation updates:
- Update
SCOPE.mdcurrent state section: Phase 5 complete - Write
docs/phase5-summary.md: what was built, governance constraints upheld, known limitations, Phase 6 readiness
- Update
- 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-conduitandaesonavailable (already in IHP's Haskell ecosystem)IHP_ANTHROPIC_API_KEYset 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_refis recorded on everyAgentProposal. Reviewers see exactly which model produced the output. - No silent promotion. A
requirement_draftproposal only becomes aRequirementCandidatewhen a human explicitly accepts it viaAcceptProposalAction. 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.confidenceis 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.