[Haik]: satisfactory Agent class abstraction for now (altho im sure it doesnt fully work in the current setup yet), onto the AgentManager abstraction

This commit is contained in:
haikdc
2026-04-02 08:59:41 -07:00
parent ecdd215fe5
commit 1c54df9f32
7 changed files with 45 additions and 103 deletions
+27 -86
View File
@@ -1,38 +1,19 @@
"""Thin coordinator for agent sessions.
# TODO: NON HAIK DEPS: ws_manager
Heavy logic lives in sibling modules:
- agent_manager_ops edit, close, resume, duplicate, invoke, LLM metadata
- agent_loop the SDK query loop, streaming, mock agent
- agent_mock session-completed analytics
- prompt_builder system-prompt composition & context injection
- mcp_builder MCP server construction & tool-policy helpers
- session_store on-disk persistence, history, message copying
"""
from __future__ import annotations
import asyncio
import logging
import os
from datetime import datetime
from uuid import uuid4
import asyncio
import os
from claude_agent_sdk import ClaudeAgentOptions
from pydantic import BaseModel, InstanceOf
from pydantic import BaseModel, Field, InstanceOf
from typing import List, Literal, Optional
from typeguard import typechecked
from backend.apps.agents.models import AgentSession, AgentConfig, ApprovalRequest
from backend.apps.agents.manager.ws_manager import ws_manager
from backend.apps.agents.execution.prompt_builder import resolve_mode
from backend.apps.agents.execution.mcp_builder import get_all_tool_names
from backend.apps.settings.settings import load_settings
from backend.apps.agents.manager.HaikFix.agent_loop import run_agent_loop
from backend.apps.agents.manager.HaikFix.helpers.Message import Message
from backend.apps.agents.manager.HaikFix.PromptChunks import ImageChunk
logger = logging.getLogger(__name__)
from backend.apps.agents.manager.HaikFix.run_agent_loop.run_agent_loop import run_agent_loop
from backend.apps.agents.manager.HaikFix.shared_structs.Message import Message
from backend.apps.agents.manager.HaikFix.shared_structs.PromptChunks import ImageChunk
from backend.apps.agents.manager.HaikFix.shared_structs.ApprovalRequest import ApprovalRequest
os.environ.setdefault("CLAUDE_CODE_STREAM_CLOSE_TIMEOUT", "3600000")
@@ -48,66 +29,25 @@ class Skill(BaseModel):
class Agent(BaseModel):
model: str
mode: str
session_id: str
status: Literal["running", "waiting_approval", "completed", "error", "stopped"]
lock: InstanceOf[asyncio.Lock]
pending_approvals: List[ApprovalRequest] = Field(default_factory=list)
session_id: str = Field(default_factory=lambda: uuid4().hex)
config: ClaudeAgentOptions
session: Optional[AgentSession] = None
branch_id: str = "main"
children: List["Agent"] = Field(default_factory=list)
parent_id: Optional[str] = None
task: Optional[asyncio.Task] = None
lock: InstanceOf[asyncio.Lock] = Field(default_factory=asyncio.Lock)
@typechecked
def __init__(
self,
model: str,
mode: str,
tools: List[str],
effective_cwd: str,
config: ClaudeAgentOptions,
) -> None:
id: str = uuid4().hex
lock: asyncio.Lock = asyncio.Lock()
super().__init__(
model=model,
mode=mode,
tools=tools,
effective_cwd=effective_cwd,
status="running",
id=id,
task=None,
lock=lock,
config=config,
)
async def launch_agent(self, config: AgentConfig) -> AgentSession:
session_id = uuid4().hex
mode_tools, _, mode_folder = resolve_mode(config.mode, get_all_tool_names)
global_settings = load_settings()
effective_cwd = (
config.target_directory or mode_folder
or global_settings.default_folder or os.path.expanduser("~")
)
if config.mode in ("view-builder", "skill-builder") and not config.target_directory:
effective_cwd = os.path.join(effective_cwd, session_id)
os.makedirs(effective_cwd, exist_ok=True)
self.session = AgentSession(
id=session_id, name=config.name,
provider=getattr(config, "provider", "anthropic"),
model=config.model, mode=config.mode,
system_prompt=config.system_prompt, allowed_tools=mode_tools,
max_turns=config.max_turns, cwd=effective_cwd,
dashboard_id=config.dashboard_id,
)
self.session_id = session_id
await ws_manager.emit_status(session_id, "running", self.session)
return self.session
async def send_message(
self,
prompt: str,
images: Optional[List[ImageChunk]] = None,
):
) -> None:
async with self.lock:
if self.task is not None and not self.task.done():
print("[Agent.send_message] Agent is already running")
@@ -133,21 +73,22 @@ class Agent(BaseModel):
parent_id=self.parent_id,
))
@typechecked
async def stop_agent(self):
for child in self.children:
await child.stop_agent()
if self.task and not self.task.done():
self.task.cancel()
try:
await self.task
except asyncio.CancelledError:
pass
if self.session:
for req in list[ApprovalRequest](self.session.pending_approvals):
ws_manager.resolve_approval(req.id, {"behavior": "deny", "message": "Agent stopped"})
self.session.pending_approvals = []
if hasattr(self.session, '_cancel_event'):
self.session._cancel_event.set()
self.session.status = "stopped"
if not self.session.closed_at:
self.session.closed_at = datetime.now()
await ws_manager.emit_status(self.session.id, "stopped", self.session)
# if self.session:
for req in list[ApprovalRequest](self.pending_approvals):
ws_manager.resolve_approval(req.id, {"behavior": "deny", "message": "Agent stopped"})
self.pending_approvals = []
self.status = "stopped"
await ws_manager.emit_status(self.session.id, "stopped", self.session)
@@ -1,4 +1,4 @@
from backend.apps.agents.manager.HaikFix.helpers.Message import Message, ToolCallContent
from backend.apps.agents.manager.HaikFix.shared_structs.Message import Message, ToolCallContent
from claude_agent_sdk.types import TextBlock, ToolUseBlock, AssistantMessage
from backend.apps.agents.manager.ws_manager import ws_manager
from typeguard import typechecked
@@ -1,12 +1,3 @@
"""Main agent loop — orchestrates the Claude Agent SDK query loop.
Heavy logic is delegated to sibling modules:
- agent_mock mock-agent fallback, streaming helpers, session analytics
- agent_hooks SDK hook factories (approval, permissions, post-tool)
- agent_options MCP server construction & ClaudeAgentOptions building
"""
from __future__ import annotations
import logging
from typeguard import typechecked
@@ -14,15 +5,14 @@ from claude_agent_sdk import (
query, ClaudeAgentOptions, AssistantMessage,
)
from claude_agent_sdk.types import StreamEvent
from backend.apps.agents.manager.HaikFix.helpers.handle_stream_event import handle_stream_event
from backend.apps.agents.manager.HaikFix.helpers.handle_assistant_message import handle_assistant_message
from backend.apps.agents.manager.HaikFix.run_agent_loop.helpers.handle_stream_event import handle_stream_event
from backend.apps.agents.manager.HaikFix.run_agent_loop.helpers.handle_assistant_message import handle_assistant_message
from backend.apps.agents.manager.HaikFix.PromptChunks import ImageChunk, ImageChunkDict, TextChunk, TextChunkDict
from backend.apps.agents.manager.HaikFix.shared_structs.PromptChunks import (
ImageChunk, ImageChunkDict, TextChunk, TextChunkDict
)
from typing import List, Dict, Literal, Union, Optional
logger = logging.getLogger(__name__)
@typechecked
def build_image_prompt_content(prompt: str, images: List[ImageChunk]) -> List[TextChunk | ImageChunk]:
content: List[Union[ImageChunkDict, TextChunkDict]] = [TextChunk(text=prompt).to_dict()]
@@ -0,0 +1,11 @@
from pydantic import BaseModel, Field
from typing import Any
from datetime import datetime
from uuid import uuid4
class ApprovalRequest(BaseModel):
id: str = Field(default_factory=lambda: uuid4().hex)
session_id: str
tool_name: str
tool_input: dict[str, Any]
created_at: datetime = Field(default_factory=datetime.now)
@@ -1,5 +1,5 @@
from typing import Optional, Literal, Union, List
from pydantic import BaseModel, Field
from typing import Optional, Literal, Union, List, Dict
from datetime import datetime
from uuid import uuid4