Initial commit: add .gitignore and README
This commit is contained in:
72
fusionagi/schemas/__init__.py
Normal file
72
fusionagi/schemas/__init__.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""Structured schemas for tasks, messages, plans, self-improvement, and AGI."""
|
||||
|
||||
from fusionagi.schemas.task import Task, TaskState, TaskPriority
|
||||
from fusionagi.schemas.messages import AgentMessage, AgentMessageEnvelope
|
||||
from fusionagi.schemas.plan import Plan, PlanStep
|
||||
from fusionagi.schemas.recommendation import (
|
||||
Recommendation,
|
||||
RecommendationKind,
|
||||
TrainingSuggestion,
|
||||
TrainingSuggestionKind,
|
||||
)
|
||||
from fusionagi.schemas.goal import Goal, GoalBudget, GoalStatus, Blocker, Checkpoint
|
||||
from fusionagi.schemas.grounding import Citation, GroundedClaim
|
||||
from fusionagi.schemas.skill import Skill, SkillKind, SkillVersionInfo
|
||||
from fusionagi.schemas.audit import AuditEntry, AuditEventType
|
||||
from fusionagi.schemas.policy import PolicyRule, PolicyEffect
|
||||
from fusionagi.schemas.world_model import StateTransition, UncertaintyInfo
|
||||
from fusionagi.schemas.head import HeadId, HeadClaim, HeadRisk, HeadOutput
|
||||
from fusionagi.schemas.witness import AgreementMap, TransparencyReport, FinalResponse
|
||||
from fusionagi.schemas.commands import UserIntent, ParsedCommand, parse_user_input
|
||||
from fusionagi.schemas.atomic import (
|
||||
AtomicUnitType,
|
||||
RelationType,
|
||||
AtomicSemanticUnit,
|
||||
SemanticRelation,
|
||||
DecompositionResult,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Task",
|
||||
"TaskState",
|
||||
"TaskPriority",
|
||||
"AgentMessage",
|
||||
"AgentMessageEnvelope",
|
||||
"Plan",
|
||||
"PlanStep",
|
||||
"Recommendation",
|
||||
"RecommendationKind",
|
||||
"TrainingSuggestion",
|
||||
"TrainingSuggestionKind",
|
||||
"Goal",
|
||||
"GoalBudget",
|
||||
"GoalStatus",
|
||||
"Blocker",
|
||||
"Checkpoint",
|
||||
"Citation",
|
||||
"GroundedClaim",
|
||||
"Skill",
|
||||
"SkillKind",
|
||||
"SkillVersionInfo",
|
||||
"AuditEntry",
|
||||
"AuditEventType",
|
||||
"PolicyRule",
|
||||
"PolicyEffect",
|
||||
"StateTransition",
|
||||
"UncertaintyInfo",
|
||||
"HeadId",
|
||||
"HeadClaim",
|
||||
"HeadRisk",
|
||||
"HeadOutput",
|
||||
"AgreementMap",
|
||||
"TransparencyReport",
|
||||
"FinalResponse",
|
||||
"UserIntent",
|
||||
"ParsedCommand",
|
||||
"parse_user_input",
|
||||
"AtomicUnitType",
|
||||
"RelationType",
|
||||
"AtomicSemanticUnit",
|
||||
"SemanticRelation",
|
||||
"DecompositionResult",
|
||||
]
|
||||
65
fusionagi/schemas/atomic.py
Normal file
65
fusionagi/schemas/atomic.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""Atomic semantic units for Super Big Brain: tokenless, addressable context."""
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AtomicUnitType(str, Enum):
|
||||
"""Type of atomic semantic unit."""
|
||||
|
||||
FACT = "fact"
|
||||
INTENT = "intent"
|
||||
ASSUMPTION = "assumption"
|
||||
CONSTRAINT = "constraint"
|
||||
RELATIONSHIP = "relationship"
|
||||
QUESTION = "question"
|
||||
|
||||
|
||||
class RelationType(str, Enum):
|
||||
"""Type of relation between atomic units."""
|
||||
|
||||
CAUSAL = "causal"
|
||||
TEMPORAL = "temporal"
|
||||
LOGICAL = "logical"
|
||||
ANALOGICAL = "analogical"
|
||||
CONTRADICTS = "contradicts"
|
||||
SUPPORTS = "supports"
|
||||
|
||||
|
||||
class AtomicSemanticUnit(BaseModel):
|
||||
"""Atomic semantic unit: irreducible fact, intent, assumption, or constraint."""
|
||||
|
||||
unit_id: str = Field(..., min_length=1, description="Unique identifier")
|
||||
content: str = Field(..., min_length=1, description="Unit content")
|
||||
type: AtomicUnitType = Field(..., description="Unit type")
|
||||
confidence: float = Field(default=1.0, ge=0.0, le=1.0, description="Confidence in [0, 1]")
|
||||
parent_id: str | None = Field(default=None, description="Parent unit in decomposition tree")
|
||||
source_ref: str | None = Field(default=None, description="Source reference")
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class SemanticRelation(BaseModel):
|
||||
"""Relation between two atomic semantic units."""
|
||||
|
||||
from_id: str = Field(..., min_length=1)
|
||||
to_id: str = Field(..., min_length=1)
|
||||
relation_type: RelationType = Field(...)
|
||||
|
||||
|
||||
class DecompositionResult(BaseModel):
|
||||
"""Result of recursive decomposition: atomic units and relations."""
|
||||
|
||||
units: list[AtomicSemanticUnit] = Field(default_factory=list)
|
||||
relations: list[SemanticRelation] = Field(default_factory=list)
|
||||
depth: int = Field(default=0, ge=0, description="Maximum decomposition depth")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AtomicUnitType",
|
||||
"RelationType",
|
||||
"AtomicSemanticUnit",
|
||||
"SemanticRelation",
|
||||
"DecompositionResult",
|
||||
]
|
||||
38
fusionagi/schemas/audit.py
Normal file
38
fusionagi/schemas/audit.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Audit log schemas for AGI governance."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
def _utc_now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
class AuditEventType(str, Enum):
|
||||
"""Type of auditable event."""
|
||||
|
||||
DECISION = "decision"
|
||||
TOOL_CALL = "tool_call"
|
||||
DATA_SOURCE = "data_source"
|
||||
STATE_CHANGE = "state_change"
|
||||
TASK_SUBMIT = "task_submit"
|
||||
TASK_COMPLETE = "task_complete"
|
||||
OVERRIDE = "override"
|
||||
POLICY_CHECK = "policy_check"
|
||||
OTHER = "other"
|
||||
|
||||
|
||||
class AuditEntry(BaseModel):
|
||||
"""Single audit log entry: every material decision, tool call, source, outcome."""
|
||||
|
||||
entry_id: str = Field(..., min_length=1)
|
||||
event_type: AuditEventType = Field(default=AuditEventType.OTHER)
|
||||
actor: str = Field(default="", description="Agent or system component")
|
||||
task_id: str | None = Field(default=None)
|
||||
action: str = Field(default="")
|
||||
payload: dict[str, Any] = Field(default_factory=dict)
|
||||
outcome: str = Field(default="")
|
||||
timestamp: datetime = Field(default_factory=_utc_now)
|
||||
93
fusionagi/schemas/commands.py
Normal file
93
fusionagi/schemas/commands.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Interaction commands and user intent for Dvādaśa UX."""
|
||||
|
||||
import re
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from fusionagi.schemas.head import HeadId
|
||||
|
||||
|
||||
class UserIntent(str, Enum):
|
||||
"""User intent derived from commands or natural input."""
|
||||
|
||||
NORMAL = "normal"
|
||||
HEAD_STRATEGY = "head_strategy"
|
||||
SHOW_DISSENT = "show_dissent"
|
||||
RERUN_RISK = "rerun_risk"
|
||||
EXPLAIN_REASONING = "explain_reasoning"
|
||||
SOURCES = "sources"
|
||||
|
||||
|
||||
# Command patterns: /command [arg] or /command \n prompt
|
||||
_COMMAND_PATTERNS: list[tuple[re.Pattern[str], UserIntent]] = [
|
||||
(re.compile(r"^/head\s+(\w+)(?:\s+(.+))?\s*$", re.I | re.DOTALL), UserIntent.HEAD_STRATEGY),
|
||||
(re.compile(r"^/show\s+dissent(?:\s+(.+))?\s*$", re.I | re.DOTALL), UserIntent.SHOW_DISSENT),
|
||||
(re.compile(r"^/rerun\s+risk(?:\s+(.+))?\s*$", re.I | re.DOTALL), UserIntent.RERUN_RISK),
|
||||
(re.compile(r"^/explain\s+reasoning(?:\s+(.+))?\s*$", re.I | re.DOTALL), UserIntent.EXPLAIN_REASONING),
|
||||
(re.compile(r"^/sources(?:\s+(.+))?\s*$", re.I | re.DOTALL), UserIntent.SOURCES),
|
||||
]
|
||||
|
||||
|
||||
class ParsedCommand(BaseModel):
|
||||
"""Parsed user command with intent and optional arguments."""
|
||||
|
||||
intent: UserIntent = Field(..., description="Detected intent")
|
||||
head_id: HeadId | None = Field(default=None, description="For HEAD_STRATEGY: which head")
|
||||
raw_input: str = Field(default="", description="Original user input")
|
||||
cleaned_prompt: str = Field(default="", description="Prompt with command stripped if applicable")
|
||||
|
||||
|
||||
def parse_user_input(text: str) -> ParsedCommand:
|
||||
"""
|
||||
Parse user input to detect commands and extract intent.
|
||||
|
||||
Examples:
|
||||
"/head strategy" -> HEAD_STRATEGY, head_id=STRATEGY
|
||||
"/show dissent" -> SHOW_DISSENT
|
||||
"What is X?" -> NORMAL, cleaned_prompt="What is X?"
|
||||
"""
|
||||
stripped = (text or "").strip()
|
||||
if not stripped:
|
||||
return ParsedCommand(
|
||||
intent=UserIntent.NORMAL,
|
||||
raw_input=stripped,
|
||||
cleaned_prompt=stripped,
|
||||
)
|
||||
|
||||
for pattern, intent in _COMMAND_PATTERNS:
|
||||
match = pattern.match(stripped)
|
||||
if match:
|
||||
head_id = None
|
||||
cleaned = ""
|
||||
if intent == UserIntent.HEAD_STRATEGY:
|
||||
arg = (match.group(1) or "").lower()
|
||||
rest = match.group(2) if match.lastindex and match.lastindex >= 2 else None
|
||||
cleaned = (rest or "").strip()
|
||||
try:
|
||||
head_id = HeadId(arg)
|
||||
except ValueError:
|
||||
head_id = None
|
||||
else:
|
||||
rest = match.group(1) if match.lastindex and match.lastindex >= 1 else None
|
||||
cleaned = (rest or "").strip()
|
||||
return ParsedCommand(
|
||||
intent=intent,
|
||||
head_id=head_id,
|
||||
raw_input=stripped,
|
||||
cleaned_prompt=cleaned,
|
||||
)
|
||||
|
||||
return ParsedCommand(
|
||||
intent=UserIntent.NORMAL,
|
||||
raw_input=stripped,
|
||||
cleaned_prompt=stripped,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"UserIntent",
|
||||
"ParsedCommand",
|
||||
"parse_user_input",
|
||||
]
|
||||
70
fusionagi/schemas/goal.py
Normal file
70
fusionagi/schemas/goal.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Goal and budget schemas for AGI executive layer."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
def _utc_now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
class GoalStatus(str, Enum):
|
||||
"""Goal lifecycle status."""
|
||||
|
||||
ACTIVE = "active"
|
||||
ACHIEVED = "achieved"
|
||||
ABANDONED = "abandoned"
|
||||
BLOCKED = "blocked"
|
||||
SUSPENDED = "suspended"
|
||||
|
||||
|
||||
class GoalBudget(BaseModel):
|
||||
"""Time and compute budget for a goal/task."""
|
||||
|
||||
time_seconds: float | None = Field(default=None, ge=0, description="Max wall-clock seconds")
|
||||
compute_budget: float | None = Field(default=None, ge=0, description="Max compute units (e.g. token budget)")
|
||||
started_at: datetime | None = Field(default=None, description="When execution started")
|
||||
|
||||
|
||||
class Blocker(BaseModel):
|
||||
"""Reason a task or goal is stuck."""
|
||||
|
||||
blocker_id: str = Field(..., min_length=1)
|
||||
task_id: str = Field(..., min_length=1)
|
||||
reason: str = Field(default="")
|
||||
dependency_step_id: str | None = Field(default=None)
|
||||
reported_at: datetime = Field(default_factory=_utc_now)
|
||||
|
||||
|
||||
class Checkpoint(BaseModel):
|
||||
"""Resumable point in task execution."""
|
||||
|
||||
checkpoint_id: str = Field(..., min_length=1)
|
||||
task_id: str = Field(..., min_length=1)
|
||||
step_ids_completed: list[str] = Field(default_factory=list)
|
||||
state_snapshot: dict[str, Any] = Field(default_factory=dict)
|
||||
created_at: datetime = Field(default_factory=_utc_now)
|
||||
|
||||
|
||||
class Goal(BaseModel):
|
||||
"""Explicit goal with objectives, priorities, constraints, and budget."""
|
||||
|
||||
goal_id: str = Field(..., min_length=1)
|
||||
objective: str = Field(..., min_length=1)
|
||||
constraints: list[str] = Field(default_factory=list)
|
||||
priority: int = Field(default=0, ge=0, le=10)
|
||||
status: GoalStatus = Field(default=GoalStatus.ACTIVE)
|
||||
budget: GoalBudget | None = Field(default=None)
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
created_at: datetime = Field(default_factory=_utc_now)
|
||||
updated_at: datetime | None = Field(default=None)
|
||||
|
||||
@field_validator("goal_id", "objective")
|
||||
@classmethod
|
||||
def validate_non_whitespace(cls, v: str) -> str:
|
||||
if not v.strip():
|
||||
raise ValueError("Field cannot be empty or whitespace")
|
||||
return v
|
||||
30
fusionagi/schemas/grounding.py
Normal file
30
fusionagi/schemas/grounding.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Grounding schemas: citations, sources, show-your-work for AGI verification."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
def _utc_now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
class Citation(BaseModel):
|
||||
"""Reference to a source supporting a claim."""
|
||||
|
||||
source_id: str = Field(..., min_length=1, description="e.g. doc id, URL, trace step")
|
||||
excerpt: str = Field(default="", description="Relevant excerpt or quote")
|
||||
confidence: float = Field(default=1.0, ge=0.0, le=1.0)
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class GroundedClaim(BaseModel):
|
||||
"""A claim with citations and optional show-your-work trace."""
|
||||
|
||||
claim_id: str = Field(..., min_length=1)
|
||||
claim: str = Field(..., min_length=1)
|
||||
citations: list[Citation] = Field(default_factory=list)
|
||||
reasoning_trace: list[dict[str, Any]] = Field(default_factory=list, description="Show-your-work steps")
|
||||
confidence: float = Field(default=1.0, ge=0.0, le=1.0)
|
||||
created_at: datetime = Field(default_factory=_utc_now)
|
||||
61
fusionagi/schemas/head.py
Normal file
61
fusionagi/schemas/head.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Dvādaśa head output schemas: claims, risks, structured outputs per head."""
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from fusionagi.schemas.grounding import Citation
|
||||
|
||||
|
||||
class HeadId(str, Enum):
|
||||
"""Identifiers for the 11 content heads plus Witness meta-controller."""
|
||||
|
||||
LOGIC = "logic"
|
||||
RESEARCH = "research"
|
||||
SYSTEMS = "systems"
|
||||
STRATEGY = "strategy"
|
||||
PRODUCT = "product"
|
||||
SECURITY = "security"
|
||||
SAFETY = "safety"
|
||||
RELIABILITY = "reliability"
|
||||
COST = "cost"
|
||||
DATA = "data"
|
||||
DEVEX = "devex"
|
||||
WITNESS = "witness"
|
||||
|
||||
|
||||
class HeadClaim(BaseModel):
|
||||
"""Atomic statement from a head with confidence and evidence."""
|
||||
|
||||
claim_text: str = Field(..., min_length=1, description="The atomic claim statement")
|
||||
confidence: float = Field(..., ge=0.0, le=1.0, description="Confidence in [0, 1]")
|
||||
evidence: list[Citation] = Field(default_factory=list, description="Citations, tool results, reasoning steps")
|
||||
assumptions: list[str] = Field(default_factory=list, description="Assumptions made")
|
||||
|
||||
|
||||
class HeadRisk(BaseModel):
|
||||
"""Risk or failure mode identified by a head."""
|
||||
|
||||
description: str = Field(..., min_length=1, description="Description of the risk")
|
||||
severity: str = Field(default="medium", description="low, medium, high, critical")
|
||||
|
||||
|
||||
class HeadOutput(BaseModel):
|
||||
"""Structured output from a Dvādaśa head."""
|
||||
|
||||
head_id: HeadId = Field(..., description="Which head produced this")
|
||||
summary: str = Field(..., min_length=1, description="1–3 sentence summary")
|
||||
claims: list[HeadClaim] = Field(default_factory=list, description="Atomic claims with confidence")
|
||||
risks: list[HeadRisk] = Field(default_factory=list, description="Failure modes identified")
|
||||
questions: list[str] = Field(default_factory=list, description="Only if absolutely necessary")
|
||||
recommended_actions: list[str] = Field(default_factory=list, description="Suggested next steps")
|
||||
tone_guidance: str = Field(default="", description="For persona consistency")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"HeadId",
|
||||
"HeadClaim",
|
||||
"HeadRisk",
|
||||
"HeadOutput",
|
||||
]
|
||||
94
fusionagi/schemas/messages.py
Normal file
94
fusionagi/schemas/messages.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""Agent message schema: sender, recipient, intent, payload, confidence/uncertainty."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from fusionagi._time import utc_now
|
||||
|
||||
|
||||
class AgentMessage(BaseModel):
|
||||
"""
|
||||
Structured message between agents.
|
||||
|
||||
Includes validation for:
|
||||
- Non-empty sender, recipient, and intent
|
||||
- Confidence in valid [0, 1] range
|
||||
"""
|
||||
|
||||
sender: str = Field(..., min_length=1, description="Agent id of sender")
|
||||
recipient: str = Field(..., min_length=1, description="Agent id of recipient")
|
||||
intent: str = Field(..., min_length=1, description="Message intent e.g. plan_ready, execute_step")
|
||||
payload: dict[str, Any] = Field(default_factory=dict, description="Message payload")
|
||||
confidence: float | None = Field(default=None, ge=0.0, le=1.0, description="Optional confidence [0,1]")
|
||||
uncertainty: str | None = Field(default=None, description="Optional uncertainty note")
|
||||
timestamp: datetime = Field(default_factory=utc_now, description="Message timestamp")
|
||||
|
||||
@field_validator("sender", "recipient", "intent")
|
||||
@classmethod
|
||||
def validate_non_whitespace(cls, v: str) -> str:
|
||||
"""Validate string fields are not just whitespace."""
|
||||
if not v.strip():
|
||||
raise ValueError("Field cannot be empty or whitespace")
|
||||
return v
|
||||
|
||||
@field_validator("confidence")
|
||||
@classmethod
|
||||
def validate_confidence(cls, v: float | None) -> float | None:
|
||||
"""Validate confidence is in [0, 1] range."""
|
||||
if v is not None and (v < 0.0 or v > 1.0):
|
||||
raise ValueError("confidence must be between 0 and 1")
|
||||
return v
|
||||
|
||||
|
||||
class AgentMessageEnvelope(BaseModel):
|
||||
"""
|
||||
Top-level envelope for agent messages; can carry task context.
|
||||
|
||||
The envelope wraps a message and provides additional context:
|
||||
- task_id: Associates the message with a specific task
|
||||
- correlation_id: Enables request/response tracking
|
||||
"""
|
||||
|
||||
message: AgentMessage = Field(..., description="The wrapped message")
|
||||
task_id: str | None = Field(default=None, description="Associated task id if any")
|
||||
correlation_id: str | None = Field(default=None, description="For request/response correlation")
|
||||
|
||||
@property
|
||||
def sender(self) -> str:
|
||||
"""Convenience accessor for message sender."""
|
||||
return self.message.sender
|
||||
|
||||
@property
|
||||
def recipient(self) -> str:
|
||||
"""Convenience accessor for message recipient."""
|
||||
return self.message.recipient
|
||||
|
||||
@property
|
||||
def intent(self) -> str:
|
||||
"""Convenience accessor for message intent."""
|
||||
return self.message.intent
|
||||
|
||||
def create_response(
|
||||
self,
|
||||
intent: str,
|
||||
payload: dict[str, Any] | None = None,
|
||||
confidence: float | None = None,
|
||||
) -> "AgentMessageEnvelope":
|
||||
"""
|
||||
Create a response envelope to this message.
|
||||
|
||||
Swaps sender/recipient and preserves task_id and correlation_id.
|
||||
"""
|
||||
return AgentMessageEnvelope(
|
||||
message=AgentMessage(
|
||||
sender=self.message.recipient,
|
||||
recipient=self.message.sender,
|
||||
intent=intent,
|
||||
payload=payload or {},
|
||||
confidence=confidence,
|
||||
),
|
||||
task_id=self.task_id,
|
||||
correlation_id=self.correlation_id,
|
||||
)
|
||||
201
fusionagi/schemas/plan.py
Normal file
201
fusionagi/schemas/plan.py
Normal file
@@ -0,0 +1,201 @@
|
||||
"""Plan schema: steps with ids, dependencies, optional fallback paths with validation."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
|
||||
|
||||
class PlanStep(BaseModel):
|
||||
"""
|
||||
Single step in a plan.
|
||||
|
||||
Validation:
|
||||
- id and description must be non-empty
|
||||
"""
|
||||
|
||||
id: str = Field(..., min_length=1, description="Step identifier")
|
||||
description: str = Field(..., min_length=1, description="What to do")
|
||||
dependencies: list[str] = Field(default_factory=list, description="Step ids that must complete first")
|
||||
tool_name: str | None = Field(default=None, description="Optional tool to invoke")
|
||||
tool_args: dict[str, Any] = Field(default_factory=dict, description="Optional tool arguments")
|
||||
metadata: dict[str, Any] = Field(default_factory=dict, description="Extra data")
|
||||
|
||||
@field_validator("id", "description")
|
||||
@classmethod
|
||||
def validate_non_whitespace(cls, v: str) -> str:
|
||||
"""Validate string fields are not just whitespace."""
|
||||
if not v.strip():
|
||||
raise ValueError("Field cannot be empty or whitespace")
|
||||
return v
|
||||
|
||||
|
||||
class Plan(BaseModel):
|
||||
"""
|
||||
Plan graph: steps and optional fallback paths.
|
||||
|
||||
Validation:
|
||||
- No duplicate step IDs
|
||||
- All dependency references must be valid step IDs
|
||||
- All fallback path references must be valid step IDs
|
||||
- No circular dependencies
|
||||
"""
|
||||
|
||||
steps: list[PlanStep] = Field(default_factory=list, description="Ordered steps")
|
||||
fallback_paths: list[list[str]] = Field(default_factory=list, description="Alternative step sequences")
|
||||
metadata: dict[str, Any] = Field(default_factory=dict, description="Plan-level metadata")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_plan(self) -> "Plan":
|
||||
"""Validate the entire plan structure."""
|
||||
step_ids = {s.id for s in self.steps}
|
||||
|
||||
# Check for duplicate step IDs
|
||||
if len(step_ids) != len(self.steps):
|
||||
seen = set()
|
||||
duplicates = []
|
||||
for s in self.steps:
|
||||
if s.id in seen:
|
||||
duplicates.append(s.id)
|
||||
seen.add(s.id)
|
||||
raise ValueError(f"Duplicate step IDs: {duplicates}")
|
||||
|
||||
# Check all dependency references are valid
|
||||
for step in self.steps:
|
||||
invalid_deps = [d for d in step.dependencies if d not in step_ids]
|
||||
if invalid_deps:
|
||||
raise ValueError(
|
||||
f"Step '{step.id}' has invalid dependencies: {invalid_deps}"
|
||||
)
|
||||
|
||||
# Check all fallback path references are valid
|
||||
for i, path in enumerate(self.fallback_paths):
|
||||
invalid_refs = [ref for ref in path if ref not in step_ids]
|
||||
if invalid_refs:
|
||||
raise ValueError(
|
||||
f"Fallback path {i} has invalid step references: {invalid_refs}"
|
||||
)
|
||||
|
||||
# Check for circular dependencies
|
||||
cycles = self._find_cycles()
|
||||
if cycles:
|
||||
raise ValueError(f"Circular dependencies detected: {cycles}")
|
||||
|
||||
return self
|
||||
|
||||
def _find_cycles(self) -> list[list[str]]:
|
||||
"""Find circular dependencies in the plan graph using DFS."""
|
||||
# Build adjacency list
|
||||
graph: dict[str, list[str]] = {s.id: list(s.dependencies) for s in self.steps}
|
||||
|
||||
cycles = []
|
||||
visited = set()
|
||||
rec_stack = set()
|
||||
path = []
|
||||
|
||||
def dfs(node: str) -> bool:
|
||||
visited.add(node)
|
||||
rec_stack.add(node)
|
||||
path.append(node)
|
||||
|
||||
for neighbor in graph.get(node, []):
|
||||
if neighbor not in visited:
|
||||
if dfs(neighbor):
|
||||
return True
|
||||
elif neighbor in rec_stack:
|
||||
# Found cycle
|
||||
cycle_start = path.index(neighbor)
|
||||
cycles.append(path[cycle_start:] + [neighbor])
|
||||
return True
|
||||
|
||||
path.pop()
|
||||
rec_stack.remove(node)
|
||||
return False
|
||||
|
||||
for step_id in graph:
|
||||
if step_id not in visited:
|
||||
dfs(step_id)
|
||||
|
||||
return cycles
|
||||
|
||||
def step_ids(self) -> list[str]:
|
||||
"""Return step ids in order."""
|
||||
return [s.id for s in self.steps]
|
||||
|
||||
def get_step(self, step_id: str) -> PlanStep | None:
|
||||
"""Get a step by ID."""
|
||||
for step in self.steps:
|
||||
if step.id == step_id:
|
||||
return step
|
||||
return None
|
||||
|
||||
def get_dependencies(self, step_id: str) -> list[PlanStep]:
|
||||
"""Get all dependency steps for a given step."""
|
||||
step = self.get_step(step_id)
|
||||
if not step:
|
||||
return []
|
||||
return [s for s in self.steps if s.id in step.dependencies]
|
||||
|
||||
def get_dependents(self, step_id: str) -> list[PlanStep]:
|
||||
"""Get all steps that depend on the given step."""
|
||||
return [s for s in self.steps if step_id in s.dependencies]
|
||||
|
||||
def topological_order(self) -> list[str]:
|
||||
"""
|
||||
Return step IDs in topological order (dependencies first).
|
||||
|
||||
Uses Kahn's algorithm.
|
||||
"""
|
||||
# Build in-degree map
|
||||
in_degree = {s.id: len(s.dependencies) for s in self.steps}
|
||||
# Build adjacency list (reverse direction for dependents)
|
||||
dependents: dict[str, list[str]] = {s.id: [] for s in self.steps}
|
||||
for step in self.steps:
|
||||
for dep in step.dependencies:
|
||||
if dep in dependents:
|
||||
dependents[dep].append(step.id)
|
||||
|
||||
# Start with nodes that have no dependencies
|
||||
queue = [sid for sid, deg in in_degree.items() if deg == 0]
|
||||
result = []
|
||||
|
||||
while queue:
|
||||
node = queue.pop(0)
|
||||
result.append(node)
|
||||
for dependent in dependents.get(node, []):
|
||||
in_degree[dependent] -= 1
|
||||
if in_degree[dependent] == 0:
|
||||
queue.append(dependent)
|
||||
|
||||
# Add any remaining nodes (would indicate cycles, but we validate above)
|
||||
remaining = [sid for sid in in_degree if sid not in result]
|
||||
result.extend(remaining)
|
||||
|
||||
return result
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""Serialize for message payload / state."""
|
||||
return {
|
||||
"steps": [s.model_dump() for s in self.steps],
|
||||
"fallback_paths": self.fallback_paths,
|
||||
"metadata": self.metadata,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict[str, Any]) -> "Plan":
|
||||
"""Deserialize from dict. Steps may be dicts (validated) or PlanStep instances."""
|
||||
if not isinstance(d, dict):
|
||||
raise TypeError(f"Plan.from_dict expects dict, got {type(d).__name__}")
|
||||
raw_steps = d.get("steps", [])
|
||||
steps: list[PlanStep] = []
|
||||
for s in raw_steps:
|
||||
if isinstance(s, PlanStep):
|
||||
steps.append(s)
|
||||
elif isinstance(s, dict):
|
||||
steps.append(PlanStep.model_validate(s))
|
||||
else:
|
||||
raise TypeError(f"Step must be dict or PlanStep, got {type(s).__name__}")
|
||||
return cls(
|
||||
steps=steps,
|
||||
fallback_paths=d.get("fallback_paths", []),
|
||||
metadata=d.get("metadata", {}),
|
||||
)
|
||||
29
fusionagi/schemas/policy.py
Normal file
29
fusionagi/schemas/policy.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""Policy engine schemas: hard constraints independent of LLM."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
def _utc_now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
class PolicyEffect(str, Enum):
|
||||
"""Allow or deny."""
|
||||
|
||||
ALLOW = "allow"
|
||||
DENY = "deny"
|
||||
|
||||
|
||||
class PolicyRule(BaseModel):
|
||||
"""Single policy rule: condition -> effect."""
|
||||
|
||||
rule_id: str = Field(..., min_length=1)
|
||||
effect: PolicyEffect = Field(...)
|
||||
condition: dict[str, Any] = Field(default_factory=dict, description="e.g. tool_name, domain, data_class")
|
||||
reason: str = Field(default="")
|
||||
priority: int = Field(default=0, ge=0, description="Higher = evaluated first")
|
||||
created_at: datetime = Field(default_factory=_utc_now)
|
||||
92
fusionagi/schemas/recommendation.py
Normal file
92
fusionagi/schemas/recommendation.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""Schemas for self-improvement: recommendations and training suggestions."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
def _utc_now() -> datetime:
|
||||
"""Return current UTC time (timezone-aware)."""
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
class RecommendationKind(str, Enum):
|
||||
"""Type of auto-generated recommendation."""
|
||||
|
||||
NEXT_ACTION = "next_action"
|
||||
TRAINING_TARGET = "training_target"
|
||||
TOOL_ADDITION = "tool_addition"
|
||||
STRATEGY_CHANGE = "strategy_change"
|
||||
GUARDRAIL_UPDATE = "guardrail_update"
|
||||
OTHER = "other"
|
||||
|
||||
|
||||
class Recommendation(BaseModel):
|
||||
"""
|
||||
Auto-generated recommendation from reflection and lessons.
|
||||
Used by the auto-recommend/suggest pipeline.
|
||||
"""
|
||||
|
||||
kind: RecommendationKind = Field(
|
||||
default=RecommendationKind.OTHER,
|
||||
description="Category of recommendation",
|
||||
)
|
||||
title: str = Field(..., min_length=1, description="Short title")
|
||||
description: str = Field(default="", description="Detailed description")
|
||||
payload: dict[str, Any] = Field(default_factory=dict, description="Structured data")
|
||||
source_task_id: str | None = Field(default=None, description="Task that triggered this")
|
||||
priority: int = Field(default=0, ge=0, le=10, description="0=low, 10=critical")
|
||||
created_at: datetime = Field(default_factory=_utc_now, description="Creation time")
|
||||
|
||||
@field_validator("title")
|
||||
@classmethod
|
||||
def validate_title(cls, v: str) -> str:
|
||||
"""Ensure title is not just whitespace."""
|
||||
if not v.strip():
|
||||
raise ValueError("title cannot be empty or whitespace")
|
||||
return v
|
||||
|
||||
|
||||
class TrainingSuggestionKind(str, Enum):
|
||||
"""Type of auto-training suggestion."""
|
||||
|
||||
HEURISTIC_UPDATE = "heuristic_update"
|
||||
PROMPT_TUNING = "prompt_tuning"
|
||||
FINE_TUNE_DATASET = "fine_tune_dataset"
|
||||
STRATEGY_PARAM = "strategy_param"
|
||||
OTHER = "other"
|
||||
|
||||
|
||||
class TrainingSuggestion(BaseModel):
|
||||
"""
|
||||
Auto-generated training suggestion from reflection and failures.
|
||||
Can be applied (e.g. heuristic update) or emitted for external training pipelines.
|
||||
"""
|
||||
|
||||
kind: TrainingSuggestionKind = Field(
|
||||
default=TrainingSuggestionKind.OTHER,
|
||||
description="Type of training",
|
||||
)
|
||||
key: str = Field(..., min_length=1, description="Target key (e.g. heuristic name)")
|
||||
value: Any = Field(..., description="Value to apply or dataset description")
|
||||
source_task_id: str | None = Field(default=None, description="Task that triggered this")
|
||||
reason: str = Field(default="", description="Why this suggestion was generated")
|
||||
created_at: datetime = Field(default_factory=_utc_now, description="Creation time")
|
||||
|
||||
@field_validator("key")
|
||||
@classmethod
|
||||
def validate_key(cls, v: str) -> str:
|
||||
"""Ensure key is not just whitespace."""
|
||||
if not v.strip():
|
||||
raise ValueError("key cannot be empty or whitespace")
|
||||
return v
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Recommendation",
|
||||
"RecommendationKind",
|
||||
"TrainingSuggestion",
|
||||
"TrainingSuggestionKind",
|
||||
]
|
||||
52
fusionagi/schemas/skill.py
Normal file
52
fusionagi/schemas/skill.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Skill schemas for AGI skill library and compilation."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
def _utc_now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
class SkillKind(str, Enum):
|
||||
"""Type of skill."""
|
||||
|
||||
WORKFLOW = "workflow"
|
||||
PROCEDURE = "procedure"
|
||||
TOOL_NATIVE = "tool_native"
|
||||
COMPOSITE = "composite"
|
||||
|
||||
|
||||
class Skill(BaseModel):
|
||||
"""Reusable skill: declarative description + executable steps."""
|
||||
|
||||
skill_id: str = Field(..., min_length=1)
|
||||
name: str = Field(..., min_length=1)
|
||||
description: str = Field(default="")
|
||||
kind: SkillKind = Field(default=SkillKind.WORKFLOW)
|
||||
steps: list[dict[str, Any]] = Field(default_factory=list, description="Declarative steps (plan-like)")
|
||||
tool_names: list[str] = Field(default_factory=list, description="Tools this skill uses")
|
||||
version: int = Field(default=1, ge=1)
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
created_at: datetime = Field(default_factory=_utc_now)
|
||||
|
||||
@field_validator("skill_id", "name")
|
||||
@classmethod
|
||||
def validate_non_whitespace(cls, v: str) -> str:
|
||||
if not v.strip():
|
||||
raise ValueError("Field cannot be empty or whitespace")
|
||||
return v
|
||||
|
||||
|
||||
class SkillVersionInfo(BaseModel):
|
||||
"""Version and performance tracking for a skill."""
|
||||
|
||||
skill_id: str = Field(..., min_length=1)
|
||||
version: int = Field(..., ge=1)
|
||||
success_count: int = Field(default=0, ge=0)
|
||||
failure_count: int = Field(default=0, ge=0)
|
||||
last_success_at: datetime | None = Field(default=None)
|
||||
regression_test_ids: list[str] = Field(default_factory=list)
|
||||
106
fusionagi/schemas/task.py
Normal file
106
fusionagi/schemas/task.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""Task schema: goal, constraints, priority, state with validation."""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
|
||||
from fusionagi._time import utc_now
|
||||
|
||||
|
||||
class TaskState(str, Enum):
|
||||
"""Lifecycle state of a task."""
|
||||
|
||||
PENDING = "pending"
|
||||
ACTIVE = "active"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
|
||||
class TaskPriority(str, Enum):
|
||||
"""Task priority level."""
|
||||
|
||||
LOW = "low"
|
||||
NORMAL = "normal"
|
||||
HIGH = "high"
|
||||
CRITICAL = "critical"
|
||||
|
||||
|
||||
# Valid state transitions for validation
|
||||
VALID_TASK_TRANSITIONS: dict[TaskState, set[TaskState]] = {
|
||||
TaskState.PENDING: {TaskState.ACTIVE, TaskState.CANCELLED},
|
||||
TaskState.ACTIVE: {TaskState.COMPLETED, TaskState.FAILED, TaskState.CANCELLED},
|
||||
TaskState.COMPLETED: set(), # Terminal
|
||||
TaskState.FAILED: {TaskState.PENDING, TaskState.CANCELLED}, # Allow retry
|
||||
TaskState.CANCELLED: set(), # Terminal
|
||||
}
|
||||
|
||||
|
||||
class Task(BaseModel):
|
||||
"""
|
||||
Task representation for orchestration.
|
||||
|
||||
Includes validation for:
|
||||
- Non-empty task_id and goal
|
||||
- Timestamps for tracking
|
||||
- State transition helpers
|
||||
"""
|
||||
|
||||
task_id: str = Field(..., min_length=1, description="Unique task identifier")
|
||||
goal: str = Field(..., min_length=1, description="High-level goal description")
|
||||
constraints: list[str] = Field(default_factory=list, description="Constraints to respect")
|
||||
priority: TaskPriority = Field(default=TaskPriority.NORMAL, description="Task priority")
|
||||
state: TaskState = Field(default=TaskState.PENDING, description="Current task state")
|
||||
metadata: dict[str, Any] = Field(default_factory=dict, description="Optional extra data")
|
||||
created_at: datetime = Field(default_factory=utc_now, description="Creation timestamp")
|
||||
updated_at: datetime | None = Field(default=None, description="Last update timestamp")
|
||||
|
||||
model_config = {"frozen": False}
|
||||
|
||||
@field_validator("task_id")
|
||||
@classmethod
|
||||
def validate_task_id(cls, v: str) -> str:
|
||||
"""Validate task_id is not just whitespace."""
|
||||
if not v.strip():
|
||||
raise ValueError("task_id cannot be empty or whitespace")
|
||||
return v
|
||||
|
||||
@field_validator("goal")
|
||||
@classmethod
|
||||
def validate_goal(cls, v: str) -> str:
|
||||
"""Validate goal is not just whitespace."""
|
||||
if not v.strip():
|
||||
raise ValueError("goal cannot be empty or whitespace")
|
||||
return v
|
||||
|
||||
def can_transition_to(self, new_state: TaskState) -> bool:
|
||||
"""Check if transitioning to new_state is valid."""
|
||||
if new_state == self.state:
|
||||
return True
|
||||
allowed = VALID_TASK_TRANSITIONS.get(self.state, set())
|
||||
return new_state in allowed
|
||||
|
||||
def transition_to(self, new_state: TaskState) -> "Task":
|
||||
"""
|
||||
Create a new Task with the new state.
|
||||
|
||||
Raises:
|
||||
ValueError: If the transition is not allowed.
|
||||
"""
|
||||
if not self.can_transition_to(new_state):
|
||||
raise ValueError(
|
||||
f"Invalid state transition: {self.state.value} -> {new_state.value}"
|
||||
)
|
||||
return self.model_copy(update={"state": new_state, "updated_at": utc_now()})
|
||||
|
||||
@property
|
||||
def is_terminal(self) -> bool:
|
||||
"""Check if task is in a terminal state."""
|
||||
return self.state in (TaskState.COMPLETED, TaskState.CANCELLED)
|
||||
|
||||
@property
|
||||
def is_active(self) -> bool:
|
||||
"""Check if task is currently active."""
|
||||
return self.state == TaskState.ACTIVE
|
||||
50
fusionagi/schemas/witness.py
Normal file
50
fusionagi/schemas/witness.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""Witness output schemas: agreement map, transparency report, final response."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AgreementMap(BaseModel):
|
||||
"""Map of agreed vs disputed claims from heads."""
|
||||
|
||||
agreed_claims: list[dict[str, Any]] = Field(
|
||||
default_factory=list,
|
||||
description="Claims with consensus support",
|
||||
)
|
||||
disputed_claims: list[dict[str, Any]] = Field(
|
||||
default_factory=list,
|
||||
description="Conflicting or contested claims",
|
||||
)
|
||||
confidence_score: float = Field(..., ge=0.0, le=1.0, description="Overall confidence [0, 1]")
|
||||
|
||||
|
||||
class TransparencyReport(BaseModel):
|
||||
"""Transparency report produced by Witness."""
|
||||
|
||||
head_contributions: list[dict[str, Any]] = Field(
|
||||
default_factory=list,
|
||||
description="Per-head contribution (head_id, summary, key_claims)",
|
||||
)
|
||||
agreement_map: AgreementMap = Field(..., description="Agreed and disputed claims")
|
||||
safety_report: str = Field(default="", description="High-level safety/compliance summary")
|
||||
confidence_score: float = Field(..., ge=0.0, le=1.0, description="Overall confidence [0, 1]")
|
||||
|
||||
|
||||
class FinalResponse(BaseModel):
|
||||
"""Final user-facing response from Witness arbitration."""
|
||||
|
||||
final_answer: str = Field(..., min_length=1, description="Composed narrative answer")
|
||||
transparency_report: TransparencyReport = Field(..., description="Full transparency payload")
|
||||
head_contributions: list[dict[str, Any]] = Field(
|
||||
default_factory=list,
|
||||
description="Which heads contributed (1–2 lines each)",
|
||||
)
|
||||
confidence_score: float = Field(..., ge=0.0, le=1.0, description="Overall confidence [0, 1]")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AgreementMap",
|
||||
"TransparencyReport",
|
||||
"FinalResponse",
|
||||
]
|
||||
29
fusionagi/schemas/world_model.py
Normal file
29
fusionagi/schemas/world_model.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""World model schemas: state transitions, rollouts, uncertainty for AGI."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
def _utc_now() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
class StateTransition(BaseModel):
|
||||
"""Causal transition: action -> resulting state."""
|
||||
|
||||
from_state: dict[str, Any] = Field(default_factory=dict)
|
||||
action: str = Field(default="")
|
||||
action_args: dict[str, Any] = Field(default_factory=dict)
|
||||
to_state: dict[str, Any] = Field(default_factory=dict)
|
||||
confidence: float = Field(default=1.0, ge=0.0, le=1.0)
|
||||
|
||||
|
||||
class UncertaintyInfo(BaseModel):
|
||||
"""Explicit uncertainty: confidence, risk, expected value."""
|
||||
|
||||
confidence: float = Field(default=1.0, ge=0.0, le=1.0)
|
||||
risk_level: str = Field(default="low", description="e.g. low, medium, high")
|
||||
expected_value: float | None = Field(default=None)
|
||||
rationale: str = Field(default="")
|
||||
Reference in New Issue
Block a user