-
Notifications
You must be signed in to change notification settings - Fork 552
feat(sdk): agent runtime behind backend/harness ports #4771
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
b9e62f9
feat(sdk): agent runtime ports, adapters, tool resolution, and messag…
mmabrouk 741fc73
fix(sdk): validate agent runner configuration
mmabrouk 2a7c129
refactor(sdk): rename rivet adapter/backend to sandbox-agent
mmabrouk 0beb120
fix(sdk): address review feedback (locking, input validation, stream/…
mmabrouk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| """Agenta agent runtime: run a coding harness (Pi, Claude, ...) as a swappable port. | ||
|
|
||
| Layers (Agenta's hexagonal vocabulary): | ||
|
|
||
| - ``dtos.py`` — data contracts (``AgentConfig``, ``SessionConfig``, ``Message``, ...). | ||
| - ``interfaces.py`` — the ports (ABCs): ``Backend``, ``Environment``, ``Sandbox``, | ||
| ``Session``, ``Harness``. | ||
| - ``adapters/`` — implementations: ``SandboxAgentBackend`` / ``InProcessPiBackend`` / ``LocalBackend`` | ||
| and ``PiHarness`` / ``ClaudeHarness``. | ||
| - ``utils/`` — shared plumbing (the ``/run`` wire and the transports to the TS runner). | ||
|
|
||
| Standalone usage:: | ||
|
|
||
| import agenta as ag | ||
| from agenta.sdk.agents import Message | ||
|
|
||
| cfg = ag.ConfigManager.get_from_registry(app_slug="my-agent") | ||
| agent = ag.AgentConfig.from_params(cfg) | ||
| harness = ag.PiHarness(ag.Environment(ag.SandboxAgentBackend())) | ||
| result = await harness.prompt(ag.SessionConfig(agent=agent), [Message(role="user", content="hi")]) | ||
| """ | ||
|
|
||
| from .adapters import ( | ||
| AgentaHarness, | ||
| ClaudeHarness, | ||
| InProcessPiBackend, | ||
| LocalBackend, | ||
| PiHarness, | ||
| SandboxAgentBackend, | ||
| make_harness, | ||
| ) | ||
| from .dtos import ( | ||
| AgentaAgentConfig, | ||
| AgentConfig, | ||
| AgentEvent, | ||
| AgentResult, | ||
| ClaudeAgentConfig, | ||
| ContentBlock, | ||
| HarnessAgentConfig, | ||
| HarnessCapabilities, | ||
| HarnessType, | ||
| Message, | ||
| PermissionPolicy, | ||
| PiAgentConfig, | ||
| RunSelection, | ||
| SessionConfig, | ||
| ToolCallback, | ||
| TraceContext, | ||
| to_messages, | ||
| ) | ||
| from .errors import ( | ||
| AgentRunnerConfigurationError, | ||
| ToolResolutionError, | ||
| UnsupportedHarnessError, | ||
| ) | ||
| from .interfaces import ( | ||
| Backend, | ||
| Environment, | ||
| Harness, | ||
| NoopSessionStore, | ||
| Sandbox, | ||
| Session, | ||
| SessionStore, | ||
| ) | ||
| from .mcp import ( | ||
| MCPConfigurationError, | ||
| MCPError, | ||
| MCPResolver, | ||
| MCPServerConfig, | ||
| MissingMCPSecretError, | ||
| ResolvedMCPServer, | ||
| ) | ||
| from .streaming import AgentRun | ||
| from .tools import ( | ||
| BuiltinToolConfig, | ||
| CallbackToolSpec, | ||
| ClientToolConfig, | ||
| ClientToolSpec, | ||
| CodeToolConfig, | ||
| CodeToolSpec, | ||
| DuplicateToolNameError, | ||
| EnvironmentToolSecretProvider, | ||
| GatewayToolResolver, | ||
| GatewayToolConfig, | ||
| GatewayToolResolution, | ||
| GatewayToolResolutionError, | ||
| MissingSecretPolicy, | ||
| MissingToolSecretError, | ||
| ResolvedToolSet, | ||
| ToolConfig, | ||
| ToolConfigError, | ||
| ToolConfigurationError, | ||
| ToolError, | ||
| ToolResolver, | ||
| ToolSecretProvider, | ||
| ToolSpec, | ||
| UnsupportedToolProviderError, | ||
| coerce_tool_config, | ||
| coerce_tool_configs, | ||
| parse_tool_config, | ||
| parse_tool_configs, | ||
| ) | ||
| from .adapters.vercel import ( | ||
| from_ui_messages, | ||
| to_ui_message, | ||
| ui_message_stream, | ||
| ) | ||
|
|
||
| __all__ = [ | ||
| # DTOs | ||
| "AgentConfig", | ||
| "RunSelection", | ||
| "SessionConfig", | ||
| "HarnessAgentConfig", | ||
| "PiAgentConfig", | ||
| "ClaudeAgentConfig", | ||
| "AgentaAgentConfig", | ||
| "HarnessType", | ||
| "HarnessCapabilities", | ||
| "ContentBlock", | ||
| "Message", | ||
| "to_messages", | ||
| "AgentEvent", | ||
| "AgentResult", | ||
| "AgentRun", | ||
| # Former flat Vercel adapter names (compatibility; new code uses adapters.vercel) | ||
| "from_ui_messages", | ||
| "to_ui_message", | ||
| "ui_message_stream", | ||
| "TraceContext", | ||
| "ToolCallback", | ||
| "PermissionPolicy", | ||
| # Canonical tools API | ||
| "ToolConfig", | ||
| "BuiltinToolConfig", | ||
| "GatewayToolConfig", | ||
| "CodeToolConfig", | ||
| "ClientToolConfig", | ||
| "ToolSpec", | ||
| "CallbackToolSpec", | ||
| "CodeToolSpec", | ||
| "ClientToolSpec", | ||
| "ResolvedToolSet", | ||
| "GatewayToolResolution", | ||
| "ToolResolver", | ||
| "ToolSecretProvider", | ||
| "GatewayToolResolver", | ||
| "EnvironmentToolSecretProvider", | ||
| "MissingSecretPolicy", | ||
| "parse_tool_config", | ||
| "parse_tool_configs", | ||
| "coerce_tool_config", | ||
| "coerce_tool_configs", | ||
| "ToolError", | ||
| "ToolConfigError", | ||
| "ToolConfigurationError", | ||
| "GatewayToolResolutionError", | ||
| "UnsupportedToolProviderError", | ||
| "MissingToolSecretError", | ||
| "DuplicateToolNameError", | ||
| # MCP is a sibling subsystem | ||
| "MCPServerConfig", | ||
| "ResolvedMCPServer", | ||
| "MCPResolver", | ||
| "MCPError", | ||
| "MCPConfigurationError", | ||
| "MissingMCPSecretError", | ||
| # Interfaces (ports) | ||
| "Backend", | ||
| "Sandbox", | ||
| "Session", | ||
| "SessionStore", | ||
| "NoopSessionStore", | ||
| "Environment", | ||
| "Harness", | ||
| # Errors | ||
| "AgentRunnerConfigurationError", | ||
| "UnsupportedHarnessError", | ||
| "ToolResolutionError", | ||
| # Adapters | ||
| "SandboxAgentBackend", | ||
| "InProcessPiBackend", | ||
| "LocalBackend", | ||
| "PiHarness", | ||
| "ClaudeHarness", | ||
| "AgentaHarness", | ||
| "make_harness", | ||
| ] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| """Adapters: concrete implementations of the agent runtime ports. | ||
|
|
||
| - Backend adapters: ``SandboxAgentBackend`` (sandbox-agent over ACP), ``InProcessPiBackend`` (in-process Pi, | ||
| the reference backend), ``LocalBackend`` (standalone SDK runs; not yet implemented). | ||
| - Harness adapters: ``PiHarness``, ``ClaudeHarness``, ``AgentaHarness`` (+ ``make_harness``). | ||
| - HTTP/browser protocol adapters live in subpackages, e.g. ``adapters.vercel``. | ||
|
|
||
| Shared plumbing for the runner-backed adapters lives in ``agents/utils``. | ||
| """ | ||
|
|
||
| from .harnesses import AgentaHarness, ClaudeHarness, PiHarness, make_harness | ||
| from .in_process import InProcessPiBackend | ||
| from .local import LocalBackend | ||
| from .sandbox_agent import SandboxAgentBackend | ||
|
|
||
| __all__ = [ | ||
| "SandboxAgentBackend", | ||
| "InProcessPiBackend", | ||
| "LocalBackend", | ||
| "PiHarness", | ||
| "ClaudeHarness", | ||
| "AgentaHarness", | ||
| "make_harness", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| """Shared constructor validation for runner-backed adapters.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from pathlib import Path | ||
| from typing import List, Optional, Sequence | ||
|
|
||
| from ..errors import AgentRunnerConfigurationError | ||
|
|
||
| DEFAULT_RUNNER_COMMAND = ["pnpm", "exec", "tsx", "src/cli.ts"] | ||
| RUNNER_CLI_PATH = Path("src") / "cli.ts" | ||
|
|
||
|
|
||
| def resolve_runner_command( | ||
| *, | ||
| backend_name: str, | ||
| url: Optional[str], | ||
| command: Optional[Sequence[str]], | ||
| cwd: Optional[str], | ||
| ) -> List[str]: | ||
| def _validated_command(raw: Sequence[str]) -> List[str]: | ||
| cmd = list(raw) | ||
| if not cmd: | ||
| raise AgentRunnerConfigurationError( | ||
| f"{backend_name} received an empty command. Pass a non-empty command, " | ||
| "pass url for an HTTP runner, or set cwd to a runner directory containing " | ||
| f"{RUNNER_CLI_PATH.as_posix()}." | ||
| ) | ||
| return cmd | ||
|
|
||
| if url: | ||
| return ( | ||
| _validated_command(command) | ||
| if command is not None | ||
| else list(DEFAULT_RUNNER_COMMAND) | ||
| ) | ||
| if command is not None: | ||
| return _validated_command(command) | ||
| if not cwd: | ||
| raise AgentRunnerConfigurationError( | ||
| f"{backend_name} requires a runner transport: pass url for an HTTP runner, " | ||
| "pass command for a custom subprocess runner, or pass cwd pointing to a " | ||
| f"runner directory containing {RUNNER_CLI_PATH.as_posix()}." | ||
| ) | ||
|
|
||
| cli_path = Path(cwd) / RUNNER_CLI_PATH | ||
| if not cli_path.is_file(): | ||
| raise AgentRunnerConfigurationError( | ||
| f"{backend_name} could not find runner CLI at {cli_path}. Pass url for an " | ||
| "HTTP runner, pass command for a custom subprocess runner, or set cwd to a " | ||
| f"runner directory containing {RUNNER_CLI_PATH.as_posix()}." | ||
| ) | ||
|
|
||
| return list(DEFAULT_RUNNER_COMMAND) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| """The Agenta harness's forced defaults: the things ``AgentaHarness`` always applies. | ||
|
|
||
| ``AgentaHarness`` is Pi with an opinion. It is the same engine as :class:`PiHarness`, but | ||
| every run carries a fixed set of Agenta-shipped extras the author cannot turn off: | ||
|
|
||
| - a base **persona** appended to Pi's system prompt (``AGENTA_FORCED_APPEND_SYSTEM``), | ||
| - a base **AGENTS.md preamble** the author's instructions are appended to (``AGENTA_PREAMBLE``), | ||
| - a set of **forced tools** (``AGENTA_FORCED_TOOLS``), and | ||
| - a set of **forced skills** (``AGENTA_FORCED_SKILLS``). | ||
|
|
||
| The forced *policy* lives here (harness knowledge). The forced skill *files* live with the | ||
| runner that runs Pi, under ``services/agent/skills/<name>/``; the contract between the two is | ||
| the skill directory **name**, so each entry in ``AGENTA_FORCED_SKILLS`` must match a committed | ||
| directory there. | ||
|
|
||
| Two layers, kept distinct on purpose (matching Pi's own split, see :class:`PiAgentConfig`): | ||
| the *persona* is an ``append_system`` (changes Pi's base prompt), while *project conventions* | ||
| belong in ``AGENTS.md``. ``AGENTA_PREAMBLE`` is the AGENTS.md layer; ``AGENTA_FORCED_APPEND_SYSTEM`` | ||
| is the persona layer. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import List, Optional | ||
|
|
||
| # The base AGENTS.md preamble. The author's own ``instructions`` are appended after this, so | ||
| # the final AGENTS.md is ``AGENTA_PREAMBLE`` + the author's project conventions. | ||
| # | ||
| # TODO(product): replace this placeholder with the real Agenta AGENTS.md preamble. | ||
| AGENTA_PREAMBLE = """\ | ||
| # Agenta agent | ||
|
|
||
| You are an agent running on the Agenta platform. The instructions below are Agenta's | ||
| baseline; the user's own instructions follow and take precedence where they are more | ||
| specific. | ||
|
|
||
| - Prefer the tools and skills provided to you over guessing. | ||
| - When a skill matches the task, read its SKILL.md fully before acting. | ||
| - Keep answers grounded in what the tools and skills actually return.""" | ||
|
|
||
| # The base persona, always appended to Pi's built-in system prompt (never replaces it). This | ||
| # is the "who the agent is" layer, distinct from the AGENTS.md project-context layer above. | ||
| # | ||
| # TODO(product): replace this placeholder with the real Agenta persona framing. | ||
| AGENTA_FORCED_APPEND_SYSTEM = """\ | ||
| You are an Agenta agent. Be precise, cite what your tools and skills return, and do not | ||
| fabricate results.""" | ||
|
|
||
| # Built-in tools every Agenta run forces on, unioned with the agent's resolved tools. | ||
| # ``read`` is mandatory: Pi only renders the skills section into the system prompt when the | ||
| # ``read`` tool is available. ``bash`` lets skills run their helper scripts. | ||
| AGENTA_FORCED_TOOLS: List[str] = ["read", "bash"] | ||
|
|
||
| # Built-in skills every Agenta run forces on. Each name must match a committed directory under | ||
| # the runner's ``services/agent/skills/<name>/`` (the runner resolves names to those dirs). | ||
| # | ||
| # TODO(product): grow this with the real Agenta skill set. | ||
| AGENTA_FORCED_SKILLS: List[str] = ["agenta-getting-started"] | ||
|
|
||
|
|
||
| def _join(*parts: Optional[str]) -> Optional[str]: | ||
| """Join the non-empty parts with a blank line, or ``None`` when nothing remains.""" | ||
| kept = [part.strip() for part in parts if part and part.strip()] | ||
| if not kept: | ||
| return None | ||
| return "\n\n".join(kept) | ||
|
|
||
|
|
||
| def compose_instructions(user: Optional[str]) -> Optional[str]: | ||
| """The AGENTS.md the harness ships: the base preamble with the author's instructions | ||
| appended after it.""" | ||
| return _join(AGENTA_PREAMBLE, user) | ||
|
|
||
|
|
||
| def compose_append_system(user: Optional[str]) -> Optional[str]: | ||
| """The ``append_system`` the harness ships: the forced base persona with the author's own | ||
| ``append_system`` appended after it.""" | ||
| return _join(AGENTA_FORCED_APPEND_SYSTEM, user) | ||
|
|
||
|
|
||
| def force_tools(builtin_tools: List[str]) -> List[str]: | ||
| """Union the resolved built-in tools with the forced set, order-stable and de-duplicated | ||
| (resolved tools first, then any forced tools not already present).""" | ||
| seen = set() | ||
| out: List[str] = [] | ||
| for name in list(builtin_tools) + AGENTA_FORCED_TOOLS: | ||
| if name and name not in seen: | ||
| seen.add(name) | ||
| out.append(name) | ||
| return out |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.