generated from coulomb/repo-seed
feat: integrate llm-connect FR-1/FR-3/FR-4 into IHF bridge
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
FR-3 (async_execute_prompt): CollectiveProposals now invokes all agents
concurrently via callAgentsBatch → single bridge subprocess with
asyncio.gather. Latency scales with slowest agent, not sum.
FR-4 (BudgetTracker): AgentDelegations passes tokenBudget to bridge;
llm-connect enforces it natively via BudgetTracker in RunConfig.
BudgetExceededError is a first-class BridgeError variant with total/
consumed/requested fields surfaced to the operator.
FR-1 (LLMServer passthrough): bridge accepts optional serverUrl field;
if present, calls POST {serverUrl}/execute instead of spawning a new
adapter. Infrastructure ready for hot-agent pre-warming (no schema
change required).
AgentBridge.hs: adds callAgentsBatch, callAgentWithBudget,
BudgetExceededError constructor, bridgeErrorMessage helper, defaultRequest,
requestToJson. All controllers updated to use bridgeErrorMessage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,16 @@
|
||||
module Web.Controller.AgentDelegations where
|
||||
|
||||
-- IHF Phase 11 — Advanced AI Federation (IHUB-WP-0012 T06)
|
||||
-- Updated: delegation token budget enforced natively by llm-connect BudgetTracker (FR-4).
|
||||
|
||||
import Web.Controller.Prelude
|
||||
import Web.View.AgentDelegations.Index
|
||||
import Web.View.AgentDelegations.Show
|
||||
import Application.Helper.AgentBridge (callBridge, BridgeRequest(..))
|
||||
import Application.Helper.AgentBridge
|
||||
( callAgentWithBudget
|
||||
, BridgeError(..)
|
||||
, bridgeErrorMessage
|
||||
)
|
||||
|
||||
instance Controller AgentDelegationsController where
|
||||
|
||||
@@ -44,24 +49,32 @@ instance Controller AgentDelegationsController where
|
||||
|> set #status "pending"
|
||||
|> createRecord
|
||||
|
||||
result <- liftIO $ callBridge BridgeRequest
|
||||
{ provider = receivingAgent.provider
|
||||
, model = receivingAgent.modelName
|
||||
, systemPrompt = receivingAgent.systemPrompt
|
||||
, prompt = scope
|
||||
, maxTokens = tokenBudget
|
||||
, temperature = 0.7
|
||||
}
|
||||
-- FR-4: token budget passed to bridge → llm-connect BudgetTracker enforces it
|
||||
-- natively, raising LLMBudgetExceededError if the call would exceed the cap.
|
||||
result <- liftIO $ callAgentWithBudget receivingAgent scope tokenBudget 0
|
||||
|
||||
now <- getCurrentTime
|
||||
case result of
|
||||
Left BudgetExceededError { errorMessage, budgetTotal, budgetConsumed, budgetRequested } -> do
|
||||
delegation
|
||||
|> set #status "failed"
|
||||
|> set #result (Just . A.toJSON $ A.object
|
||||
[ "error" A..= errorMessage
|
||||
, "budgetTotal" A..= budgetTotal
|
||||
, "budgetConsumed" A..= budgetConsumed
|
||||
, "budgetRequested" A..= budgetRequested
|
||||
])
|
||||
|> set #completedAt (Just now)
|
||||
|> updateRecord
|
||||
setErrorMessage ("Budget exceeded: requested " <> show budgetRequested
|
||||
<> " tokens but only " <> show (budgetTotal - budgetConsumed) <> " remain")
|
||||
Left err -> do
|
||||
delegation
|
||||
|> set #status "failed"
|
||||
|> set #result (Just . A.toJSON $ A.object ["error" A..= err.errorMessage])
|
||||
|> set #result (Just . A.toJSON $ A.object ["error" A..= bridgeErrorMessage err])
|
||||
|> set #completedAt (Just now)
|
||||
|> updateRecord
|
||||
setErrorMessage ("Delegation failed: " <> err.errorMessage)
|
||||
setErrorMessage ("Delegation failed: " <> bridgeErrorMessage err)
|
||||
Right resp -> do
|
||||
delegation
|
||||
|> set #status "completed"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
module Web.Controller.CollectiveProposals where
|
||||
|
||||
-- IHF Phase 11 — Advanced AI Federation (IHUB-WP-0012 T07)
|
||||
-- Updated: agents invoked concurrently via callAgentsBatch (FR-3 async).
|
||||
|
||||
import Web.Controller.Prelude
|
||||
import Web.View.CollectiveProposals.Index
|
||||
import Web.View.CollectiveProposals.Show
|
||||
import Application.Helper.AgentBridge (callAgent, BridgeResponse(..))
|
||||
import Application.Helper.AgentBridge (callAgent, callAgentsBatch, BridgeResponse(..))
|
||||
import Application.Helper.ModelRouter (resolveAllAgents)
|
||||
import Data.List (intercalate)
|
||||
|
||||
@@ -45,10 +46,15 @@ instance Controller CollectiveProposalsController where
|
||||
|> createRecord
|
||||
|
||||
agents <- resolveAllAgents hubId taskType
|
||||
contributions <- forM agents \agent -> do
|
||||
result <- liftIO $ callAgent agent prompt
|
||||
|
||||
-- FR-3: invoke all agents concurrently in a single bridge subprocess call
|
||||
-- instead of sequential forM. Latency now scales with the slowest agent,
|
||||
-- not the sum of all agents.
|
||||
results <- liftIO $ callAgentsBatch [(a, prompt) | a <- agents]
|
||||
|
||||
successContribs <- fmap catMaybes $ forM (zip agents results) \(agent, result) ->
|
||||
case result of
|
||||
Left err -> pure Nothing
|
||||
Left _ -> pure Nothing
|
||||
Right resp -> do
|
||||
contrib <- newRecord @CollectiveProposalContribution
|
||||
|> set #collectiveProposalId proposal.id
|
||||
@@ -60,22 +66,21 @@ instance Controller CollectiveProposalsController where
|
||||
|> createRecord
|
||||
pure (Just (contrib, resp))
|
||||
|
||||
let successContribs = catMaybes contributions
|
||||
consensusStatus <- if null successContribs
|
||||
then pure "divergent"
|
||||
then do
|
||||
proposal |> set #consensusStatus "divergent" |> updateRecord
|
||||
pure "divergent"
|
||||
else do
|
||||
let contribTexts = map (\(_, r) -> r.content) successContribs
|
||||
synthesisPrompt = "The following agents have independently proposed solutions. "
|
||||
<> "Synthesize a unified recommendation:\n\n"
|
||||
<> intercalate "\n---\n" contribTexts
|
||||
mSynthAgent <- resolveAllAgents hubId taskType >>= \case
|
||||
(a:_) -> pure (Just a)
|
||||
[] -> pure Nothing
|
||||
case mSynthAgent of
|
||||
Nothing -> do
|
||||
-- Synthesis uses the highest-priority agent (head of the list)
|
||||
case agents of
|
||||
[] -> do
|
||||
proposal |> set #consensusStatus "divergent" |> updateRecord
|
||||
pure "divergent"
|
||||
Just synthAgent -> do
|
||||
(synthAgent:_) -> do
|
||||
synthResult <- liftIO $ callAgent synthAgent synthesisPrompt
|
||||
case synthResult of
|
||||
Left _ -> do
|
||||
@@ -95,8 +100,7 @@ instance Controller CollectiveProposalsController where
|
||||
setSuccessMessage ("Collective proposal created (" <> consensusStatus <> ")")
|
||||
redirectTo ShowCollectiveProposalAction { collectiveProposalId = proposal.id }
|
||||
|
||||
-- | Simple consensus heuristic: if all contributions have a non-empty content
|
||||
-- and there are at least 2, mark as consensus; single contributor = pending.
|
||||
-- | Simple consensus heuristic: ≥2 successful contributions = consensus.
|
||||
detectConsensus :: [CollectiveProposalContribution] -> Text
|
||||
detectConsensus contribs
|
||||
| length contribs >= 2 = "consensus"
|
||||
|
||||
@@ -8,7 +8,7 @@ import Web.View.DecisionRecords.Edit
|
||||
import Generated.Types
|
||||
import IHP.Prelude
|
||||
import IHP.ControllerPrelude
|
||||
import Application.Helper.AgentBridge (callAgent, checkGovernancePolicy)
|
||||
import Application.Helper.AgentBridge (callAgent, checkGovernancePolicy, bridgeErrorMessage)
|
||||
import Application.Helper.ModelRouter (resolveAgent)
|
||||
import Data.List (intercalate)
|
||||
|
||||
@@ -227,7 +227,7 @@ instance Controller DecisionRecordsController where
|
||||
result <- liftIO $ callAgent agent userMsg
|
||||
case result of
|
||||
Left err -> do
|
||||
setErrorMessage ("Implementation proposal failed: " <> err.errorMessage)
|
||||
setErrorMessage ("Implementation proposal failed: " <> bridgeErrorMessage err)
|
||||
redirectTo ShowDecisionRecordAction { decisionRecordId }
|
||||
Right resp -> do
|
||||
newRecord @AgentProposal
|
||||
|
||||
@@ -8,7 +8,7 @@ import Web.View.RequirementCandidates.Edit
|
||||
import Generated.Types
|
||||
import IHP.Prelude
|
||||
import IHP.ControllerPrelude
|
||||
import Application.Helper.AgentBridge (callAgent, checkGovernancePolicy)
|
||||
import Application.Helper.AgentBridge (callAgent, checkGovernancePolicy, bridgeErrorMessage)
|
||||
import Application.Helper.ModelRouter (resolveAgent)
|
||||
import Data.List (intercalate)
|
||||
import Data.Aeson (decode, Value(..), Array)
|
||||
@@ -298,7 +298,7 @@ instance Controller RequirementCandidatesController where
|
||||
result <- liftIO $ callAgent agent userMsg
|
||||
case result of
|
||||
Left err -> do
|
||||
setErrorMessage ("Duplicate detection failed: " <> err.errorMessage)
|
||||
setErrorMessage ("Duplicate detection failed: " <> bridgeErrorMessage err)
|
||||
redirectTo ShowRequirementCandidateAction { requirementCandidateId }
|
||||
Right resp -> do
|
||||
newRecord @AgentProposal
|
||||
|
||||
@@ -11,7 +11,7 @@ import IHP.ControllerPrelude
|
||||
import Data.Aeson (toJSON, object, (.=))
|
||||
import Application.Helper.Controller (isInRegression, widgetCycleCounts)
|
||||
import Application.Helper.TypeRegistry (validateWidgetType, validatePolicyScope, activeWidgetTypes, activePolicyScopes)
|
||||
import Application.Helper.AgentBridge (callAgent, checkGovernancePolicy)
|
||||
import Application.Helper.AgentBridge (callAgent, checkGovernancePolicy, bridgeErrorMessage)
|
||||
import Application.Helper.ModelRouter (resolveAgent)
|
||||
import Data.List (intercalate)
|
||||
|
||||
@@ -209,7 +209,7 @@ instance Controller WidgetsController where
|
||||
result <- liftIO $ callAgent agent userMsg
|
||||
case result of
|
||||
Left err -> do
|
||||
setErrorMessage ("AI summarization failed: " <> err.errorMessage)
|
||||
setErrorMessage ("AI summarization failed: " <> bridgeErrorMessage err)
|
||||
redirectTo ShowWidgetAction { widgetId }
|
||||
Right resp -> do
|
||||
newRecord @AgentProposal
|
||||
@@ -258,7 +258,7 @@ instance Controller WidgetsController where
|
||||
result <- liftIO $ callAgent agent userMsg
|
||||
case result of
|
||||
Left err -> do
|
||||
setErrorMessage ("AI draft failed: " <> err.errorMessage)
|
||||
setErrorMessage ("AI draft failed: " <> bridgeErrorMessage err)
|
||||
redirectTo ShowWidgetAction { widgetId }
|
||||
Right resp -> do
|
||||
newRecord @AgentProposal
|
||||
|
||||
Reference in New Issue
Block a user