Claude/fix ci pipeline 1i wa h - #61
Conversation
Implements the full 7-stage cognitive loop (Perceive→Attend→Reason→ Deliberate→Act→Reflect→Consolidate) with three-layer memory, multi-agent deliberation, hybrid reasoning, and quantum-inspired decisions. cognitive-engine/: - cognitive_loop.py: CognitiveEngine + CognitiveState, all 7 async stages, session tracing, introspection report - memory/: WorkingMemory (8-slot decay buffer), EpisodicMemory (persisted, importance-weighted recall), SemanticMemory (concept graph, BFS traversal, confidence decay), MemoryManager (unified interface, auto-promotion) - agents/deliberation.py: Planner/Executor/Critic parallel deliberation, confidence-weighted consensus vote, preserved dissenting_view - intelligence/hybrid_reasoner.py: deductive + inductive + abductive in parallel via asyncio.gather, coherence-based selection - intelligence/quantum_decision.py: superposition → destructive/constructive interference → collapse to highest-amplitude option - intelligence/goal_engine.py: priority queue, decomposition, goal tree - api/routes.py: FastAPI REST + WebSocket streaming of per-stage events - tests/test_cognitive_loop.py: 27 unit tests covering all 7 stages, full cycle, introspection, confidence threshold branching ai-platform/backend/app/collaboration/coordinator.py: - Iterative consensus refinement: re-runs dissenting agents (bottom 50% by peer-review score) with enriched context when agreement < 0.4, up to MAX_ITERATIONS=3 — prevents premature weak consensus - _identify_dissenting_agents(): peer-score ranking for refinement targeting - _selective_execute(): targeted re-run of agent subset https://claude.ai/code/session_01L5fWwPFRf6E17zZoYnwYLE
📝 WalkthroughWalkthroughThis PR introduces a comprehensive cognitive engine package implementing a 7-stage asynchronous cognitive loop coordinated by Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API
participant Engine as CognitiveEngine
participant Perceive
participant Attend
participant Reason as HybridReasoner
participant Deliberate as AgentDeliberator
participant Act
participant Reflect
participant Consolidate
participant Memory as MemoryManager
Client->>API: POST /process {input}
API->>Engine: process(raw_input, session_id)
Engine->>Perceive: _perceive(state)
Perceive->>Engine: intent, entities, uncertainty
Engine->>Attend: _attend(state)
Attend->>Memory: retrieve(query, session_id)
Memory->>Engine: recalled_episodes, concepts
Engine->>Reason: reason(query, context)
par Parallel Reasoning
Reason->>Reason: _deductive(context)
Reason->>Reason: _inductive(context)
Reason->>Reason: _abductive(context)
end
Reason->>Engine: hypotheses[], selected_hypothesis
Engine->>Deliberate: deliberate(state)
par Concurrent Agents
Deliberate->>Deliberate: _planner_agent(state)
Deliberate->>Deliberate: _executor_agent(state)
Deliberate->>Deliberate: _critic_agent(state)
end
Deliberate->>Deliberate: _vote(opinions)
Deliberate->>Engine: consensus, dissenting_view
Engine->>Act: _act(state)
Engine->>Act: compute confidence threshold
alt High Confidence
Act->>Engine: action dict
else Low Confidence
Act->>Engine: clarification_request
end
Engine->>Reflect: _reflect(state)
Reflect->>Engine: reflection, learning_triggers
Engine->>Consolidate: _consolidate(state)
Consolidate->>Memory: store_episode(session_id, ...)
Consolidate->>Memory: learn_concept(...)
Engine->>API: CognitiveState
API->>Client: ProcessResponse {intent, action, reflection, durations}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 33
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ai-platform/backend/app/collaboration/coordinator.py`:
- Around line 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.
- Around line 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.
In `@cognitive-engine/agents/deliberation.py`:
- Around line 357-359: The list comprehension building all_concerns uses two
separate startswith checks; replace s.startswith("insufficient") or
s.startswith("empty") with a single tuple-based call
s.startswith(("insufficient", "empty")) so the expression becomes risk_flags +
[s for s in quality_signals if s.startswith(("insufficient", "empty"))]; update
the variable all_concerns accordingly (references: all_concerns, risk_flags,
quality_signals).
- Around line 341-344: The code incorrectly reads entities from
state.context.get("entities") — but entities are stored directly on the state
dataclass as state.entities; update the conditional in the deliberation logic to
check state.entities (and treat None/empty appropriately) and append to
quality_signals ("entities_extracted" or "no_entities_detected") based on
whether state.entities contains any entries; modify the block that references
state.context to use state.entities instead so the check and appended signals
are accurate.
- Around line 217-233: The code erroneously accesses state.context (e.g.,
state.context.get("intent_confidence"), state.context.get("uncertainty_level"),
state.context.get("entities")) but CognitiveState has no context field; update
deliberation.py to read the actual CognitiveState attributes instead (use
getattr fallbacks): replace state.context.get("intent_confidence", 0.0) with
getattr(state, "intent_confidence", 0.0) (or state.intent_confidence if
present), replace state.context.get("uncertainty_level", 0) with getattr(state,
"uncertainty", 0) or state.uncertainty, and replace
state.context.get("entities", []) with getattr(state, "entities", []) to avoid
AttributeError and preserve defaults; ensure any other uses of state.context in
deliberation.py are similarly migrated and tested against the CognitiveState
definition in cognitive_loop.py.
In `@cognitive-engine/api/routes.py`:
- Line 180: Replace the direct access to the private member _engine._traces by
adding a public method on CognitiveEngine (e.g., record_trace or add_trace) that
accepts session_id and state and appends the state to the engine's trace store;
update the call site in routes.py to call that new method (pass session_id and
state) instead of touching _engine._traces directly, and implement any necessary
initialization or locking inside the new CognitiveEngine method to preserve the
original behavior.
- Around line 128-133: The nested try/except silently swallows errors from
websocket.send_json, hiding failures; update the inner block to catch Exception
as inner_exc and log it (e.g., call log.error or log.exception) with context
that send_json failed while handling the original exc (reference the outer
variable exc) so you get both the original error and the inner send_json error;
keep the send attempt and still avoid raising for disconnects, but ensure the
inner exception is logged (use websocket.send_json and log.error/log.exception
to record inner_exc and note it occurred while sending the error message for
exc).
- Around line 162-178: The loop currently logs exceptions from stage_fn but
still builds/sends a normal payload; modify the flow in the for loop that
iterates stage_map so payload creation/sending happens after the try/except and
includes an error indicator: call await stage_fn(state) inside the try as now,
but in the except block set a local flag (e.g., stage_failed=True) and capture
the exception (exc) and append error details to state.learning_triggers; after
computing duration and calling _stage_payload(stage_name, state), add
payload["error"]=True and payload["error_message"]=str(exc) when stage_failed
(otherwise set payload["error"]=False), then send via ws.send_json as before;
keep state.stage_durations updated and preserve the existing logging
(log.error("stage_failed", ...)).
- Around line 88-96: STAGE_ORDER is defined but never used; either remove this
dead constant or replace the ad-hoc stage_map usage in _stream_cognitive_cycle
with STAGE_ORDER to centralize stage names. Update references in
_stream_cognitive_cycle (and any related code that relies on stage_map) to
iterate over STAGE_ORDER, or if you choose removal, delete STAGE_ORDER and
ensure no other modules expect it. Keep stage names consistent by using the
symbol STAGE_ORDER if you opt to reuse it.
In `@cognitive-engine/cognitive_loop.py`:
- Around line 152-155: The code currently overwrites state.learning_triggers
(losing earlier "stage_error:{stage_name}" entries) when updating triggers;
instead, preserve previously captured triggers by merging or extending the
existing state.learning_triggers with the new triggers rather than assigning a
new list. Locate the place where state.learning_triggers is set and change it to
append/extend/merge (e.g., extend with the new list or use a set union to avoid
duplicates) so stage-error entries added in the except block remain present;
keep existing behavior of adding stage error strings like
"stage_error:{stage_name}" and ensure no accidental duplication.
- Around line 152-153: The except block in cognitive_loop.py currently logs
failures with log.error("stage_failed", stage=stage_name, error=str(exc)) which
drops the traceback; change the logging to record exception context by calling
log.exception or passing exc_info=True (e.g., log.exception("stage_failed",
stage=stage_name) or log.error("stage_failed", stage=stage_name, exc_info=True))
inside the except Exception as exc handler so the full stack trace is preserved
for diagnosability.
- Around line 291-330: The mapping of reflection factors in state.reflection
incorrectly uses fixed indices into quality_factors (so agent_convergence,
memory_retrieval, hypothesis_diversity can be mislabeled when agent_convergence
wasn't appended); update the construction of state.reflection to derive each
named factor by computing or extracting the corresponding value explicitly
(e.g., compute agent_convergence from opinions/agent_deliberations or set to
None if not computed, use state.confidence for action_confidence, and determine
memory_retrieval and hypothesis_diversity from the presence of
recalled_episodes/recalled_concepts and len(state.hypotheses) respectively)
rather than relying on quality_factors[0..3], ensuring consistency with the
logic that appends to quality_factors earlier in the code.
- Line 136: The current info log in cognitive_loop.py logs raw_input[:80] which
can leak sensitive data; change the logging to emit a redacted or hashed preview
instead — e.g., implement or call a helper (e.g., redact_sensitive_input or
hash_input_preview) that either redacts sensitive tokens or returns a short
deterministic hash/nonce of raw_input, and replace input_preview=raw_input[:80]
in the log.info("cognitive_cycle_start", ...) call with the sanitized value;
ensure the helper is used wherever raw_input previews are logged and that it
never includes unredacted user content.
- Line 127: The turn assignment in process() (calculating turn =
len(self._traces.get(session_id, []))) races with other awaits and can yield
duplicate turn IDs; fix by adding a per-session asyncio.Lock (e.g.,
self._session_locks: Dict[session_id, asyncio.Lock] initialized in __init__) and
wrap the turn calculation and the subsequent append to self._traces inside
"async with lock:" so only one coroutine per session computes and appends the
trace at a time; ensure lock creation is atomic (use setdefault or get-or-create
pattern) and keep the rest of process() logic outside the critical section to
minimize contention.
In `@cognitive-engine/intelligence/goal_engine.py`:
- Around line 258-278: get_next_actionable() currently ignores the heap and
rebuilds a sorted list, so either remove the heap logic from __init__ and
add_goal() or (recommended) make the heap authoritative by implementing
lazy-deletion: when adding a goal push a tuple like (-priority, created_at,
goal_id) onto the heap in add_goal(), then in get_next_actionable() pop items
until you find a goal_id whose entry in self._goals matches the popped
priority/created_at and has status STATUS_PENDING or STATUS_ACTIVE and whose
dependencies are met; when found, set status to STATUS_ACTIVE, update updated_at
and log as currently done, otherwise continue popping; ensure stale entries are
ignored by checking self._goals for current values (this preserves the existing
_goals dict while making the heap effective).
- Around line 400-408: The stats() method returns a misleading "heap_size" even
though get_next_actionable() doesn't use _heap and _heap may contain stale
entries; update stats() (and its docstring if present) to remove the "heap_size"
entry and only report metrics derived from the authoritative _goals (e.g.,
"total_goals" and "by_status"), referencing the stats(), get_next_actionable(),
_heap, and _goals symbols so you locate and modify the returned dict
accordingly.
- Around line 97-121: The class _GoalEntry defines __eq__ but not __hash__,
making instances unhashable; add a __hash__(self) -> int method on _GoalEntry
that returns a stable hash consistent with __eq__, e.g.,
hash(self.goal["goal_id"]) (or the appropriate unique identifier used in
__eq__), so _GoalEntry can be used in sets/dict keys while preserving equality
semantics between __eq__ and __hash__.
In `@cognitive-engine/intelligence/hybrid_reasoner.py`:
- Line 352: The string literal "Occam's razor applied: selecting minimal
hypothesis" is prefixed with an unnecessary f-string; remove the leading f so it
becomes a plain string literal (i.e., replace f"Occam's razor applied: selecting
minimal hypothesis" with "Occam's razor applied: selecting minimal hypothesis")
wherever it appears (e.g., the message construction at the Occam's razor
selection point).
- Line 339: Replace the Unicode multiplication sign in the f-string that
constructs the phrase "appears {top_freq}× across query and context" with the
ASCII letter "x" to avoid encoding issues; update the string in
cognitive-engine/intelligence/hybrid_reasoner.py where variables top_word and
top_freq are used (the f-string producing "about '{top_word}' (appears
{top_freq}× across query and context)") so it becomes "appears {top_freq}x
across query and context" and ensure any other occurrences of the Unicode × in
the same module are similarly replaced.
In `@cognitive-engine/intelligence/quantum_decision.py`:
- Around line 116-117: The zip between options and raw_amplitudes should be
strict to catch length mismatches: update the comprehension that iterates over
zip(options, raw_amplitudes) (where raw_amplitudes and options are paired to
build task amplitudes) to call zip(options, raw_amplitudes, strict=True) so any
unexpected length difference raises immediately during development.
In `@cognitive-engine/memory/episodic.py`:
- Around line 202-214: get_timeline currently reads self._episodes directly
without calling _ensure_loaded(), so it may operate on unloaded data; add a call
to self._ensure_loaded() at the start of get_timeline (mirroring consolidate)
before filtering self._episodes, ensuring the session lookup and subsequent sort
use the loaded episode list.
- Around line 182-200: consolidate currently reads self._episodes without
ensuring the memory is loaded; change consolidate to be asynchronous (async def
consolidate(self, min_importance: float = 0.7) -> list[dict[str, Any]]), call
await self._ensure_loaded() at the start of the method, then proceed to filter
self._episodes as before; update any callers to await
episodic_memory.consolidate(...) or, if you cannot change callers, instead add a
synchronous guard that raises a clear error when not loaded (e.g., check
self._loaded flag) — reference the consolidate method and the _ensure_loaded
helper to locate where to add the await or the guard.
- Around line 128-131: In record(), avoid the race by calling self._persist()
while holding the same lock used to append; move the await self._persist() call
inside the async with self._lock block so the append to self._episodes and the
persist happen atomically under the lock, ensuring no other concurrent record()
modifies _episodes between append and persist.
- Around line 77-86: The _persist method claims atomic writes but currently
writes directly to self._path; change it to write the JSON to a temporary file
in the same directory (e.g., using a filename based on self._path with a suffix
or random token), ensure the temp file is fully written and closed (await
flush/close when using aiofiles) and then atomically replace the target with
os.replace(temp_path, self._path); on errors remove the temp file if present and
keep the existing file untouched, and update the log calls (e.g., in _persist)
to reflect success or failure accordingly while still creating the parent
directory as you already do.
In `@cognitive-engine/memory/memory_manager.py`:
- Around line 70-82: The semantic queries loop runs sequentially; instead create
tasks for the keywords and await them in parallel so queries run concurrently.
Replace the loop over keywords[:3] that calls await self.semantic.query(kw) with
creating asyncio tasks (e.g., via asyncio.create_task or a tasks list) for each
self.semantic.query(kw), await them together with asyncio.gather, then iterate
the returned results, attach the corresponding keyword to each result as
concept["name"] and append non-empty concepts to semantic_results; keep the
existing episodic_task and await it as before.
- Around line 218-243: The promote_to_semantic function currently uses
asyncio.get_event_loop and asyncio.ensure_future causing deprecated behavior,
lost exceptions, and premature increment of promoted; change promote_to_semantic
to be async (async def promote_to_semantic(...)), remove
get_event_loop/ensure_future logic, directly await self.semantic.learn(...) and
only increment the promoted counter after the await succeeds, wrap the await in
try/except to catch and log exceptions (use log.warning with error details and
concept), and update the caller in cognitive_loop.py::_consolidate to await
self.memory.promote_to_semantic(...) so failures are surfaced and counted
correctly.
In `@cognitive-engine/memory/semantic.py`:
- Around line 84-92: In _persist replace the current direct write with an atomic
write-to-temp-then-rename pattern: create the parent dir, write JSON to a
temporary file in the same directory (e.g., _path with a .tmp suffix), fsync the
temp file and its directory, then atomically replace the target via os.replace;
also change the exception logging inside the except block to use
log.exception("semantic_memory.persist_failed") so the stack trace is captured
(refer to the _persist method and self._path/_graph symbols).
- Around line 282-293: decay_confidence currently mutates self._graph but never
persists the updated confidences; change decay_confidence to be async (async def
decay_confidence(...)) and after looping and updating node["confidence"] call
await self._persist() so changes are saved; keep the existing logging and ensure
callers await the new async method (update any places that call decay_confidence
to use await or schedule it), and reference the method name decay_confidence and
the helper _persist to locate the changes.
In `@cognitive-engine/memory/working.py`:
- Around line 142-153: The stats() method currently returns a different key set
when self._slots is empty (missing "capacity"); update stats() so the
empty-branch returns the same schema as the non-empty branch by including
"capacity": MAX_SLOTS alongside "count", "avg_relevance", "max_relevance", and
"min_relevance". Locate the stats method in the Working memory class (function
name stats, field self._slots, constant MAX_SLOTS) and change the early return
to include "capacity": MAX_SLOTS and keep numeric defaults (0 or 0.0) matching
the non-empty types so callers always receive a consistent dict shape.
- Around line 63-72: When MAX_SLOTS has been reached in add/insert logic,
currently the code always pops the lowest-relevance slot (min_idx) and evicts
it; change this to first compute min_relevance =
self._slots[min_idx]["relevance"] and compare it to the incoming
entry["relevance"], and only pop/evict if entry["relevance"] > min_relevance; if
not, skip inserting the new entry (or return) so you don't replace a
higher-relevance existing slot with a lower-relevance new one; update the
working_memory.evicted logging to only run on actual eviction and optionally add
a debug log for dropped entries referencing MAX_SLOTS, self._slots, min_idx,
evicted, and entry.
In `@cognitive-engine/requirements.txt`:
- Around line 1-5: Update the dependency pins in requirements.txt to explicitly
ensure the h11 vulnerability is patched: add an explicit h11>=0.16.0 entry
(alongside the existing uvicorn[standard]>=0.32.0) so the resolver cannot pick a
vulnerable transitive h11 (uvicorn pulls h11>=0.8); ensure the new line
"h11>=0.16.0" is added to the file to force the fixed version and consider
pinning other critical transitive deps explicitly if scanned as vulnerable.
In `@cognitive-engine/tests/test_cognitive_loop.py`:
- Around line 73-84: The FakeHypothesis.__iter__ returns
iter(self.__dict__.items()) but the test defines attributes at class level so
dict(hyp) will be empty; fix by making FakeHypothesis set mode, hypothesis,
confidence, and chain_of_thought as instance attributes in an __init__ (or
change __iter__ to yield class attributes), then keep reas.reason =
AsyncMock(return_value=[hyp]) and reas.select_best = MagicMock(return_value=hyp)
unchanged so dict(hyp) and iteration behave as expected.
- Around line 319-321: The test method test_get_session_ids is synchronous but
uses the engine fixture which may be an async fixture; change the test to an
async test (async def test_get_session_ids(self, engine):) so pytest-asyncio can
properly provide the async fixture, then call engine.get_session_ids() as before
(no await needed since get_session_ids() is synchronous). Ensure the test name
and the call to get_session_ids() remain unchanged to locate the logic easily.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 50e8ef01-640b-4506-83fe-9eb40e14c7c4
📒 Files selected for processing (20)
.gitignoreai-platform/backend/app/collaboration/coordinator.pycognitive-engine/__init__.pycognitive-engine/agents/__init__.pycognitive-engine/agents/deliberation.pycognitive-engine/api/__init__.pycognitive-engine/api/routes.pycognitive-engine/cognitive_loop.pycognitive-engine/intelligence/__init__.pycognitive-engine/intelligence/goal_engine.pycognitive-engine/intelligence/hybrid_reasoner.pycognitive-engine/intelligence/quantum_decision.pycognitive-engine/memory/__init__.pycognitive-engine/memory/episodic.pycognitive-engine/memory/memory_manager.pycognitive-engine/memory/semantic.pycognitive-engine/memory/working.pycognitive-engine/requirements.txtcognitive-engine/tests/__init__.pycognitive-engine/tests/test_cognitive_loop.py
| 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] |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| if state.context.get("intent_confidence", 0.0) > 0.8: | ||
| base_confidence = 0.85 | ||
| elif state.context.get("intent_confidence", 0.0) < 0.4: | ||
| base_confidence = 0.55 | ||
|
|
||
| # Factor in hypothesis quality | ||
| if state.hypotheses: | ||
| avg_hyp_confidence = sum( | ||
| h.get("confidence", 0.5) for h in state.hypotheses | ||
| ) / len(state.hypotheses) | ||
| base_confidence = (base_confidence + avg_hyp_confidence) / 2 | ||
|
|
||
| concerns: list[str] = [] | ||
| if len(steps) > 4: | ||
| concerns.append("multi_step_plan_may_require_clarification") | ||
| if state.context.get("uncertainty_level", 0) > 0.6: | ||
| concerns.append("high_uncertainty_may_affect_plan_execution") |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify CognitiveState fields
ast-grep --pattern 'class CognitiveState:
$$$
'Repository: Tashima-Tarsh/Disha
Length of output: 3390
🏁 Script executed:
rg "state\.context" cognitive-engine/agents/deliberation.py -nRepository: Tashima-Tarsh/Disha
Length of output: 565
🏁 Script executed:
rg "context\s*=" cognitive-engine/ -n | grep -i "state\|context" | head -20Repository: Tashima-Tarsh/Disha
Length of output: 121
🏁 Script executed:
sed -n '225,235p' cognitive-engine/cognitive_loop.pyRepository: Tashima-Tarsh/Disha
Length of output: 466
🏁 Script executed:
rg "state\.context\s*=" cognitive-engine/ -nRepository: Tashima-Tarsh/Disha
Length of output: 45
🏁 Script executed:
rg "from.*deliberation|import.*deliberation" cognitive-engine/ -nRepository: Tashima-Tarsh/Disha
Length of output: 271
🏁 Script executed:
sed -n '200,250p' cognitive-engine/cognitive_loop.pyRepository: Tashima-Tarsh/Disha
Length of output: 2149
🏁 Script executed:
cat -n cognitive-engine/agents/deliberation.py | sed -n '210,220p'Repository: Tashima-Tarsh/Disha
Length of output: 649
state.context does not exist on CognitiveState — code will raise AttributeError at runtime.
The code accesses state.context.get("intent_confidence"), state.context.get("uncertainty_level"), and state.context.get("entities") at lines 217, 219, 232, and 341, but CognitiveState (defined in cognitive_loop.py:39-83) has no context field. This will fail at runtime whenever the planner, critic, or other code paths execute these lines.
Note: CognitiveState does have direct fields for some of these values (e.g., state.uncertainty, state.entities), suggesting state.context was never implemented or was removed from the schema.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cognitive-engine/agents/deliberation.py` around lines 217 - 233, The code
erroneously accesses state.context (e.g.,
state.context.get("intent_confidence"), state.context.get("uncertainty_level"),
state.context.get("entities")) but CognitiveState has no context field; update
deliberation.py to read the actual CognitiveState attributes instead (use
getattr fallbacks): replace state.context.get("intent_confidence", 0.0) with
getattr(state, "intent_confidence", 0.0) (or state.intent_confidence if
present), replace state.context.get("uncertainty_level", 0) with getattr(state,
"uncertainty", 0) or state.uncertainty, and replace
state.context.get("entities", []) with getattr(state, "entities", []) to avoid
AttributeError and preserve defaults; ensure any other uses of state.context in
deliberation.py are similarly migrated and tested against the CognitiveState
definition in cognitive_loop.py.
| if state.context.get("entities"): | ||
| quality_signals.append("entities_extracted") | ||
| else: | ||
| quality_signals.append("no_entities_detected") |
There was a problem hiding this comment.
Another state.context access that doesn't exist.
Line 341 accesses state.context.get("entities"), but entities are stored directly as state.entities per the dataclass definition.
🐛 Proposed fix
- if state.context.get("entities"):
+ if state.entities:
quality_signals.append("entities_extracted")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if state.context.get("entities"): | |
| quality_signals.append("entities_extracted") | |
| else: | |
| quality_signals.append("no_entities_detected") | |
| if state.entities: | |
| quality_signals.append("entities_extracted") | |
| else: | |
| quality_signals.append("no_entities_detected") |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cognitive-engine/agents/deliberation.py` around lines 341 - 344, The code
incorrectly reads entities from state.context.get("entities") — but entities are
stored directly on the state dataclass as state.entities; update the conditional
in the deliberation logic to check state.entities (and treat None/empty
appropriately) and append to quality_signals ("entities_extracted" or
"no_entities_detected") based on whether state.entities contains any entries;
modify the block that references state.context to use state.entities instead so
the check and appended signals are accurate.
| all_concerns = risk_flags + [ | ||
| s for s in quality_signals if s.startswith("insufficient") or s.startswith("empty") | ||
| ] |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Minor: Merge startswith calls into tuple.
♻️ Proposed fix per static analysis
all_concerns = risk_flags + [
- s for s in quality_signals if s.startswith("insufficient") or s.startswith("empty")
+ s for s in quality_signals if s.startswith(("insufficient", "empty"))
]🧰 Tools
🪛 Ruff (0.15.9)
[warning] 358-358: Call startswith once with a tuple
Merge into a single startswith call
(PIE810)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cognitive-engine/agents/deliberation.py` around lines 357 - 359, The list
comprehension building all_concerns uses two separate startswith checks; replace
s.startswith("insufficient") or s.startswith("empty") with a single tuple-based
call s.startswith(("insufficient", "empty")) so the expression becomes
risk_flags + [s for s in quality_signals if s.startswith(("insufficient",
"empty"))]; update the variable all_concerns accordingly (references:
all_concerns, risk_flags, quality_signals).
| if len(self._slots) >= MAX_SLOTS: | ||
| # Evict the slot with the lowest relevance | ||
| min_idx = min(range(len(self._slots)), key=lambda i: self._slots[i]["relevance"]) | ||
| evicted = self._slots.pop(min_idx) | ||
| log.debug( | ||
| "working_memory.evicted", | ||
| evicted_source=evicted.get("source"), | ||
| evicted_relevance=round(evicted["relevance"], 3), | ||
| new_item_source=entry["source"], | ||
| ) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider comparing new item relevance before eviction.
The current logic evicts the lowest-relevance item regardless of whether the new item has higher relevance. If the new item has lower relevance than all existing items, it will still evict a more relevant item. Consider adding a check:
♻️ Optional: Only evict if new item is more relevant
if len(self._slots) >= MAX_SLOTS:
# Evict the slot with the lowest relevance
min_idx = min(range(len(self._slots)), key=lambda i: self._slots[i]["relevance"])
+ if self._slots[min_idx]["relevance"] >= relevance:
+ log.debug(
+ "working_memory.rejected",
+ new_relevance=round(relevance, 3),
+ min_existing=round(self._slots[min_idx]["relevance"], 3),
+ )
+ return # Don't add items less relevant than all existing
evicted = self._slots.pop(min_idx)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cognitive-engine/memory/working.py` around lines 63 - 72, When MAX_SLOTS has
been reached in add/insert logic, currently the code always pops the
lowest-relevance slot (min_idx) and evicts it; change this to first compute
min_relevance = self._slots[min_idx]["relevance"] and compare it to the incoming
entry["relevance"], and only pop/evict if entry["relevance"] > min_relevance; if
not, skip inserting the new entry (or return) so you don't replace a
higher-relevance existing slot with a lower-relevance new one; update the
working_memory.evicted logging to only run on actual eviction and optionally add
a debug log for dropped entries referencing MAX_SLOTS, self._slots, min_idx,
evicted, and entry.
| def stats(self) -> dict[str, Any]: | ||
| """Return a summary of current memory state.""" | ||
| if not self._slots: | ||
| return {"count": 0, "avg_relevance": 0.0, "max_relevance": 0.0, "min_relevance": 0.0} | ||
| relevances = [s["relevance"] for s in self._slots] | ||
| return { | ||
| "count": len(self._slots), | ||
| "capacity": MAX_SLOTS, | ||
| "avg_relevance": round(sum(relevances) / len(relevances), 4), | ||
| "max_relevance": round(max(relevances), 4), | ||
| "min_relevance": round(min(relevances), 4), | ||
| } |
There was a problem hiding this comment.
stats() returns inconsistent keys when empty vs non-empty.
When _slots is empty, stats() returns 4 keys without "capacity". When non-empty, it includes "capacity". This inconsistency could cause issues for consumers expecting a consistent schema.
🔧 Proposed fix
def stats(self) -> dict[str, Any]:
"""Return a summary of current memory state."""
if not self._slots:
- return {"count": 0, "avg_relevance": 0.0, "max_relevance": 0.0, "min_relevance": 0.0}
+ return {"count": 0, "capacity": MAX_SLOTS, "avg_relevance": 0.0, "max_relevance": 0.0, "min_relevance": 0.0}
relevances = [s["relevance"] for s in self._slots]
return {
"count": len(self._slots),
"capacity": MAX_SLOTS,
"avg_relevance": round(sum(relevances) / len(relevances), 4),
"max_relevance": round(max(relevances), 4),
"min_relevance": round(min(relevances), 4),
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cognitive-engine/memory/working.py` around lines 142 - 153, The stats()
method currently returns a different key set when self._slots is empty (missing
"capacity"); update stats() so the empty-branch returns the same schema as the
non-empty branch by including "capacity": MAX_SLOTS alongside "count",
"avg_relevance", "max_relevance", and "min_relevance". Locate the stats method
in the Working memory class (function name stats, field self._slots, constant
MAX_SLOTS) and change the early return to include "capacity": MAX_SLOTS and keep
numeric defaults (0 or 0.0) matching the non-empty types so callers always
receive a consistent dict shape.
| fastapi>=0.115.0 | ||
| uvicorn[standard]>=0.32.0 | ||
| websockets>=13.0 | ||
| structlog>=24.4.0 | ||
| pydantic>=2.9.0 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the h11 vulnerability advisory and patched version
gh api graphql -f query='
{
securityVulnerabilities(first: 3, ecosystem: PIP, package: "h11") {
nodes {
advisory {
summary
severity
publishedAt
}
vulnerableVersionRange
firstPatchedVersion {
identifier
}
}
}
}'Repository: Tashima-Tarsh/Disha
Length of output: 223
🌐 Web query:
h11 GHSA-vqfr-h8mv-ghfj vulnerability patched version
💡 Result:
The GHSA-vqfr-h8mv-ghfj vulnerability (CVE-2025-43859) in the h11 Python library, affecting versions up to and including 0.15.0, was patched in version 0.16.0. This critical severity issue involves h11 accepting malformed Chunked-Encoding bodies, potentially leading to HTTP request smuggling when combined with a buggy proxy.
Citations:
- 1: GHSA-vqfr-h8mv-ghfj
- 2: https://osv.dev/GHSA-vqfr-h8mv-ghfj
- 3: https://nvd.nist.gov/vuln/detail/CVE-2025-43859
- 4: GHSA-vqfr-h8mv-ghfj
- 5: https://h11.readthedocs.io/en/latest/changes.html
- 6: python-hyper/h11@v0.14.0...v0.16.0
- 7: python-hyper/h11@9c9567f
- 8: https://h11.readthedocs.io/en/v0.16.0/changes.html
- 9: https://pypi.org/project/h11/
🏁 Script executed:
# Check if there are lock files in the repository that show actual dependency resolution
git ls-files | grep -E '\.(lock|txt|in)$' | head -20Repository: Tashima-Tarsh/Disha
Length of output: 889
🌐 Web query:
uvicorn 0.32.0 h11 dependency requirement version
💡 Result:
Uvicorn 0.32.0 requires h11 >= 0.8.
Citations:
- 1: https://pypi.org/project/uvicorn/
- 2: https://github.com/encode/uvicorn/blob/master/pyproject.toml
- 3: https://uvicorn.dev/installation/
🌐 Web query:
fastapi 0.115.0 dependencies h11 version
💡 Result:
FastAPI 0.115.0 does not directly depend on h11. h11 is an optional dependency of Starlette (via uvicorn[standard] when using fastapi[standard]), on which FastAPI 0.115.0 depends with constraint starlette >=0.40.0,<0.47.0. No specific pinned version for h11 is declared in FastAPI or Starlette dependencies for this range; any compatible h11 version satisfying Starlette/uvicorn constraints is used (typically >=0.13.0 or >=0.14.0 based on common usage, but not explicitly pinned).
Citations:
- 1: https://fastapi.tiangolo.com/release-notes/
- 2: https://fastapi.tiangolo.com/deployment/versions/
- 3: https://github.com/fastapi/fastapi/releases/tag/0.115.10
- 4: ./setup.sh fails: fastapi and starlette version mismatch srbhr/Resume-Matcher#543
- 5: https://pypi.org/project/fastapi/0.115.11/
- 6: https://github.com/fastapi/fastapi/releases/tag/0.115.3
- 7: ⬆️ Upgrade Starlette to
>=0.40.0,<0.42.0fastapi/fastapi#12469
🏁 Script executed:
# Check if there are any Python version specification files
git ls-files | grep -E '\.(cfg|toml|ini|yaml|yml)$' | grep -E '(setup|pyproject|pipfile)'Repository: Tashima-Tarsh/Disha
Length of output: 45
Correct the h11 vulnerability patch version and recommend explicit pinning.
The OSV scanner flagged a critical vulnerability in h11 (GHSA-vqfr-h8mv-ghfj) related to malformed Chunked-Encoding body handling, affecting h11 versions up to 0.15.0. The vulnerability is fixed in h11 >=0.16.0 (not 0.14.0).
h11 is a transitive dependency pulled in by uvicorn>=0.32.0 (which requires h11 >= 0.8). The current minimum versions do not guarantee a patched h11 version—the resolver could select any h11 version from 0.8 to 0.15.0, all of which are vulnerable. Add h11>=0.16.0 to this requirements file to ensure the vulnerability is resolved.
🧰 Tools
🪛 OSV Scanner (2.3.5)
[CRITICAL] 1-1: h11 0.9.0: h11 accepts some malformed Chunked-Encoding bodies
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cognitive-engine/requirements.txt` around lines 1 - 5, Update the dependency
pins in requirements.txt to explicitly ensure the h11 vulnerability is patched:
add an explicit h11>=0.16.0 entry (alongside the existing
uvicorn[standard]>=0.32.0) so the resolver cannot pick a vulnerable transitive
h11 (uvicorn pulls h11>=0.8); ensure the new line "h11>=0.16.0" is added to the
file to force the fixed version and consider pinning other critical transitive
deps explicitly if scanned as vulnerable.
| class FakeHypothesis: | ||
| mode = "deductive" | ||
| hypothesis = "evil.io is a C2 server" | ||
| confidence = 0.82 | ||
| chain_of_thought = ["It resolves to a known bad IP", "Pattern matches APT42"] | ||
|
|
||
| def __iter__(self): | ||
| return iter(self.__dict__.items()) | ||
|
|
||
| hyp = FakeHypothesis() | ||
| reas.reason = AsyncMock(return_value=[hyp]) | ||
| reas.select_best = MagicMock(return_value=hyp) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
FakeHypothesis.__iter__ is problematic and may not work as intended.
The __iter__ method returns iter(self.__dict__.items()), but __dict__ on an instance won't include class attributes like mode, hypothesis, confidence, and chain_of_thought since they're defined at the class level, not as instance attributes.
If the intent is to make dict(hyp) work, this will return an empty dict. Consider using instance attributes in __init__ instead:
♻️ Proposed fix
class FakeHypothesis:
- mode = "deductive"
- hypothesis = "evil.io is a C2 server"
- confidence = 0.82
- chain_of_thought = ["It resolves to a known bad IP", "Pattern matches APT42"]
-
- def __iter__(self):
- return iter(self.__dict__.items())
+ def __init__(self):
+ self.mode = "deductive"
+ self.hypothesis = "evil.io is a C2 server"
+ self.confidence = 0.82
+ self.chain_of_thought = ["It resolves to a known bad IP", "Pattern matches APT42"]
+
+ def __iter__(self):
+ return iter(vars(self).items())🧰 Tools
🪛 Ruff (0.15.9)
[warning] 77-77: Mutable default value for class attribute
(RUF012)
[warning] 79-79: Missing return type annotation for special method __iter__
(ANN204)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cognitive-engine/tests/test_cognitive_loop.py` around lines 73 - 84, The
FakeHypothesis.__iter__ returns iter(self.__dict__.items()) but the test defines
attributes at class level so dict(hyp) will be empty; fix by making
FakeHypothesis set mode, hypothesis, confidence, and chain_of_thought as
instance attributes in an __init__ (or change __iter__ to yield class
attributes), then keep reas.reason = AsyncMock(return_value=[hyp]) and
reas.select_best = MagicMock(return_value=hyp) unchanged so dict(hyp) and
iteration behave as expected.
| def test_get_session_ids(self, engine): | ||
| ids = engine.get_session_ids() | ||
| assert isinstance(ids, list) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Synchronous test using async fixture may cause issues.
test_get_session_ids is a synchronous test method but uses the engine fixture. While get_session_ids() itself is synchronous, if the fixture relies on async setup/teardown or if pytest-asyncio is configured in strict mode, this could behave unexpectedly.
♻️ Consider making the test async for consistency
+ `@pytest.mark.asyncio`
- def test_get_session_ids(self, engine):
+ async def test_get_session_ids(self, engine):
ids = engine.get_session_ids()
assert isinstance(ids, list)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cognitive-engine/tests/test_cognitive_loop.py` around lines 319 - 321, The
test method test_get_session_ids is synchronous but uses the engine fixture
which may be an async fixture; change the test to an async test (async def
test_get_session_ids(self, engine):) so pytest-asyncio can properly provide the
async fixture, then call engine.get_session_ids() as before (no await needed
since get_session_ids() is synchronous). Ensure the test name and the call to
get_session_ids() remain unchanged to locate the logic easily.
Pull Request
Summary
Type of Change
Component(s) Affected
src/)ai-platform/)cyber-defense/)historical-strategy/)quantum-physics/)integrations/)mcp-server/)Testing
Checklist
bun run lint/flake8passes)Related Issues
Summary by CodeRabbit
New Features
Tests