diff options
| author | Guillermo Serrahima <dev@vylion.com> | 2026-06-26 14:39:23 +0200 |
|---|---|---|
| committer | Guillermo Serrahima <dev@vylion.com> | 2026-06-26 14:39:23 +0200 |
| commit | 963f59569437fe43a49ce36a003a143fa2f767c8 (patch) | |
| tree | 968b0dad13193536770ae1e6b580b27e22f9366b /agent/orchestrator/models/claude_code.py | |
| parent | 741a04c2abcbd77451c0933a19d91fddb9982882 (diff) | |
| download | senia-963f59569437fe43a49ce36a003a143fa2f767c8.tar.xz senia-963f59569437fe43a49ce36a003a143fa2f767c8.zip | |
created custom agent loop
Diffstat (limited to 'agent/orchestrator/models/claude_code.py')
| -rw-r--r-- | agent/orchestrator/models/claude_code.py | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/agent/orchestrator/models/claude_code.py b/agent/orchestrator/models/claude_code.py new file mode 100644 index 0000000..c73df98 --- /dev/null +++ b/agent/orchestrator/models/claude_code.py @@ -0,0 +1,68 @@ +"""Claude Code backend. + +Shells out to `claude -p` for non-interactive turns. Claude Code handles its +own persona discovery (CLAUDE.md / SOUL.md from the workspace) and its own MCP +tool dispatch internally; this backend just formats the prompt and parses +the response. +""" + +import os +import subprocess +from pathlib import Path + +from .base import ModelBackend, ModelRequest, ModelResponse + + +_ROLE_LABELS = { + "user": "User", + "assistant": "Assistant", + "tool": "Tool", + "system": "System", +} + + +class ClaudeCodeBackend(ModelBackend): + def __init__( + self, + workspace: Path = Path(os.environ.get("SENIA_WORKSPACE", "/workspace")), + timeout: int = 300, + allowed_tools: str = "mcp__senia__*", + ): + self.workspace = workspace + self.timeout = timeout + self.allowed_tools = allowed_tools + + def call(self, request: ModelRequest) -> ModelResponse: + prompt = self._format_prompt(request) + result = subprocess.run( + [ + "claude", "-p", prompt, + "--output-format", "text", + "--allowedTools", self.allowed_tools, + ], + cwd=self.workspace, + capture_output=True, + text=True, + timeout=self.timeout, + ) + if result.returncode != 0: + raise RuntimeError( + f"claude exited {result.returncode}: {result.stderr.strip()}" + ) + return ModelResponse(content=result.stdout.strip()) + + def _format_prompt(self, request: ModelRequest) -> str: + """Flatten messages + dynamic context into a single prompt string. + + Claude Code's -p mode takes one big prompt, not a structured + conversation. We render the conversation as a readable transcript with + the per-turn context prepended. Persona is NOT here; Claude Code + finds it in the workspace itself. + """ + parts = [] + if request.turn_context: + parts.append(f"## Context for this turn\n\n{request.turn_context}\n") + for msg in request.messages: + parts.append(f"## {_ROLE_LABELS[msg.role]}\n\n{msg.content}\n") + parts.append("## Assistant\n\n") + return "\n".join(parts) |
