generated from coulomb/repo-seed
chore(WP-0012): create Phase 11 workplan — Hub Registry and Widget Marketplace
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
647
workplans/IHUB-WP-0012-ihf-phase11-advanced-ai-federation.md
Normal file
647
workplans/IHUB-WP-0012-ihf-phase11-advanced-ai-federation.md
Normal file
@@ -0,0 +1,647 @@
|
||||
---
|
||||
id: IHUB-WP-0012
|
||||
type: workplan
|
||||
title: "IHF Phase 11 — Advanced AI Federation"
|
||||
domain: inter_hub
|
||||
repo: inter-hub
|
||||
status: todo
|
||||
owner: custodian
|
||||
topic_slug: inter_hub
|
||||
created: "2026-04-01"
|
||||
updated: "2026-04-01"
|
||||
state_hub_sync: done
|
||||
state_hub_workstream_id: "b6d8058b-b786-4cb8-81d4-19067b2684f8"
|
||||
---
|
||||
|
||||
# IHF Phase 11 — Advanced AI Federation
|
||||
|
||||
## Goal
|
||||
|
||||
Move AI assistance from single-model, single-agent operation (Phase 5) to a
|
||||
federated, multi-model, multi-agent architecture. Phase 11 introduces agent
|
||||
specialisation, model routing, inter-agent delegation, collective proposals,
|
||||
and AI governance policies — while keeping humans firmly in control of final
|
||||
decisions.
|
||||
|
||||
## Background
|
||||
|
||||
Phases 1–10 and IHUB-WP-0012 entry gates are satisfied:
|
||||
|
||||
- Phase 5 `AgentProposal`, `AgentReviewRecord`, `ConfidenceAnnotation`
|
||||
operational ✓
|
||||
- `HubCapabilityManifest` and type registries in place (governance policies
|
||||
attach per-hub) ✓
|
||||
- `/api/v2/` surface live (agent registry will expose an API endpoint) ✓
|
||||
- GAAF scorecard at 3.56 (Strong) ✓
|
||||
- `llm-connect` available at `~/llm-connect` as a Python subprocess library ✓
|
||||
|
||||
Reference: `specs/InteractionHubFrameworkSpecification_v0.2.md` §Phase 11.
|
||||
|
||||
## llm-connect Integration Architecture
|
||||
|
||||
**llm-connect** (`~/llm-connect`) is a Python library with pluggable LLM
|
||||
adapters for OpenRouter, Gemini, OpenAI, and Claude Code CLI. It is not a
|
||||
Haskell library; the integration is via a thin **Python subprocess bridge**.
|
||||
|
||||
### Bridge pattern
|
||||
|
||||
```
|
||||
IHP controller action
|
||||
→ AgentBridge.callLlmBridge (Haskell)
|
||||
→ System.Process: python3 scripts/llm_bridge.py
|
||||
→ llm_connect.create_adapter(provider, model, api_key, system_prompt)
|
||||
→ adapter.execute_prompt(prompt, RunConfig(...))
|
||||
→ JSON stdout: {content, model, tokens_in, tokens_out, finish_reason}
|
||||
← AgentResponse | AgentError
|
||||
```
|
||||
|
||||
`scripts/llm_bridge.py` is the sole integration seam. Each `AgentRegistration`
|
||||
record stores the provider, model name, and system prompt that are passed to the
|
||||
bridge. The API key is resolved from environment variables at bridge call time
|
||||
(following llm-connect's key resolution order: env var → key file → error).
|
||||
|
||||
### Supported providers via llm-connect
|
||||
|
||||
| Provider | llm-connect adapter | Notes |
|
||||
|---|---|---|
|
||||
| `openrouter` | `OpenRouterAdapter` | Multi-model routing; recommended for production |
|
||||
| `gemini` | `GeminiAdapter` | Free tier available; Google AI Studio key |
|
||||
| `openai` | `OpenAIAdapter` | GPT-4o and family |
|
||||
| `claude-code` | `ClaudeCodeAdapter` | Shells out to `claude --print` CLI |
|
||||
|
||||
### Known llm-connect limitations (feature requests raised in T10)
|
||||
|
||||
| Gap | Impact on Phase 11 | Feature request |
|
||||
|---|---|---|
|
||||
| No HTTP serve mode | Process spawn overhead on every agent call | FR-1: `--serve` mode (local JSON-RPC HTTP server) |
|
||||
| No routing policy API | ModelRoutingPolicy implemented in Haskell only | FR-2: `RoutingPolicy` class for declarative provider/model selection |
|
||||
| Synchronous only | Collective proposals invoke agents sequentially | FR-3: `async_execute_prompt()` for concurrent execution |
|
||||
| No token budget ledger | Delegation budget is checked by Haskell, not enforced by bridge | FR-4: `BudgetTracker` that spans a delegation chain |
|
||||
|
||||
These limitations do not block Phase 11 — they affect performance and
|
||||
architectural elegance. The feature requests are filed against llm-connect as
|
||||
a separate deliverable (T10).
|
||||
|
||||
## GAAF Architectural Constraints
|
||||
|
||||
1. `agent_registrations.trust_level` must carry a CHECK constraint
|
||||
(`advisory`, `elevated`, `autonomous`) — no bare TEXT discriminator.
|
||||
2. `agent_delegations.status` must carry a CHECK constraint
|
||||
(`pending`, `completed`, `failed`, `cancelled`).
|
||||
3. `collective_proposals.consensus_status` must carry a CHECK constraint
|
||||
(`pending`, `consensus`, `divergent`).
|
||||
4. `ai_governance_policies.allowed_actions` is a JSONB array — validated at
|
||||
the controller layer (each element must be one of: `read`, `propose`,
|
||||
`delegate`, `auto_apply`).
|
||||
5. Core tables remain frozen — `agent_proposals` gains `agent_registration_id`
|
||||
and token columns via ALTER TABLE, with a corresponding update to
|
||||
`/contracts/core/`.
|
||||
6. Append-only invariant on `interaction_events` and `outcome_signals`
|
||||
is permanent and unaffected by this phase.
|
||||
|
||||
## Data Artifacts Introduced
|
||||
|
||||
`AgentRegistration`, `ModelRoutingPolicy`, `AgentDelegation`,
|
||||
`CollectiveProposal`, `CollectiveProposalContribution`,
|
||||
`AiGovernancePolicy`, `AgentPerformanceRecord`
|
||||
|
||||
### Schema additions
|
||||
|
||||
```sql
|
||||
-- agent_registrations: named, versioned AI agents backed by llm-connect providers
|
||||
CREATE TABLE agent_registrations (
|
||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||
hub_id UUID NOT NULL REFERENCES hubs(id),
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
provider TEXT NOT NULL, -- openrouter | gemini | openai | claude-code
|
||||
model_name TEXT NOT NULL,
|
||||
trust_level TEXT NOT NULL DEFAULT 'advisory',
|
||||
capabilities JSONB NOT NULL DEFAULT '[]',
|
||||
system_prompt TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
CHECK (trust_level IN ('advisory', 'elevated', 'autonomous'))
|
||||
);
|
||||
|
||||
-- model_routing_policies: task_type → agent selection rules per hub
|
||||
CREATE TABLE model_routing_policies (
|
||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||
hub_id UUID NOT NULL REFERENCES hubs(id),
|
||||
task_type TEXT NOT NULL,
|
||||
agent_registration_id UUID NOT NULL REFERENCES agent_registrations(id),
|
||||
priority INTEGER NOT NULL DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
UNIQUE (hub_id, task_type, priority)
|
||||
);
|
||||
|
||||
-- agent_delegations: auditable inter-agent subtask delegation records
|
||||
CREATE TABLE agent_delegations (
|
||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||
delegating_agent_id UUID NOT NULL REFERENCES agent_registrations(id),
|
||||
receiving_agent_id UUID NOT NULL REFERENCES agent_registrations(id),
|
||||
parent_proposal_id UUID REFERENCES agent_proposals(id),
|
||||
scope TEXT NOT NULL,
|
||||
token_budget INTEGER NOT NULL DEFAULT 1000,
|
||||
tokens_used INTEGER,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
result JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
completed_at TIMESTAMP WITH TIME ZONE,
|
||||
CHECK (status IN ('pending', 'completed', 'failed', 'cancelled'))
|
||||
);
|
||||
|
||||
-- collective_proposals: multi-agent proposals with attribution
|
||||
CREATE TABLE collective_proposals (
|
||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
summary TEXT,
|
||||
task_type TEXT NOT NULL,
|
||||
consensus_status TEXT NOT NULL DEFAULT 'pending',
|
||||
final_content JSONB,
|
||||
source_widget_id UUID REFERENCES widgets(id),
|
||||
source_candidate_id UUID REFERENCES requirement_candidates(id),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
CHECK (consensus_status IN ('pending', 'consensus', 'divergent'))
|
||||
);
|
||||
|
||||
-- collective_proposal_contributions: per-agent contribution records
|
||||
CREATE TABLE collective_proposal_contributions (
|
||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||
collective_proposal_id UUID NOT NULL REFERENCES collective_proposals(id),
|
||||
agent_registration_id UUID NOT NULL REFERENCES agent_registrations(id),
|
||||
content JSONB NOT NULL,
|
||||
tokens_in INTEGER,
|
||||
tokens_out INTEGER,
|
||||
model_used TEXT,
|
||||
contributed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
|
||||
);
|
||||
|
||||
-- ai_governance_policies: per-hub rules controlling agent scope
|
||||
CREATE TABLE ai_governance_policies (
|
||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||
hub_id UUID NOT NULL REFERENCES hubs(id),
|
||||
agent_registration_id UUID NOT NULL REFERENCES agent_registrations(id),
|
||||
artifact_type TEXT NOT NULL,
|
||||
allowed_actions JSONB NOT NULL DEFAULT '["read"]',
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
|
||||
);
|
||||
|
||||
-- agent_performance_records: periodic snapshots of per-agent metrics
|
||||
CREATE TABLE agent_performance_records (
|
||||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
|
||||
agent_registration_id UUID NOT NULL REFERENCES agent_registrations(id),
|
||||
hub_id UUID NOT NULL REFERENCES hubs(id),
|
||||
period_start TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
period_end TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
proposals_generated INTEGER NOT NULL DEFAULT 0,
|
||||
proposals_accepted INTEGER NOT NULL DEFAULT 0,
|
||||
proposals_rejected INTEGER NOT NULL DEFAULT 0,
|
||||
proposals_revised INTEGER NOT NULL DEFAULT 0,
|
||||
mean_confidence DOUBLE PRECISION,
|
||||
calibration_score DOUBLE PRECISION,
|
||||
computed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
|
||||
);
|
||||
|
||||
-- Extend agent_proposals with agent_registration_id and token tracking
|
||||
ALTER TABLE agent_proposals
|
||||
ADD COLUMN agent_registration_id UUID REFERENCES agent_registrations(id),
|
||||
ADD COLUMN tokens_in INTEGER,
|
||||
ADD COLUMN tokens_out INTEGER;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tasks
|
||||
|
||||
### T01 — Schema: all Phase 11 tables + migration
|
||||
|
||||
```task
|
||||
id: IHUB-WP-0012-T01
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "d86574f0-f0a4-4217-b4de-d020442de7e4"
|
||||
```
|
||||
|
||||
Add all Phase 11 tables to `Application/Schema.sql` and write migration
|
||||
`Application/Migration/<timestamp>-ihf-phase11-ai-federation.sql`.
|
||||
|
||||
Includes the ALTER TABLE on `agent_proposals` to add `agent_registration_id`,
|
||||
`tokens_in`, `tokens_out`. Run `migrate` after writing.
|
||||
|
||||
---
|
||||
|
||||
### T02 — llm-connect bridge: scripts/llm_bridge.py + Application/Helper/AgentBridge.hs
|
||||
|
||||
```task
|
||||
id: IHUB-WP-0012-T02
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "404d1a89-aae2-49ea-a565-90261001a633"
|
||||
```
|
||||
|
||||
**`scripts/llm_bridge.py`** — thin wrapper around llm-connect:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# Usage: echo '{"provider":"openrouter","model":"...","prompt":"..."}' | python3 scripts/llm_bridge.py
|
||||
import sys, json, os
|
||||
sys.path.insert(0, os.path.expanduser("~/llm-connect"))
|
||||
from llm_connect import create_adapter, RunConfig
|
||||
from llm_connect.exceptions import LLMError
|
||||
|
||||
def main():
|
||||
req = json.load(sys.stdin)
|
||||
try:
|
||||
adapter = create_adapter(
|
||||
provider=req.get("provider", "openrouter"),
|
||||
model=req.get("model"),
|
||||
api_key=req.get("api_key"),
|
||||
system_prompt=req.get("systemPrompt"),
|
||||
)
|
||||
config = RunConfig(
|
||||
model_name=req.get("model", ""),
|
||||
temperature=req.get("temperature", 0.7),
|
||||
max_tokens=req.get("maxTokens", 2000),
|
||||
)
|
||||
resp = adapter.execute_prompt(req["prompt"], config)
|
||||
print(json.dumps({
|
||||
"content": resp.content,
|
||||
"model": resp.model,
|
||||
"tokensIn": resp.usage.get("prompt_tokens", 0),
|
||||
"tokensOut": resp.usage.get("completion_tokens", 0),
|
||||
"finishReason": resp.finish_reason,
|
||||
}))
|
||||
except LLMError as e:
|
||||
json.dump({"error": str(e), "errorType": type(e).__name__}, sys.stdout)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
**`Application/Helper/AgentBridge.hs`** — Haskell wrapper:
|
||||
|
||||
```haskell
|
||||
module Application.Helper.AgentBridge where
|
||||
|
||||
import IHP.Prelude
|
||||
import Data.Aeson (object, (.=), encode, decode)
|
||||
import qualified Data.ByteString.Lazy as LBS
|
||||
import System.Process (readProcessWithExitCode)
|
||||
import System.Exit (ExitCode(..))
|
||||
import Generated.Types
|
||||
|
||||
data BridgeRequest = BridgeRequest
|
||||
{ provider :: !Text
|
||||
, model :: !Text
|
||||
, systemPrompt :: !(Maybe Text)
|
||||
, prompt :: !Text
|
||||
, maxTokens :: !Int
|
||||
, temperature :: !Double
|
||||
}
|
||||
|
||||
data BridgeResponse = BridgeResponse
|
||||
{ content :: !Text
|
||||
, modelUsed :: !Text
|
||||
, tokensIn :: !Int
|
||||
, tokensOut :: !Int
|
||||
, finishReason :: !Text
|
||||
} deriving (Show)
|
||||
|
||||
data BridgeError = BridgeError
|
||||
{ errorMessage :: !Text
|
||||
, errorType :: !Text
|
||||
} deriving (Show)
|
||||
|
||||
callBridge :: BridgeRequest -> IO (Either BridgeError BridgeResponse)
|
||||
callBridge req = do
|
||||
let input = cs . LBS.toStrict . encode $ object
|
||||
[ "provider" .= req.provider
|
||||
, "model" .= req.model
|
||||
, "systemPrompt" .= req.systemPrompt
|
||||
, "prompt" .= req.prompt
|
||||
, "maxTokens" .= req.maxTokens
|
||||
, "temperature" .= req.temperature
|
||||
]
|
||||
(exitCode, stdout, stderr) <-
|
||||
readProcessWithExitCode "python3" ["scripts/llm_bridge.py"] input
|
||||
let outBytes = LBS.fromStrict (cs stdout)
|
||||
case exitCode of
|
||||
ExitSuccess -> case decode outBytes of
|
||||
Just v -> pure (Right v)
|
||||
Nothing -> pure (Left (BridgeError "Unparseable bridge output" "ParseError"))
|
||||
ExitFailure _ -> case decode outBytes of
|
||||
Just v -> pure (Left v)
|
||||
Nothing -> pure (Left (BridgeError (cs stderr) "BridgeError"))
|
||||
|
||||
-- | Call bridge using an AgentRegistration record.
|
||||
callAgent :: AgentRegistration -> Text -> IO (Either BridgeError BridgeResponse)
|
||||
callAgent agent prompt =
|
||||
callBridge BridgeRequest
|
||||
{ provider = agent.provider
|
||||
, model = agent.modelName
|
||||
, systemPrompt = agent.systemPrompt
|
||||
, prompt
|
||||
, maxTokens = 2000
|
||||
, temperature = 0.7
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### T03 — Agent Registry: AgentRegistration CRUD + capability/trust UI
|
||||
|
||||
```task
|
||||
id: IHUB-WP-0012-T03
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "3ae5b3b1-e644-444d-ae72-8eef07318c49"
|
||||
```
|
||||
|
||||
**Controller:** `Web/Controller/AgentRegistrations.hs`
|
||||
|
||||
Actions:
|
||||
- `AgentRegistrationsAction` — list all agents with hub name, trust level,
|
||||
provider, active/inactive
|
||||
- `ShowAgentRegistrationAction { agentRegistrationId }` — detail with routing
|
||||
policies, recent proposals, performance record
|
||||
- `NewAgentRegistrationAction` / `CreateAgentRegistrationAction` — form with
|
||||
hub selector, provider dropdown (openrouter / gemini / openai / claude-code),
|
||||
model name, trust level selector, capabilities multi-tag input, system prompt
|
||||
textarea
|
||||
- `EditAgentRegistrationAction` / `UpdateAgentRegistrationAction`
|
||||
- `DeactivateAgentAction { agentRegistrationId }` — set `is_active = False`
|
||||
|
||||
Provider dropdown options map to the four llm-connect adapters.
|
||||
|
||||
**Views:** `Web/View/AgentRegistrations/{Index,Show,New,Edit}.hs`
|
||||
|
||||
Add nav link ("Agents") next to existing "Agent" proposals link.
|
||||
|
||||
---
|
||||
|
||||
### T04 — Model routing: ModelRoutingPolicy CRUD + Application/Helper/ModelRouter.hs
|
||||
|
||||
```task
|
||||
id: IHUB-WP-0012-T04
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "20bcff74-1923-4c11-95ef-2b37a7a10dd8"
|
||||
```
|
||||
|
||||
**Controller:** `Web/Controller/ModelRoutingPolicies.hs`
|
||||
|
||||
Actions:
|
||||
- `ModelRoutingPoliciesAction` — list by hub, grouped by task_type
|
||||
- `NewModelRoutingPolicyAction` / `CreateModelRoutingPolicyAction` — hub,
|
||||
task_type selector (requirement_draft / triage / synthesis / policy_check /
|
||||
implementation), agent selector, priority
|
||||
- `DeleteModelRoutingPolicyAction { modelRoutingPolicyId }`
|
||||
|
||||
**`Application/Helper/ModelRouter.hs`:**
|
||||
|
||||
```haskell
|
||||
module Application.Helper.ModelRouter where
|
||||
|
||||
import IHP.Prelude
|
||||
import IHP.ControllerPrelude
|
||||
import Generated.Types
|
||||
|
||||
-- | Resolve the AgentRegistration to use for a hub + task type.
|
||||
-- Selects the highest-priority active policy for the hub.
|
||||
-- Returns Nothing if no policy matches (caller should fall back or error).
|
||||
resolveAgent ::
|
||||
(?modelContext :: ModelContext) =>
|
||||
Id Hub -> Text -> IO (Maybe AgentRegistration)
|
||||
resolveAgent hubId taskType = do
|
||||
mPolicy <- sqlQuery
|
||||
"SELECT mrp.agent_registration_id \
|
||||
\ FROM model_routing_policies mrp \
|
||||
\ WHERE mrp.hub_id = ? AND mrp.task_type = ? AND mrp.is_active = TRUE \
|
||||
\ ORDER BY mrp.priority DESC LIMIT 1"
|
||||
(hubId, taskType)
|
||||
case mPolicy of
|
||||
[Only agentId] -> fetchOneOrNothing agentId
|
||||
_ -> pure Nothing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### T05 — Agent invocation: route + invoke via bridge, replace Phase 5 hard-coded calls
|
||||
|
||||
```task
|
||||
id: IHUB-WP-0012-T05
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "45fec21a-47d3-4381-99bc-88dcf0f117cb"
|
||||
```
|
||||
|
||||
Update the four existing agent invocation points to use the routing + bridge:
|
||||
|
||||
1. `SummarizeClusterAction` in `Web/Controller/Widgets.hs`
|
||||
2. `DraftRequirementAction` in `Web/Controller/Widgets.hs`
|
||||
3. `ProposeImplementationAction` in `Web/Controller/DecisionRecords.hs`
|
||||
4. `DetectDuplicatesAction` in `Web/Controller/RequirementCandidates.hs`
|
||||
|
||||
**Pattern for each:**
|
||||
```haskell
|
||||
mAgent <- resolveAgent hubId "requirement_draft"
|
||||
case mAgent of
|
||||
Nothing -> setErrorMessage "No routing policy for this task type"
|
||||
Just agent -> do
|
||||
-- Check AI governance policy before invocation
|
||||
allowed <- checkGovernancePolicy hubId agent.id "requirement_candidate"
|
||||
if not allowed
|
||||
then do
|
||||
newRecord @AgentProposal
|
||||
|> set #status "blocked_by_policy"
|
||||
|> set #agentRegistrationId (Just agent.id)
|
||||
|> createRecord
|
||||
setErrorMessage "Blocked by AI governance policy"
|
||||
else do
|
||||
result <- callAgent agent prompt
|
||||
case result of
|
||||
Left err -> setErrorMessage err.errorMessage
|
||||
Right resp -> do
|
||||
newRecord @AgentProposal
|
||||
|> set #content resp.content
|
||||
|> set #agentRegistrationId (Just agent.id)
|
||||
|> set #tokensIn (Just resp.tokensIn)
|
||||
|> set #tokensOut (Just resp.tokensOut)
|
||||
|> createRecord
|
||||
```
|
||||
|
||||
`checkGovernancePolicy` defined in `Application/Helper/AgentBridge.hs`:
|
||||
queries `ai_governance_policies` for the hub + agent + artifact_type combination.
|
||||
|
||||
---
|
||||
|
||||
### T06 — Inter-agent delegation: AgentDelegation records + bounded sub-task invocation
|
||||
|
||||
```task
|
||||
id: IHUB-WP-0012-T06
|
||||
status: todo
|
||||
priority: medium
|
||||
state_hub_task_id: "9fe1c284-ecb7-4777-98f3-253224df704c"
|
||||
```
|
||||
|
||||
**Controller:** `Web/Controller/AgentDelegations.hs`
|
||||
|
||||
Actions:
|
||||
- `AgentDelegationsAction` — list delegations for a proposal
|
||||
- `ShowAgentDelegationAction { agentDelegationId }` — detail with result
|
||||
- `DelegateSubtaskAction { agentProposalId }` — form: pick receiving agent,
|
||||
scope description, token budget; on submit: create `AgentDelegation` record,
|
||||
call bridge with `maxTokens = delegation.tokenBudget`, store result +
|
||||
`tokensUsed`, set `status = completed | failed`
|
||||
|
||||
**Delegation tree on proposal Show page:** recursive query over
|
||||
`parent_proposal_id` to render the full provenance chain.
|
||||
|
||||
---
|
||||
|
||||
### T07 — Collective proposals: multi-agent attribution + consensus detection
|
||||
|
||||
```task
|
||||
id: IHUB-WP-0012-T07
|
||||
status: todo
|
||||
priority: medium
|
||||
state_hub_task_id: "37782cec-2d42-44b8-8bda-2049a7bf4898"
|
||||
```
|
||||
|
||||
**Controller:** `Web/Controller/CollectiveProposals.hs`
|
||||
|
||||
Actions:
|
||||
- `CollectiveProposalsAction` — list by hub
|
||||
- `ShowCollectiveProposalAction { collectiveProposalId }` — side-by-side
|
||||
agent contributions + consensus/divergent status + human review buttons
|
||||
- `CreateCollectiveProposalAction` — invokes all `is_active` agents for the
|
||||
task_type in `model_routing_policies` priority order; creates a
|
||||
`CollectiveProposalContribution` per agent; then runs a synthesis step
|
||||
(invoke the highest-priority agent with all contributions as context) to
|
||||
produce `final_content`
|
||||
|
||||
**Consensus detection:**
|
||||
```haskell
|
||||
detectConsensus :: [CollectiveProposalContribution] -> Text
|
||||
detectConsensus contribs =
|
||||
-- Simple heuristic: if all contributions contain the same top-level
|
||||
-- recommendation key, mark as consensus; otherwise divergent
|
||||
if allSameRecommendation contribs then "consensus" else "divergent"
|
||||
```
|
||||
|
||||
**Views:** `Web/View/CollectiveProposals/{Index,Show}.hs`
|
||||
|
||||
---
|
||||
|
||||
### T08 — AI governance policies: AiGovernancePolicy CRUD + enforcement at invocation
|
||||
|
||||
```task
|
||||
id: IHUB-WP-0012-T08
|
||||
status: todo
|
||||
priority: medium
|
||||
state_hub_task_id: "50d05787-9629-4f94-ac55-50a6e417f730"
|
||||
```
|
||||
|
||||
**Controller:** `Web/Controller/AiGovernancePolicies.hs`
|
||||
|
||||
Actions:
|
||||
- `AiGovernancePoliciesAction` — list by hub
|
||||
- `NewAiGovernancePolicyAction` / `CreateAiGovernancePolicyAction` — form:
|
||||
hub, agent, artifact_type, allowed_actions checkboxes (read / propose /
|
||||
delegate / auto_apply)
|
||||
- `ToggleAiGovernancePolicyAction { aiGovernancePolicyId }` — flip `is_active`
|
||||
|
||||
**`checkGovernancePolicy` in `Application/Helper/AgentBridge.hs`:**
|
||||
|
||||
```haskell
|
||||
checkGovernancePolicy ::
|
||||
(?modelContext :: ModelContext) =>
|
||||
Id Hub -> Id AgentRegistration -> Text -> IO Bool
|
||||
checkGovernancePolicy hubId agentId artifactType = do
|
||||
mPolicy <- query @AiGovernancePolicy
|
||||
|> filterWhere (#hubId, hubId)
|
||||
|> filterWhere (#agentRegistrationId, agentId)
|
||||
|> filterWhere (#artifactType, artifactType)
|
||||
|> filterWhere (#isActive, True)
|
||||
|> fetchOneOrNothing
|
||||
case mPolicy of
|
||||
Nothing -> pure True -- no policy = permit by default
|
||||
Just p -> pure ("propose" `elem` jsonArrayTexts p.allowedActions)
|
||||
```
|
||||
|
||||
**Governance compliance panel** added to existing `GovernanceDashboardAction`:
|
||||
count of blocked invocations in the last 7 days per hub.
|
||||
|
||||
---
|
||||
|
||||
### T09 — Agent performance dashboard
|
||||
|
||||
```task
|
||||
id: IHUB-WP-0012-T09
|
||||
status: todo
|
||||
priority: medium
|
||||
state_hub_task_id: "6ef63612-f913-4a13-b683-f44929cb1b2d"
|
||||
```
|
||||
|
||||
**Action added to `AgentRegistrationsController`:**
|
||||
`ComputeAgentPerformanceAction { agentRegistrationId }` — computes a 30-day
|
||||
snapshot and writes `AgentPerformanceRecord`:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE ap.status = 'accepted') AS accepted,
|
||||
COUNT(*) FILTER (WHERE ap.status = 'rejected') AS rejected,
|
||||
COUNT(*) FILTER (WHERE ap.status NOT IN ('accepted','rejected')) AS other,
|
||||
AVG(ca.score) AS mean_confidence
|
||||
FROM agent_proposals ap
|
||||
LEFT JOIN confidence_annotations ca ON ca.proposal_id = ap.id
|
||||
WHERE ap.agent_registration_id = ?
|
||||
AND ap.created_at >= NOW() - INTERVAL '30 days'
|
||||
```
|
||||
|
||||
Calibration score = Pearson correlation between `ca.score` and the binary
|
||||
accept/reject outcome.
|
||||
|
||||
**View:** `Web/View/AgentRegistrations/Performance.hs` — panel per agent with
|
||||
progress bars for acceptance/rejection ratio, mean confidence gauge,
|
||||
calibration indicator.
|
||||
|
||||
---
|
||||
|
||||
### T10 — llm-connect feature requests + scorecard + CLAUDE.md
|
||||
|
||||
```task
|
||||
id: IHUB-WP-0012-T10
|
||||
status: todo
|
||||
priority: medium
|
||||
state_hub_task_id: "41a64c6c-0ce8-4c63-9175-6b8d28143f81"
|
||||
```
|
||||
|
||||
**File feature requests against `~/llm-connect`** (as GitHub issues or local
|
||||
`FEATURE_REQUESTS.md` in that repo):
|
||||
|
||||
| # | Title | Why needed | Proposed API |
|
||||
|---|---|---|---|
|
||||
| FR-1 | HTTP/JSON-RPC serve mode | Avoid process spawn per call in production | `python -m llm_connect.server --port 9999`; IHP calls `POST localhost:9999/execute` |
|
||||
| FR-2 | RoutingPolicy class | Declarative provider/model selection beyond `model_name` in RunConfig | `RoutingPolicy(rules=[{task_type: "triage", prefer: [{provider: "openrouter", model: "claude-haiku-4-5"}], max_cost_per_1k: 0.5}])` |
|
||||
| FR-3 | `async_execute_prompt()` | Collective proposals need parallel agent invocation | Standard asyncio coroutine interface matching `execute_prompt` signature |
|
||||
| FR-4 | `BudgetTracker` for delegation chains | Token budget enforcement across nested delegations | `BudgetTracker(total=4000)` passed through `RunConfig`; raises `LLMBudgetExceededError` |
|
||||
|
||||
**ARCHITECTURE-LAYERS.md scorecard update:**
|
||||
- Functional: 3.4 → 3.6 (multi-agent federation formalises AI collaboration)
|
||||
- Extensions: 3.8 → 3.9 (agent registry exposes an API surface)
|
||||
- Target overall: ≥3.7
|
||||
|
||||
**CLAUDE.md:** move IHUB-WP-0012 to completed; set active → IHUB-WP-0013
|
||||
(Phase 12 — Platform Memory and Continuous Learning).
|
||||
|
||||
**Commit all changes** and mark workplan `status: done`.
|
||||
Reference in New Issue
Block a user