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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,6 @@ logs/sentinel/

# Agent Core β€” memory persistence
.disha/

# Claude Code internal state
.claude/
116 changes: 108 additions & 8 deletions ai-platform/backend/app/collaboration/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ def get_capable_agents(self, capability: str) -> list:
if capability in node.capabilities and node.status != "offline"
]

ITERATION_THRESHOLD = 0.4 # re-run if consensus agreement falls below this
MAX_ITERATIONS = 3 # cap iterations to prevent infinite loops

async def collaborative_investigate(
self,
target: str,
Expand All @@ -85,11 +88,12 @@ async def collaborative_investigate(

Flow:
1. Coordinator broadcasts task to all agents
2. Agents claim subtasks based on capabilities
3. Agents execute and share results
4. Review phase: agents critique each other
5. Consensus: agents vote on final assessment
6. Iterate if needed
2. Agents execute in parallel
3. Peer review: agents score each other's results
4. Consensus: confidence-weighted vote on risk assessment
5. If consensus is too low, iterate with dissenting agents
re-running with additional context from the first round
6. Compile final result
"""
conversation = self.router.create_conversation(topic=target)
context = context or {}
Expand All @@ -109,14 +113,53 @@ async def collaborative_investigate(
self.router.send(task_msg)
conversation.add_message(task_msg)

# Phase 2: Parallel execution by all capable agents
# Phase 2: Parallel execution
results = await self._parallel_execute(target, context)

# Phase 3: Share results for peer review
# Phase 3: Peer review
review_results = await self._peer_review(results, conversation)

# Phase 4: Consensus building
# Phase 4: Consensus + iterative refinement
consensus = self._build_consensus(results, review_results)
iteration = 0

while (
not consensus.get("consensus_reached", False)
and consensus.get("agreement_score", 1.0) < self.ITERATION_THRESHOLD
and iteration < self.MAX_ITERATIONS
):
iteration += 1
logger.info(
"consensus_below_threshold_iterating",
target=target,
iteration=iteration,
agreement=round(consensus.get("agreement_score", 0), 3),
)

# Identify low-confidence agents and re-run them with shared context
low_conf_agents = self._identify_dissenting_agents(results, review_results)
if not low_conf_agents:
break

enriched_context = {
**context,
"prior_round": iteration,
"peer_findings": {
name: res.get("entities", [])[:5]
for name, res in results.items()
if isinstance(res, dict) and name not in low_conf_agents
},
}

# Re-run only the dissenting agents
refined = await self._selective_execute(low_conf_agents, target, enriched_context)
results.update(refined)

# Rebuild consensus with updated results
review_results = await self._peer_review(results, conversation)
consensus = self._build_consensus(results, review_results)

consensus["iterations"] = iteration

# Phase 5: Compile final result
final_result = self._compile_results(
Expand All @@ -129,12 +172,69 @@ async def collaborative_investigate(
"collaborative_investigation_complete",
target=target,
agents_involved=len(results),
iterations=iteration,
consensus_score=consensus.get("score", 0),
agreement=consensus.get("agreement_score", 0),
risk_score=final_result.get("risk_score", 0),
)

return final_result

def _identify_dissenting_agents(self, results: dict, reviews: dict) -> list[str]:
"""Return names of agents whose results received the lowest peer review scores."""
avg_scores: dict[str, list[float]] = {}
for reviewer_reviews in reviews.values():
for name, score in reviewer_reviews.items():
avg_scores.setdefault(name, []).append(score)

scored = {
name: sum(scores) / len(scores)
for name, scores in avg_scores.items()
if scores
}
if not scored:
return []

# Return the bottom 50% of agents by peer-review score
sorted_agents = sorted(scored, key=scored.get)
cutoff = max(1, len(sorted_agents) // 2)
return sorted_agents[:cutoff]
Comment on lines +183 to +201

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Select dissenters by consensus deviation, not payload completeness.

_build_consensus() lowers agreement_score from risk_score variance, but this helper reruns agents based only on peer-review scores derived from _score_result(). Since _score_result() only looks at field presence/status, equally shaped results with conflicting risk_scores get the same score, so the retry loop can re-run arbitrary agents and never improve agreement.

Proposed fix
 def _identify_dissenting_agents(self, results: dict, reviews: dict) -> list[str]:
     """Return names of agents whose results received the lowest peer review scores."""
-    avg_scores: dict[str, list[float]] = {}
-    for reviewer_reviews in reviews.values():
-        for name, score in reviewer_reviews.items():
-            avg_scores.setdefault(name, []).append(score)
-
-    scored = {
-        name: sum(scores) / len(scores)
-        for name, scores in avg_scores.items()
-        if scores
-    }
-    if not scored:
-        return []
-
-    # Return the bottom 50% of agents by peer-review score
-    sorted_agents = sorted(scored, key=scored.get)
+    risk_scores = {
+        name: result["risk_score"]
+        for name, result in results.items()
+        if isinstance(result, dict) and "risk_score" in result
+    }
+    if risk_scores:
+        avg_risk = sum(risk_scores.values()) / len(risk_scores)
+        sorted_agents = sorted(
+            risk_scores,
+            key=lambda name: abs(risk_scores[name] - avg_risk),
+            reverse=True,
+        )
+        cutoff = max(1, len(sorted_agents) // 2)
+        return sorted_agents[:cutoff]
+
+    avg_scores: dict[str, list[float]] = {}
+    for reviewer_reviews in reviews.values():
+        for name, score in reviewer_reviews.items():
+            avg_scores.setdefault(name, []).append(score)
+
+    scored = {
+        name: sum(scores) / len(scores)
+        for name, scores in avg_scores.items()
+        if scores
+    }
+    if not scored:
+        return []
+
+    sorted_agents = sorted(scored, key=scored.get)
     cutoff = max(1, len(sorted_agents) // 2)
     return sorted_agents[:cutoff]
🧰 Tools
πŸͺ› Ruff (0.15.9)

[warning] 183-183: Unused method argument: results

(ARG002)

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ai-platform/backend/app/collaboration/coordinator.py` around lines 183 - 201,
The current _identify_dissenting_agents uses only peer-review scores from
_score_result (which checks payload completeness) and thus misses agents whose
risk_score deviates from consensus; change _identify_dissenting_agents to
compute a consensus risk_score from the current results (e.g., call or mirror
logic in _build_consensus to get a median/mean consensus risk_score and/or
agreement_score) and then rank agents by absolute deviation between each agent's
results[name]["risk_score"] and that consensus, optionally combining that
deviation metric with the peer-review score from reviews to break ties; return
the top-N highest deviators (or >threshold) instead of the lowest
peer-review-only agents so retries target actual risk_score dissenters.


async def _selective_execute(
self, agent_names: list[str], target: str, context: dict
) -> dict:
"""Re-run a specific subset of agents."""
selected = [(name, node) for name, node in self.nodes.items() if name in agent_names]
if not selected:
return {}

results = {}

async def run_agent(name, node):
start = time.time()
try:
result = await node.agent.run(target, context)
node.tasks_completed += 1
node.total_time += time.time() - start
node.last_active = time.time()
return name, result
except Exception as e:
logger.error("selective_agent_execution_failed", agent=name, error=str(e))
return name, {"error": str(e), "status": "failed"}

agent_results = await asyncio.gather(
*[run_agent(name, node) for name, node in selected],
return_exceptions=True,
)

for item in agent_results:
if isinstance(item, Exception):
continue
name, result = item
results[name] = result

return results
Comment on lines +203 to +236

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Mirror the busy/idle lifecycle in selective reruns.

Unlike _parallel_execute(), this path never marks rerun agents as busy and never restores them in a finally block. get_cluster_status() will report those agents as idle throughout refinement, which makes the new execution path operationally inconsistent.

Proposed fix
     async def run_agent(name, node):
         start = time.time()
+        node.status = "busy"
         try:
             result = await node.agent.run(target, context)
             node.tasks_completed += 1
             node.total_time += time.time() - start
             node.last_active = time.time()
             return name, result
         except Exception as e:
             logger.error("selective_agent_execution_failed", agent=name, error=str(e))
             return name, {"error": str(e), "status": "failed"}
+        finally:
+            node.status = "idle"
🧰 Tools
πŸͺ› Ruff (0.15.9)

[warning] 213-213: Missing return type annotation for private function run_agent

(ANN202)


[warning] 220-220: Consider moving this statement to an else block

(TRY300)


[warning] 221-221: Do not catch blind exception: Exception

(BLE001)


[warning] 222-222: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ai-platform/backend/app/collaboration/coordinator.py` around lines 203 - 236,
The selective rerun path in _selective_execute doesn't mirror the busy/idle
lifecycle used in _parallel_execute, so update run_agent inside
_selective_execute to set node.busy = True before running node.agent.run(...)
and ensure node.busy is reset to False in a finally block (after updating
node.tasks_completed, node.total_time, and node.last_active or after catching
exceptions) so agents are marked busy during execution and restored to idle
regardless of success/failure; follow the same try/except/finally structure and
logging pattern used by _parallel_execute to keep get_cluster_status()
consistent.


async def _parallel_execute(self, target: str, context: dict) -> dict:
"""Execute all available agents in parallel."""
results = {}
Expand Down
11 changes: 11 additions & 0 deletions cognitive-engine/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
DISHA-MIND β€” Cognitive Engine Package.

Entry point for the 7-stage cognitive loop, three-layer memory architecture,
multi-agent deliberation, hybrid reasoning, and quantum-inspired decisions.
"""

from cognitive_engine.cognitive_loop import CognitiveEngine, CognitiveState

__all__ = ["CognitiveEngine", "CognitiveState"]
__version__ = "1.0.0"
17 changes: 17 additions & 0 deletions cognitive-engine/agents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
cognitive-engine/agents β€” Multi-Agent Deliberation for the DISHA Cognitive Architecture.

This subpackage implements the three-agent deliberation layer that sits between
raw reasoning hypotheses and final action selection:

Planner β†’ strategic goal decomposition
Executor β†’ concrete step-by-step action plan
Critic β†’ quality gate and risk assessment
Vote β†’ confidence-weighted consensus

The AgentDeliberator class orchestrates all three and returns a consensus dict.
"""

from cognitive_engine.agents.deliberation import AgentDeliberator

__all__ = ["AgentDeliberator"]
Loading
Loading