Compare commits

..

1 Commits

Author SHA1 Message Date
ي
6cef7c7257 Add capability-matrix checks and social action fallbacks 2026-05-18 16:02:08 +05:30
3 changed files with 115 additions and 108 deletions

View File

@@ -101,7 +101,6 @@ class AgentContextVFS:
"/steps/integrations": AgentFlatContextStore.STEP5_FILENAME,
}
HIGH_SIGNAL_MARKERS = ("agent_summary", "high_signal_terms", "quick_facts", "context_type")
LOW_CONFIDENCE_MARKER = "low_confidence"
def __init__(self, user_id: str, project_id: Optional[str] = None):
self.user_id = user_id
@@ -295,101 +294,6 @@ class AgentContextVFS:
)
return ranked[: max(1, top_k)]
@staticmethod
def _mnemonic_token(result: Dict[str, Any], rank: int) -> str:
"""Create compressed mnemonic token with source reference."""
path = str(result.get("path") or "unknown")
reason = str(result.get("reason") or "match")
confidence = float(result.get("confidence") or 0.0)
low_flag = "!" if result.get(AgentContextVFS.LOW_CONFIDENCE_MARKER) else ""
src = path.replace(".json", "").replace("_", "-")[:28]
hint = reason.replace(" ", "-")[:20]
return f"M{rank}:{src}|{hint}|c{confidence:.2f}{low_flag}"
@staticmethod
def _detect_contradictions(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Detect contradictory learnings by path with conflicting reasons/relevance classes."""
by_path: Dict[str, List[Dict[str, Any]]] = {}
for item in results:
p = str(item.get("path") or "")
by_path.setdefault(p, []).append(item)
contradictions: List[Dict[str, Any]] = []
for path, rows in by_path.items():
reasons = {str(r.get("reason") or "").strip().lower() for r in rows}
relevance = {str(r.get("relevance") or "").strip().lower() for r in rows}
# contradictory if both high/supported or mixed summary/body signals in same source cluster
if len(reasons) > 1 and len(relevance) > 1:
contradictions.append(
{
"path": path,
"reason_variants": sorted([r for r in reasons if r]),
"relevance_variants": sorted([r for r in relevance if r]),
"count": len(rows),
}
)
return contradictions
def _run_synthesis_pipeline(
self, ranked_results: List[Dict[str, Any]], *, char_budget: int = 1200, top_k: int = 5
) -> Dict[str, Any]:
"""
Flat-context synthesis pipeline:
1) Compress telemetry into mnemonic tokens with source references
2) Detect contradictions and mark low-confidence heuristics
3) Select top-ranked, budget-fitting tokens for prompt injection
4) Persist synthesis + source lineage for explainability
"""
contradictions = self._detect_contradictions(ranked_results)
contradiction_paths = {c["path"] for c in contradictions}
normalized: List[Dict[str, Any]] = []
for idx, item in enumerate(ranked_results, start=1):
row = dict(item)
low_conf = bool(row.get("low_probability")) or (str(row.get("path") or "") in contradiction_paths)
row[self.LOW_CONFIDENCE_MARKER] = low_conf
if low_conf:
row["confidence"] = round(max(0.05, float(row.get("confidence", 0.0)) * 0.7), 3)
row["mnemonic_token"] = self._mnemonic_token(row, idx)
normalized.append(row)
chosen: List[Dict[str, Any]] = []
used = 0
for row in normalized[: max(1, top_k * 3)]:
token = str(row.get("mnemonic_token") or "")
cost = len(token) + 8
if chosen and used + cost > char_budget:
continue
chosen.append(row)
used += cost
if len(chosen) >= top_k:
break
synthesis = {
"created_at": datetime.now(timezone.utc).isoformat(),
"top_k": top_k,
"char_budget": char_budget,
"char_budget_used": used,
"selected_mnemonics": [c.get("mnemonic_token") for c in chosen],
"source_lineage": [
{
"mnemonic_token": c.get("mnemonic_token"),
"path": c.get("path"),
"reason": c.get("reason"),
"confidence": c.get("confidence"),
"low_confidence": c.get(self.LOW_CONFIDENCE_MARKER, False),
}
for c in chosen
],
"contradictions": contradictions,
}
self.append_activity_log(
event_type="flat_context_synthesis",
actor="agent_context_vfs",
details=synthesis,
)
return {"ranked_results": normalized, "synthesis": synthesis}
@staticmethod
def _resolve_json_path(data: Any, path_query: str) -> Any:
"""Resolve dot/bracket JSON path such as 'data.seo_audit.recommendations[0]'."""
@@ -614,26 +518,15 @@ class AgentContextVFS:
bounded_results.append(r)
used += cost
synthesis_bundle = self._run_synthesis_pipeline(
self._static_triage(bounded_results, normalized),
char_budget=1200,
top_k=5,
)
triaged_results = synthesis_bundle["ranked_results"]
synthesis = synthesis_bundle["synthesis"]
result = {
"query": normalized,
"attempted_queries": attempted_queries,
"matched_files_count": len(matched_files),
"results": triaged_results,
"results": self._static_triage(bounded_results, normalized),
"notice": notice,
"char_budget_used": used,
"can_answer": bool(bounded_results),
"synthesis": synthesis,
"prompt_context_mnemonics": synthesis.get("selected_mnemonics", []),
}
# Top-ranked, budget-fitting mnemonic tokens are the only ones intended for prompt context injection.
result["triage_top5"] = self._llm_router_stub(result["results"], top_k=5)
logger.info(
f"[vfs_audit] user={self.store.safe_user_id} action=search_context query={normalized!r} results={len(result['results'])}"

View File

@@ -697,6 +697,39 @@ class BaseALwrityAgent(ABC):
"action_id": action.action_id,
"agent_id": self.agent_id,
}
capability_decision = self._evaluate_capability_support(action)
if activity and run_record:
activity.log_event(
event_type="decision",
severity="info" if capability_decision.get("supported", False) else "warning",
message=capability_decision.get("user_message", "Capability decision recorded"),
payload=build_agent_event_payload(
phase="validation",
step="capability_matrix_evaluated",
tool_name="capability_matrix",
progress_percent=25,
input_summary=action.action_type,
output_summary="Supported action" if capability_decision.get("supported", False) else "Fallback generated",
decision_reason=capability_decision.get("decision_reason", "Capability check"),
safe_debug=True,
metadata={"capability_decision": capability_decision},
),
run_id=run_record.id,
agent_type=self.agent_type,
)
if not capability_decision.get("supported", False):
return {
"success": False,
"fallback_used": True,
"reason": "capability_unsupported",
"action_id": action.action_id,
"agent_id": self.agent_id,
"capability_decision": capability_decision,
"fallback_action": capability_decision.get("fallback_action"),
"user_message": capability_decision.get("user_message"),
}
# 2. Create rollback checkpoint
try:
@@ -912,6 +945,83 @@ class BaseALwrityAgent(ABC):
Please execute this action and provide a detailed response.
Consider user goals, safety constraints, and potential impacts.
"""
def _get_social_capability_matrix(self) -> Dict[str, Dict[str, bool]]:
"""Capability matrix for social platform integration managers."""
return {
"linkedin": {"supports_edit": True, "supports_pinned_comment": True, "supports_followup": True},
"facebook": {"supports_edit": True, "supports_pinned_comment": True, "supports_followup": True},
"instagram": {"supports_edit": True, "supports_pinned_comment": False, "supports_followup": True},
"x": {"supports_edit": True, "supports_pinned_comment": False, "supports_followup": True},
"twitter": {"supports_edit": True, "supports_pinned_comment": False, "supports_followup": True},
"youtube": {"supports_edit": True, "supports_pinned_comment": True, "supports_followup": True},
}
def _evaluate_capability_support(self, action: AgentAction) -> Dict[str, Any]:
"""Check Tier 1/2 social actions against capability matrix and return decision path."""
platform = str(action.parameters.get("platform", "")).strip().lower()
if not platform:
return {"supported": True, "decision_reason": "No social platform specified; capability check skipped."}
matrix = self._get_social_capability_matrix()
platform_caps = matrix.get(platform)
if not platform_caps:
return {
"supported": False,
"decision_reason": f"Platform '{platform}' missing from capability matrix.",
"fallback_action": self._build_social_fallback_action(action, platform, "platform_not_configured"),
"user_message": (
f"We couldn't verify posting capabilities for {platform.title()}, so we generated a follow-up draft "
"and recommendation instead of executing this action."
),
}
action_tier = str(action.parameters.get("action_tier", "")).strip().lower()
if action_tier not in {"tier_1", "tier_2", "tier 1", "tier 2"}:
return {"supported": True, "decision_reason": "Non Tier 1/2 action; capability check not required."}
action_type = action.action_type.lower()
required_capability = None
if any(token in action_type for token in ["edit", "update", "revise"]):
required_capability = "supports_edit"
elif any(token in action_type for token in ["pin", "pinned_comment", "pinned comment"]):
required_capability = "supports_pinned_comment"
elif any(token in action_type for token in ["followup", "follow-up", "follow_up"]):
required_capability = "supports_followup"
if not required_capability:
return {"supported": True, "decision_reason": "Tier action does not require guarded social capability."}
supported = bool(platform_caps.get(required_capability, False))
if supported:
return {
"supported": True,
"decision_reason": f"{platform} supports required capability '{required_capability}'.",
"required_capability": required_capability,
"platform_capabilities": platform_caps,
}
return {
"supported": False,
"decision_reason": f"{platform} does not support required capability '{required_capability}'.",
"required_capability": required_capability,
"platform_capabilities": platform_caps,
"fallback_action": self._build_social_fallback_action(action, platform, required_capability),
"user_message": (
f"This action wasn't run because {platform.title()} does not support {required_capability}. "
"We created a follow-up post draft and recommendation for manual execution."
),
}
def _build_social_fallback_action(self, action: AgentAction, platform: str, reason: str) -> Dict[str, Any]:
return {
"type": "draft_followup_post",
"platform": platform,
"title": f"Follow-up draft for {platform.title()}",
"draft": f"Follow-up for original action '{action.action_type}' on {action.target_resource}.",
"recommendation": "Review and publish manually, then notify the team.",
"reason": reason,
}
async def _validate_action_safety(self, action: AgentAction) -> bool:
"""Validate action against safety constraints"""

View File

@@ -69,6 +69,10 @@ class SocialAmplificationAgent(BaseALwrityAgent):
# Instruction will be provided via orchestrator context or initial prompt
# Instruction should be provided during invocation or via orchestrator context
)
def get_social_integration_capabilities(self) -> Dict[str, Dict[str, bool]]:
"""Expose platform capability flags used by social integration managers."""
return self._get_social_capability_matrix()
# Tool Implementations