MarisUK's picture
Maris AI model sync
f440f03 verified
"""Autonomais aģents — plāno un izpilda uzdevumus."""
from __future__ import annotations
import asyncio
import logging
import uuid
from datetime import UTC, datetime
from typing import Any
from fastapi import APIRouter
from pydantic import BaseModel, Field
from maris_core.autonomous.executor import TaskExecutionError, task_executor
from maris_core.autonomous.planner import Planner
from maris_core.autonomous.session_store import session_store
from maris_core.memory_context import MemoryMatch, memory_store
from maris_core.orchestrator.routing import build_system_prompt
from maris_core.personas import resolve_persona
from maris_core.text.generate import call_generation_pipeline, get_pipeline
logger = logging.getLogger(__name__)
router = APIRouter()
CODE_GENERATION_KEYWORDS = ("kod", "api", "script", "python", "rust")
WEB_RESEARCH_KEYWORDS = ("meklē", "research", "search", "salīdzini")
WEB_AUTOMATION_KEYWORDS = ("browser", "pārlūk", "form", "klikš", "scrape", "web")
VALIDATION_KEYWORDS = ("test", "verify", "pārbaud")
AUTONOMOUS_BACKGROUND_LOOP_DELAY_SECONDS = 0.05
# Karstais runtime cache virs persistenta session store.
_sessions: dict[str, dict[str, Any]] = {}
_session_runners: dict[str, asyncio.Task[None]] = {}
_session_runner_lock: asyncio.Lock | None = None
_session_runner_lock_loop: asyncio.AbstractEventLoop | None = None
planner = Planner()
_AUTONOMOUS_AGENT_ROLES = [
{
"id": "planner",
"title": "Planner",
"responsibility": "Sadala mērķi izpildāmā plānā ar atkarībām un checkpointiem.",
},
{
"id": "executor",
"title": "Executor",
"responsibility": "Izpilda nākamo gatavo uzdevumu un straumē rezultātus.",
},
{
"id": "reviewer",
"title": "Reviewer",
"responsibility": "Pārbauda riskus, validāciju un sagatavo approval signālus.",
},
{
"id": "operator",
"title": "Operator",
"responsibility": "Saņem interruptus, approval pieprasījumus un var atjaunot sesiju no checkpointa.",
},
]
class StartRequest(BaseModel):
session_id: str
goal: str
max_steps: int = 10
persona_id: str | None = None
class StatusRequest(BaseModel):
session_id: str
class TaskModel(BaseModel):
id: str
description: str
status: str
result: str | None = None
created_at: str
tool: str
depends_on: list[str] = Field(default_factory=list)
attempts: int = 0
max_attempts: int = 2
last_error: str | None = None
class TimelineEventModel(BaseModel):
id: str
event_type: str
title: str
detail: str
agent_role: str
level: str = "info"
created_at: str
task_id: str | None = None
interruptible: bool = False
class CheckpointModel(BaseModel):
id: str
label: str
status: str
summary: str
created_at: str
task_id: str | None = None
class ApprovalModel(BaseModel):
id: str
kind: str
status: str
title: str
summary: str
created_at: str
task_id: str | None = None
resolution_note: str | None = None
class AgentRoleModel(BaseModel):
id: str
title: str
responsibility: str
status: str
class SessionResponse(BaseModel):
session_id: str
goal: str
status: str
tasks: list[TaskModel]
progress_percent: int = 0
persona_id: str = "assistant"
persona_title: str = "Core Assistant"
persona_summary: str = ""
events: list[TimelineEventModel] = Field(default_factory=list)
checkpoints: list[CheckpointModel] = Field(default_factory=list)
approvals: list[ApprovalModel] = Field(default_factory=list)
agent_roles: list[AgentRoleModel] = Field(default_factory=list)
replay_cursor: int = 0
resume_token: str = ""
failover_mode: str = "checkpoint_resume"
def _sanitize_for_storage(value: Any) -> Any:
if isinstance(value, dict):
sanitized: dict[str, Any] = {}
for key, item in value.items():
if str(key).startswith("_"):
continue
sanitized[str(key)] = _sanitize_for_storage(item)
return sanitized
if isinstance(value, list):
return [_sanitize_for_storage(item) for item in value]
if isinstance(value, tuple):
return [_sanitize_for_storage(item) for item in value]
if isinstance(value, set):
return sorted(_sanitize_for_storage(item) for item in value)
return value
def _now_iso() -> str:
return datetime.now(tz=UTC).isoformat()
def _build_task(
description: str,
*,
tool: str,
depends_on: list[str] | None = None,
max_attempts: int = 2,
) -> dict[str, Any]:
return {
"id": str(uuid.uuid4()),
"description": description,
"status": "pending",
"result": None,
"created_at": _now_iso(),
"tool": tool,
"depends_on": depends_on or [],
"attempts": 0,
"max_attempts": max_attempts,
"last_error": None,
}
def _build_agent_roles() -> list[dict[str, str]]:
return [{**role, "status": "ready"} for role in _AUTONOMOUS_AGENT_ROLES]
def _append_event(
session: dict[str, Any],
*,
event_type: str,
title: str,
detail: str,
agent_role: str,
level: str = "info",
task_id: str | None = None,
interruptible: bool = False,
) -> None:
event = {
"id": str(uuid.uuid4()),
"event_type": event_type,
"title": title,
"detail": detail,
"agent_role": agent_role,
"level": level,
"created_at": _now_iso(),
"task_id": task_id,
"interruptible": interruptible,
}
session.setdefault("events", []).append(event)
session["replay_cursor"] = len(session["events"])
telemetry = session.setdefault("telemetry", {})
telemetry["last_event_type"] = event_type
telemetry["last_event_at"] = event["created_at"]
def _ensure_checkpoint(
session: dict[str, Any], *, label: str, summary: str, status: str, task_id: str | None = None
) -> None:
checkpoint_key = (label, task_id or "")
existing = session.setdefault("_checkpoint_keys", set())
if checkpoint_key in existing:
return
existing.add(checkpoint_key)
session.setdefault("checkpoints", []).append(
{
"id": str(uuid.uuid4()),
"label": label,
"status": status,
"summary": summary,
"created_at": _now_iso(),
"task_id": task_id,
}
)
telemetry = session.setdefault("telemetry", {})
telemetry["checkpoint_count"] = len(session.get("checkpoints", []))
def _set_agent_role_status(session: dict[str, Any], role_id: str, status: str) -> None:
for role in session.get("agent_roles", []):
if role["id"] == role_id:
role["status"] = status
break
def _upsert_approval(
session: dict[str, Any],
*,
task_id: str | None,
kind: str,
status: str,
title: str,
summary: str,
resolution_note: str | None = None,
) -> None:
approvals = session.setdefault("approvals", [])
for approval in approvals:
if approval.get("task_id") == task_id and approval.get("kind") == kind:
approval.update(
{
"status": status,
"title": title,
"summary": summary,
"resolution_note": resolution_note,
}
)
return
approvals.append(
{
"id": str(uuid.uuid4()),
"kind": kind,
"status": status,
"title": title,
"summary": summary,
"created_at": _now_iso(),
"task_id": task_id,
"resolution_note": resolution_note,
}
)
telemetry = session.setdefault("telemetry", {})
telemetry["approval_count"] = len(approvals)
async def _persist_session(session_id: str, session: dict[str, Any]) -> None:
await session_store.save_session(session_id, _sanitize_for_storage(session))
async def _record_audit(session: dict[str, Any], record_type: str, payload: dict[str, Any]) -> None:
session_id = str(session.get("session_id", "")).strip()
if not session_id:
return
await session_store.append_audit_record(
session_id,
record_type=record_type,
payload=_sanitize_for_storage(payload),
)
async def _load_session(session_id: str) -> dict[str, Any]:
cached = _sessions.get(session_id)
if cached is not None:
return cached
restored = await session_store.load_session(session_id)
if restored is None:
return {}
restored.setdefault(
"_checkpoint_keys",
{
(checkpoint.get("label", ""), checkpoint.get("task_id", "") or "")
for checkpoint in restored.get("checkpoints", [])
},
)
_sessions[session_id] = restored
return restored
async def _run_session_until_terminal(session_id: str) -> None:
try:
while True:
session = await _load_session(session_id)
if not session or session.get("status") in {"completed", "failed"}:
return
await _advance_session(session_id)
session = await _load_session(session_id)
if not session or session.get("status") in {"completed", "failed"}:
return
await asyncio.sleep(AUTONOMOUS_BACKGROUND_LOOP_DELAY_SECONDS)
except asyncio.CancelledError:
raise
except Exception as exc: # noqa: BLE001
logger.exception("Autonomous background runner neizdevās sesijai %s: %s", session_id, exc)
session = await _load_session(session_id)
if session and session.get("status") not in {"completed", "failed"}:
session["status"] = "failed"
_set_agent_role_status(session, "reviewer", "attention")
_append_event(
session,
event_type="session.runtime_failed",
title="Autonomous runtime failed",
detail=f"Fona izpildītājs apstājās ar kļūdu: {exc}",
agent_role="operator",
level="warning",
interruptible=True,
)
_ensure_checkpoint(
session,
label="Runtime failure checkpoint",
summary="Sesija apstājās fona runtime kļūdas dēļ un ir atjaunojama no checkpointa.",
status="recoverable",
)
await _persist_session(session_id, session)
finally:
current_task = asyncio.current_task()
async with _get_session_runner_lock():
if current_task is not None and _session_runners.get(session_id) is current_task:
_session_runners.pop(session_id, None)
def _get_session_runner_lock() -> asyncio.Lock:
global _session_runner_lock # noqa: PLW0603
global _session_runner_lock_loop # noqa: PLW0603
loop = asyncio.get_running_loop()
if _session_runner_lock is None or _session_runner_lock_loop is not loop:
_session_runner_lock = asyncio.Lock()
_session_runner_lock_loop = loop
return _session_runner_lock
async def _ensure_session_runner(session_id: str) -> None:
session = await _load_session(session_id)
if not session or session.get("status") in {"completed", "failed"}:
return
async with _get_session_runner_lock():
existing = _session_runners.get(session_id)
if existing is not None and not existing.done():
return
_session_runners[session_id] = asyncio.create_task(
_run_session_until_terminal(session_id),
name=f"maris-autonomous-{session_id}",
)
def _infer_tool(description: str) -> str:
lowered = description.lower()
if any(token in lowered for token in CODE_GENERATION_KEYWORDS):
return "code_generation"
if any(token in lowered for token in WEB_AUTOMATION_KEYWORDS):
return "browser_automation"
if any(token in lowered for token in WEB_RESEARCH_KEYWORDS):
return "web_research"
if any(token in lowered for token in VALIDATION_KEYWORDS):
return "validation"
return "reasoning"
def _fallback_descriptions(goal: str, max_steps: int) -> list[str]:
stages = [
f"Izanalizēt mērķi un ierobežojumus: {goal}",
"Sadalīt darbu prioritārās izpildes daļās un noteikt atkarības",
"Izpildīt galveno risinājuma soli ar piemērotāko rīku",
"Pārbaudīt rezultātu, apkopot riskus un nākamos soļus",
]
return stages[: max(1, max_steps)]
def _extract_step_descriptions(content: str, goal: str, max_steps: int) -> list[str]:
descriptions = [
line.split(".", 1)[-1].strip()
for line in content.split("\n")
if line.strip() and line.lstrip()[0].isdigit()
]
if descriptions[:max_steps]:
return descriptions[:max_steps]
return planner.describe(goal, max_steps=max_steps)
def _load_memory_context(session_id: str, goal: str, *, limit: int = 4) -> list[MemoryMatch]:
return memory_store.retrieve_relevant_context(session_id, goal, limit=limit)
async def _plan_tasks(
goal: str,
max_steps: int,
persona_id: str | None = None,
*,
memory_context: list[MemoryMatch] | None = None,
) -> list[dict[str, Any]]:
"""Izmanto LLM lai sadalītu mērķi uzdevumos."""
pipe = get_pipeline()
persona = resolve_persona(persona_id)
memory_context = memory_context or []
memory_overlay = ""
if memory_context:
memory_lines = "\n".join(
f"- ({match.role}/{match.source}) {match.content}" for match in memory_context
)
memory_overlay = f" Saistītā sesijas atmiņa:\n{memory_lines}\n"
if pipe is not None:
try:
messages = [
{
"role": "system",
"content": (
f"{build_system_prompt('planner', persona_id=persona.id)} "
"Tu esi Maris AI plānotājs. Dod mērķi un sadalī to "
f"maksimāli {max_steps} konkrētos soļos. "
"Katru soli ievietojiet jaunā rindā ar numuru. "
"Sakārto soļus tā, lai katrs nākamais būtu atkarīgs no iepriekšējā. "
f"Plāno ar aktīvo personu '{persona.title}' un tās prioritātēm."
f"{memory_overlay}"
),
},
{"role": "user", "content": f"Mērķis: {goal}"},
]
out = call_generation_pipeline(
pipe,
messages,
max_new_tokens=512,
temperature=0.3,
)
content = out[0]["generated_text"][-1]["content"]
descriptions = _extract_step_descriptions(content, goal, max_steps)
tasks: list[dict[str, Any]] = []
for description in descriptions:
dependency_ids = [tasks[-1]["id"]] if tasks else []
tasks.append(
_build_task(
description,
tool=_infer_tool(description),
depends_on=dependency_ids,
)
)
return tasks
except Exception as exc: # noqa: BLE001
logger.error("Plānošanas kļūda: %s", exc)
planned = planner.decompose(goal, max_steps=max_steps, memory_context=memory_context)
if planned:
tasks: list[dict[str, Any]] = []
id_by_step: dict[int, str] = {}
for index, step in enumerate(planned, start=1):
depends_on_steps = [
id_by_step[dependency_step]
for dependency_step in step.get("depends_on_steps", [])
if dependency_step in id_by_step
]
task = _build_task(
str(step["action"]),
tool=str(step.get("tool", _infer_tool(str(step["action"])))),
depends_on=depends_on_steps,
max_attempts=int(step.get("max_attempts", 2)),
)
task["execution_policy"] = step.get("execution_policy", "sequential")
task["risk_level"] = step.get("risk_level", "medium")
task["approval_required"] = bool(step.get("approval_required", False))
task["observability_tags"] = step.get("observability_tags", [])
tasks.append(task)
id_by_step[index] = task["id"]
return tasks
tasks: list[dict[str, Any]] = []
for description in planner.describe(goal, max_steps=max_steps):
tasks.append(
_build_task(
description,
tool=_infer_tool(description),
depends_on=[tasks[-1]["id"]] if tasks else [],
)
)
return tasks
async def _execute_task(
task: dict[str, Any],
goal: str,
tasks: list[dict[str, Any]],
*,
persona_id: str | None = None,
) -> dict[str, Any]:
try:
result = await task_executor.execute(
task,
goal,
tasks,
persona_id=persona_id,
session_id=str(task.get("session_id", "") or ""),
)
except TaskExecutionError:
raise
except Exception as exc: # noqa: BLE001
raise TaskExecutionError(
f"Izpilde neizdevās: {exc}",
failure_class="unexpected_runtime_error",
) from exc
return {
"summary": result.summary,
"artifacts": result.artifacts,
"metrics": result.metrics,
}
def _refresh_session_status(session: dict[str, Any]) -> None:
previous_status = session.get("status")
tasks = session.get("tasks", [])
if tasks and all(task["status"] == "completed" for task in tasks):
session["status"] = "completed"
elif any(task["status"] == "failed" for task in tasks) and not any(
task["status"] in {"pending", "retrying", "running"} for task in tasks
):
session["status"] = "failed"
else:
session["status"] = "running"
if session["status"] == previous_status:
return
if session["status"] == "completed":
_set_agent_role_status(session, "reviewer", "completed")
_append_event(
session,
event_type="session.completed",
title="Session replay sealed",
detail="Sesija ir pabeigta un replay timeline ir gatavs operatora pārskatam.",
agent_role="operator",
)
_ensure_checkpoint(
session,
label="Final replay checkpoint",
summary="Sesiju var atjaunot no pēdējā veiksmīgā stāvokļa.",
status="sealed",
)
elif session["status"] == "failed":
_set_agent_role_status(session, "reviewer", "attention")
_append_event(
session,
event_type="session.interrupt",
title="Operator intervention required",
detail="Sesija apstājās kļūdas dēļ un gaida operatora lēmumu vai resume no checkpointa.",
agent_role="operator",
level="warning",
interruptible=True,
)
_ensure_checkpoint(
session,
label="Recovery checkpoint",
summary="Pēdējais drošais checkpoint automātiskai failover atjaunošanai.",
status="recoverable",
)
def _progress_percent(tasks: list[dict[str, Any]]) -> int:
total = max(len(tasks), 1)
completed = sum(1 for task in tasks if task["status"] == "completed")
return int(completed / total * 100)
def _build_session_response(session_id: str, session: dict[str, Any]) -> SessionResponse:
tasks_raw = session.get("tasks", [])
return SessionResponse(
session_id=session_id,
goal=session.get("goal", ""),
status=session.get("status", "unknown"),
tasks=[TaskModel(**task) for task in tasks_raw],
progress_percent=_progress_percent(tasks_raw),
persona_id=str(session.get("persona_id", "assistant")),
persona_title=str(session.get("persona_title", "Core Assistant")),
persona_summary=str(session.get("persona_summary", "")),
events=[TimelineEventModel(**event) for event in session.get("events", [])],
checkpoints=[
CheckpointModel(**checkpoint) for checkpoint in session.get("checkpoints", [])
],
approvals=[ApprovalModel(**approval) for approval in session.get("approvals", [])],
agent_roles=[AgentRoleModel(**role) for role in session.get("agent_roles", [])],
replay_cursor=int(session.get("replay_cursor", len(session.get("events", [])))),
resume_token=str(session.get("resume_token", f"resume:{session_id}")),
failover_mode=str(session.get("failover_mode", "checkpoint_resume")),
)
async def _advance_session(session_id: str) -> None:
session = await _load_session(session_id)
if not session or session.get("status") in {"completed", "failed"}:
return
tasks = session["tasks"]
completed_ids = {task["id"] for task in tasks if task["status"] == "completed"}
ready_task = next(
(
task
for task in tasks
if task["status"] in {"pending", "retrying"}
and all(dep in completed_ids for dep in task["depends_on"])
),
None,
)
if ready_task is None:
_refresh_session_status(session)
return
_set_agent_role_status(session, "executor", "running")
if ready_task["tool"] in {"browser_automation", "validation", "code_generation"}:
_upsert_approval(
session,
task_id=ready_task["id"],
kind="operator_review",
status="pending_review",
title="Human-in-the-loop gate",
summary=f"Uzdevums '{ready_task['description']}' izmanto rīku {ready_task['tool']} un tiek izsekots operatora panelī.",
)
_append_event(
session,
event_type="approval.requested",
title="Approval queued",
detail=f"Reviewer atzīmēja uzdevumu '{ready_task['description']}' kā operatoram redzamu darbību.",
agent_role="reviewer",
level="warning",
task_id=ready_task["id"],
interruptible=True,
)
ready_task["status"] = "running"
ready_task["attempts"] += 1
ready_task["session_id"] = session_id
_append_event(
session,
event_type="task.started",
title="Task started",
detail=f"Executor sāka '{ready_task['description']}' ar rīku {ready_task['tool']}.",
agent_role="executor",
task_id=ready_task["id"],
interruptible=True,
)
await _record_audit(session, "task.started", ready_task)
await _persist_session(session_id, session)
try:
execution = await _execute_task(
ready_task,
session["goal"],
tasks,
persona_id=session.get("persona_id"),
)
ready_task["result"] = execution["summary"]
ready_task["artifacts"] = execution.get("artifacts", {})
ready_task["metrics"] = execution.get("metrics", {})
ready_task["status"] = "completed"
ready_task["last_error"] = None
telemetry = session.setdefault("telemetry", {})
telemetry["completed_tasks"] = telemetry.get("completed_tasks", 0) + 1
telemetry["last_completed_task_id"] = ready_task["id"]
_append_event(
session,
event_type="task.completed",
title="Task completed",
detail=ready_task["result"] or "Uzdevums pabeigts.",
agent_role="executor",
task_id=ready_task["id"],
)
_append_event(
session,
event_type="reviewer.summary",
title="Reviewer checkpointed result",
detail=f"Reviewer apstiprināja uzdevuma '{ready_task['description']}' rezultātu replay timeline.",
agent_role="reviewer",
task_id=ready_task["id"],
)
await _record_audit(
session,
"task.completed",
{
"task_id": ready_task["id"],
"result": ready_task["result"],
"artifacts": ready_task.get("artifacts", {}),
"metrics": ready_task.get("metrics", {}),
},
)
_ensure_checkpoint(
session,
label=f"Checkpoint after {ready_task['description']}",
summary="Drošs stāvoklis ar pilnu task graph un timeline replay metadatiem.",
status="ready",
task_id=ready_task["id"],
)
if ready_task["tool"] in {"browser_automation", "validation", "code_generation"}:
_upsert_approval(
session,
task_id=ready_task["id"],
kind="operator_review",
status="auto_approved",
title="Human-in-the-loop gate",
summary=f"Uzdevums '{ready_task['description']}' tika izpildīts bez blokējošas iejaukšanās.",
resolution_note="Auto-approved for this local runtime; production should require explicit operator action.",
)
except TaskExecutionError as exc:
ready_task["last_error"] = str(exc)
ready_task["result"] = f"Mēģinājums {ready_task['attempts']} neizdevās: {exc}"
ready_task["failure_class"] = exc.failure_class
ready_task.setdefault("metrics", {})["failure_class"] = exc.failure_class
session.setdefault("telemetry", {}).setdefault("failure_classes", []).append(
exc.failure_class
)
_append_event(
session,
event_type="task.failed_attempt",
title="Task attempt failed",
detail=ready_task["result"],
agent_role="executor",
level="warning",
task_id=ready_task["id"],
interruptible=True,
)
await _record_audit(
session,
"task.failed_attempt",
{
"task_id": ready_task["id"],
"failure_class": exc.failure_class,
"retryable": exc.retryable,
"error": str(exc),
},
)
_set_agent_role_status(session, "reviewer", "attention")
if exc.retryable and ready_task["attempts"] < ready_task["max_attempts"]:
ready_task["status"] = "retrying"
_append_event(
session,
event_type="task.retrying",
title="Retry scheduled",
detail=f"Reviewer piešķīra atkārtotu mēģinājumu uzdevumam '{ready_task['description']}'.",
agent_role="reviewer",
level="warning",
task_id=ready_task["id"],
)
_ensure_checkpoint(
session,
label=f"Retry checkpoint for {ready_task['description']}",
summary="Saglabāts stāvoklis pirms nākamā mēģinājuma.",
status="retry_pending",
task_id=ready_task["id"],
)
else:
ready_task["status"] = "failed"
_upsert_approval(
session,
task_id=ready_task["id"],
kind="operator_review",
status="needs_intervention",
title="Operator intervention required",
summary=f"Uzdevums '{ready_task['description']}' izsmēla mēģinājumus un gaida resume no checkpointa.",
resolution_note=str(exc),
)
except Exception as exc: # noqa: BLE001
ready_task["last_error"] = str(exc)
ready_task["result"] = f"Mēģinājums {ready_task['attempts']} neizdevās: {exc}"
_append_event(
session,
event_type="task.failed_attempt",
title="Task attempt failed",
detail=ready_task["result"],
agent_role="executor",
level="warning",
task_id=ready_task["id"],
interruptible=True,
)
_set_agent_role_status(session, "reviewer", "attention")
if ready_task["attempts"] < ready_task["max_attempts"]:
ready_task["status"] = "retrying"
_append_event(
session,
event_type="task.retrying",
title="Retry scheduled",
detail=f"Reviewer piešķīra atkārtotu mēģinājumu uzdevumam '{ready_task['description']}'.",
agent_role="reviewer",
level="warning",
task_id=ready_task["id"],
)
_ensure_checkpoint(
session,
label=f"Retry checkpoint for {ready_task['description']}",
summary="Saglabāts stāvoklis pirms nākamā mēģinājuma.",
status="retry_pending",
task_id=ready_task["id"],
)
else:
ready_task["status"] = "failed"
_upsert_approval(
session,
task_id=ready_task["id"],
kind="operator_review",
status="needs_intervention",
title="Operator intervention required",
summary=f"Uzdevums '{ready_task['description']}' izsmēla mēģinājumus un gaida resume no checkpointa.",
resolution_note=str(exc),
)
_refresh_session_status(session)
await _persist_session(session_id, session)
if session["status"] == "running":
_set_agent_role_status(session, "planner", "completed")
_set_agent_role_status(session, "executor", "ready")
if all(
approval["status"] != "needs_intervention" for approval in session.get("approvals", [])
):
_set_agent_role_status(session, "reviewer", "ready")
@router.post("/start", response_model=SessionResponse)
async def start_session(req: StartRequest) -> SessionResponse:
"""Sāk autonomo sesiju."""
persona = resolve_persona(req.persona_id)
memory_context = _load_memory_context(req.session_id, req.goal)
tasks_raw = await _plan_tasks(
req.goal,
req.max_steps,
req.persona_id,
memory_context=memory_context,
)
created_at = _now_iso()
for task in tasks_raw:
task["session_id"] = req.session_id
_sessions[req.session_id] = {
"session_id": req.session_id,
"goal": req.goal,
"status": "running",
"created_at": created_at,
"tasks": tasks_raw,
"persona_id": persona.id,
"persona_title": persona.title,
"persona_summary": persona.summary,
"events": [],
"checkpoints": [],
"approvals": [],
"agent_roles": _build_agent_roles(),
"replay_cursor": 0,
"resume_token": f"resume:{req.session_id}",
"failover_mode": "checkpoint_resume",
"telemetry": {
"planned_task_count": len(tasks_raw),
"completed_tasks": 0,
"failure_classes": [],
},
}
session = _sessions[req.session_id]
_append_event(
session,
event_type="session.started",
title="Session created",
detail=f"Planner saņēma mērķi '{req.goal}' un sāka veidot task graph.",
agent_role="planner",
)
_append_event(
session,
event_type="task_graph.ready",
title="Task graph published",
detail=f"Plānā ir {len(tasks_raw)} soļi ar secīgām atkarībām un replay cursor atbalstu.",
agent_role="planner",
)
if memory_context:
_append_event(
session,
event_type="memory.context_loaded",
title="Session context restored",
detail=f"Planner ielādēja {len(memory_context)} saistītus sesijas atmiņas ierakstus pirms plānošanas.",
agent_role="planner",
)
_ensure_checkpoint(
session,
label="Planning checkpoint",
summary="Task graph ir publicēts un sesiju var atsākt no plānošanas posma.",
status="ready",
)
memory_store.remember_message(req.session_id, "user", req.goal, source="autonomous_goal")
await _record_audit(
session,
"session.started",
{
"goal": req.goal,
"persona_id": persona.id,
"planned_task_count": len(tasks_raw),
},
)
await _persist_session(req.session_id, session)
await _advance_session(req.session_id)
await _ensure_session_runner(req.session_id)
return _build_session_response(req.session_id, session)
@router.post("/status", response_model=SessionResponse)
async def get_status(req: StatusRequest) -> SessionResponse:
"""Atgriež sesijas statusu."""
session = await _load_session(req.session_id)
if session:
await _ensure_session_runner(req.session_id)
return _build_session_response(req.session_id, session)