"""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)