Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdks/python/agenta/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
# `agenta.sdk.agents` when needed.
from .sdk.agents import ( # noqa: F401
AgentaHarness,
AgentConfig,
AgentTemplate,
ClaudeHarness,
Environment,
LocalBackend,
Expand Down
24 changes: 12 additions & 12 deletions sdks/python/agenta/sdk/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Layers (Agenta's hexagonal vocabulary):

- ``dtos.py`` — data contracts (``AgentConfig``, ``SessionConfig``, ``Message``, ...).
- ``dtos.py`` — data contracts (``AgentTemplate``, ``SessionConfig``, ``Message``, ...).
- ``interfaces.py`` — the ports (ABCs): ``Backend``, ``Environment``, ``Sandbox``,
``Session``, ``Harness``.
- ``adapters/`` — implementations: ``SandboxAgentBackend`` / ``LocalBackend``
Expand All @@ -15,7 +15,7 @@
from agenta.sdk.agents import Message

cfg = ag.ConfigManager.get_from_registry(app_slug="my-agent")
agent = ag.AgentConfig.from_params(cfg)
agent = ag.AgentTemplate.from_params(cfg)
harness = ag.PiHarness(ag.Environment(ag.SandboxAgentBackend()))
result = await harness.prompt(ag.SessionConfig(agent=agent), [Message(role="user", content="hi")])
"""
Expand Down Expand Up @@ -53,21 +53,21 @@
UnsupportedProviderError,
)
from .dtos import (
AgentaAgentConfig,
AgentConfig,
AgentaAgentTemplate,
AgentTemplate,
Event,
AgentResult,
ClaudeAgentConfig,
ClaudeAgentTemplate,
ContentBlock,
HARNESS_IDENTITIES,
HarnessAgentConfig,
HarnessAgentTemplate,
HarnessCapabilities,
HarnessIdentity,
HarnessType,
Message,
NetworkEgress,
PermissionPolicy,
PiAgentConfig,
PiAgentTemplate,
RunContext,
RunContextReference,
RunContextTrace,
Expand Down Expand Up @@ -150,12 +150,12 @@

__all__ = [
# DTOs
"AgentConfig",
"AgentTemplate",
"SessionConfig",
"HarnessAgentConfig",
"PiAgentConfig",
"ClaudeAgentConfig",
"AgentaAgentConfig",
"HarnessAgentTemplate",
"PiAgentTemplate",
"ClaudeAgentTemplate",
"AgentaAgentTemplate",
"HarnessType",
"HarnessIdentity",
"HARNESS_IDENTITIES",
Expand Down
2 changes: 1 addition & 1 deletion sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
layer); the server-side ``StaticWorkflowCatalog`` imports the same constant so the embed path
and the forced path stay one source of truth.

Two layers, kept distinct on purpose (matching Pi's own split, see :class:`PiAgentConfig`):
Two layers, kept distinct on purpose (matching Pi's own split, see :class:`PiAgentTemplate`):
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.
Expand Down
20 changes: 10 additions & 10 deletions sdks/python/agenta/sdk/agents/adapters/harnesses.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
from agenta.sdk.utils.logging import get_module_logger

from ..dtos import (
AgentaAgentConfig,
ClaudeAgentConfig,
AgentaAgentTemplate,
ClaudeAgentTemplate,
HarnessType,
PiAgentConfig,
PiAgentTemplate,
SessionConfig,
)
from ..interfaces import Environment, Harness
Expand Down Expand Up @@ -58,14 +58,14 @@ def _normalize_tool_specs(specs: List[Dict[str, Any]]) -> List[ToolSpec]:
class PiHarness(Harness):
harness_type = HarnessType.PI

def _to_harness_config(self, config: SessionConfig) -> PiAgentConfig:
def _to_harness_config(self, config: SessionConfig) -> PiAgentTemplate:
# Pi delivers tools natively: built-in names plus resolved specs registered through
# the Pi extension. Pi does not gate tool use, so the permission policy is dropped.
# Pi reads its own slice of the neutral harness_kwargs bag (the `pi_core` key, shared
# by both Pi-family harnesses): `system` replaces Pi's base prompt, `append_system`
# extends it (both leave AGENTS.md untouched).
pi_options = config.agent.harness_kwargs.get(HarnessType.PI.value, {})
return PiAgentConfig(
return PiAgentTemplate(
agents_md=config.agent.instructions,
model=config.agent.model,
resolved_connection=config.resolved_connection,
Expand All @@ -84,7 +84,7 @@ def _to_harness_config(self, config: SessionConfig) -> PiAgentConfig:
class ClaudeHarness(Harness):
harness_type = HarnessType.CLAUDE

def _to_harness_config(self, config: SessionConfig) -> ClaudeAgentConfig:
def _to_harness_config(self, config: SessionConfig) -> ClaudeAgentTemplate:
# Claude has no Pi built-in tools; drop them rather than ship a name Claude cannot
# honor. Tools go over MCP, and Claude gates tool use, so the permission policy is
# carried through.
Expand All @@ -96,11 +96,11 @@ def _to_harness_config(self, config: SessionConfig) -> ClaudeAgentConfig:
# Skills stay on the harness config; the runner materializes them under `.claude/skills`
# in the session cwd so Claude ACP can load the same resolved inline packages.
# The whole neutral harness_kwargs bag (plus sandbox_permission + mcp_servers) is threaded
# onto the ClaudeAgentConfig; the config's `wire_harness_files` (the Python claude adapter)
# onto the ClaudeAgentTemplate; the config's `wire_harness_files` (the Python claude adapter)
# parses the `claude.permissions` slice and renders `.claude/settings.json` as a generic
# `harnessFiles` entry. No claude-specific parsing happens here; the runner just writes the
# files into the cwd.
return ClaudeAgentConfig(
return ClaudeAgentTemplate(
agents_md=config.agent.instructions,
model=config.agent.model,
resolved_connection=config.resolved_connection,
Expand All @@ -125,11 +125,11 @@ class AgentaHarness(Harness):

harness_type = HarnessType.AGENTA

def _to_harness_config(self, config: SessionConfig) -> AgentaAgentConfig:
def _to_harness_config(self, config: SessionConfig) -> AgentaAgentTemplate:
# The author's Pi options still apply; the pi_agenta harness reads the same `pi_core`
# slice as PiHarness (it drives Pi) and layers its forced extras on top.
pi_options = config.agent.harness_kwargs.get(HarnessType.PI.value, {})
return AgentaAgentConfig(
return AgentaAgentTemplate(
agents_md=compose_instructions(config.agent.instructions),
model=config.agent.model,
resolved_connection=config.resolved_connection,
Expand Down
4 changes: 2 additions & 2 deletions sdks/python/agenta/sdk/agents/adapters/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from typing import Mapping, Optional

from ..dtos import HarnessAgentConfig, HarnessType, RunContext, TraceContext
from ..dtos import HarnessAgentTemplate, HarnessType, RunContext, TraceContext
from ..interfaces import Backend, Sandbox, Session


Expand All @@ -40,7 +40,7 @@ async def create_sandbox(self) -> Sandbox:
async def create_session(
self,
sandbox: Sandbox,
config: HarnessAgentConfig,
config: HarnessAgentTemplate,
*,
harness: HarnessType,
secrets: Optional[Mapping[str, str]] = None,
Expand Down
6 changes: 3 additions & 3 deletions sdks/python/agenta/sdk/agents/adapters/sandbox_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from ..dtos import (
AgentResult,
EventSink,
HarnessAgentConfig,
HarnessAgentTemplate,
HarnessType,
Message,
RunContext,
Expand Down Expand Up @@ -61,7 +61,7 @@ def __init__(
self,
backend: "SandboxAgentBackend",
sandbox: SandboxAgentSandbox,
config: HarnessAgentConfig,
config: HarnessAgentTemplate,
*,
harness: HarnessType,
secrets: Optional[Mapping[str, str]],
Expand Down Expand Up @@ -153,7 +153,7 @@ async def create_sandbox(self) -> SandboxAgentSandbox:
async def create_session(
self,
sandbox: Sandbox,
config: HarnessAgentConfig,
config: HarnessAgentTemplate,
*,
harness: HarnessType,
secrets: Optional[Mapping[str, str]] = None,
Expand Down
50 changes: 25 additions & 25 deletions sdks/python/agenta/sdk/agents/dtos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Everything the ports and adapters pass around: harness identity, capabilities, content
blocks, messages, run events, the run result, trace/tool-callback plumbing, the neutral
``AgentConfig``, the per-harness configs a backend plumbs, and the ``SessionConfig`` bundle.
``AgentTemplate``, the per-harness configs a backend plumbs, and the ``SessionConfig`` bundle.

These are Pydantic models (the SDK already depends on Pydantic), kept neutral: an adapter
translates them to and from its engine's own shapes at its edge.
Expand Down Expand Up @@ -66,7 +66,7 @@ def coerce(cls, value: "HarnessType | str") -> "HarnessType":
# (``agenta:<namespace>:<name>:v<N>``, mirroring ``agenta:builtin:agent:v0`` in
# ``engines/running/interfaces.py``). The namespace is ``harness`` and the trailing ``v0`` is
# bumped only when the harness contract shape breaks. This is purely the INTERFACE identity the
# agent_config schema advertises; the stored/wire harness VALUE stays the bare enum string
# agent_template schema advertises; the stored/wire harness VALUE stays the bare enum string
# (``pi_core`` / ``pi_agenta`` / ``claude``), which the runner reads as the runtime selector.


Expand All @@ -75,7 +75,7 @@ class HarnessIdentity(BaseModel):

``value`` is the wire/runtime selector (the ``HarnessType`` value); ``slug`` is the
versioned contract identity in the repo's slug grammar; ``name`` is the human-facing label
the playground dropdown shows. This is the single source the agent_config schema builds the
the playground dropdown shows. This is the single source the agent_template schema builds the
harness ``oneOf`` from, so the slug, name, and value never drift across the SDK, the service
schema, and the frontend control."""

Expand Down Expand Up @@ -132,7 +132,7 @@ class SandboxPermission(BaseModel):

``network`` is the outbound-egress policy; ``filesystem`` is declared but not enforced
today; ``enforcement`` picks ``strict`` (fail the run when the boundary cannot be applied)
or ``best_effort``. Optional on :class:`AgentConfig`: an unset value never reaches the wire,
or ``best_effort``. Optional on :class:`AgentTemplate`: an unset value never reaches the wire,
so existing configs are unaffected."""

network: NetworkEgress = Field(default_factory=NetworkEgress)
Expand Down Expand Up @@ -494,14 +494,14 @@ class AgentResult(BaseModel):
# ---------------------------------------------------------------------------


class AgentConfig(BaseModel):
class AgentTemplate(BaseModel):
"""What an agent is and how it runs — the single agent definition. ``instructions`` becomes
``AGENTS.md``. ``tools`` are provider-agnostic references; resolving them into runnable
specs is the caller's job (the Agenta service does it server-side).

``harness`` / ``sandbox`` / ``permission_policy`` are the run-selection fields: which
coding agent to drive, where it runs, and how a permission-gating harness answers tool-use
prompts in a headless run. They live on ``AgentConfig`` (under ``data.parameters.agent``)
prompts in a headless run. They live on ``AgentTemplate`` (under ``data.parameters.agent``)
rather than a separate object — there is one agent definition, not an agent plus a sidecar
selection. ``sandbox`` is a backend/environment concern the caller reads to pick a backend;
it never enters ``SessionConfig`` or the neutral run.
Expand Down Expand Up @@ -562,9 +562,9 @@ def from_params(
cls,
params: Dict[str, Any],
*,
defaults: Optional["AgentConfig"] = None,
) -> "AgentConfig":
"""Build an :class:`AgentConfig` from a request/config dict.
defaults: Optional["AgentTemplate"] = None,
) -> "AgentTemplate":
"""Build an :class:`AgentTemplate` from a request/config dict.

Accepts three shapes, in priority order: the dedicated ``agent`` element, the
playground ``prompt`` prompt-template (system message -> instructions, ``llm_config``
Expand Down Expand Up @@ -595,7 +595,7 @@ def from_params(
# ---------------------------------------------------------------------------


class HarnessAgentConfig(BaseModel):
class HarnessAgentTemplate(BaseModel):
"""Base for a harness-specific config. A Harness produces one of these from the neutral
config; a backend plumbs it as-is, with no business logic about how the harness works.

Expand Down Expand Up @@ -628,7 +628,7 @@ class HarnessAgentConfig(BaseModel):
skills: List[SkillTemplate] = Field(default_factory=list)
sandbox_permission: Optional[SandboxPermission] = None
# The neutral per-harness options bag (a map keyed by harness name), carried verbatim from
# ``AgentConfig.harness_kwargs`` by the harness adapter. The active harness's CONFIG translates
# ``AgentTemplate.harness_kwargs`` by the harness adapter. The active harness's CONFIG translates
# its own slice into rendered files for the wire (see :meth:`wire_harness_files`); the raw bag
# itself does not ride the wire anymore.
harness_kwargs: Dict[str, Dict[str, Any]] = Field(default_factory=dict)
Expand Down Expand Up @@ -747,7 +747,7 @@ def wire_resolved_connection(self) -> Dict[str, Any]:
return self.resolved_connection.to_wire()


class PiAgentConfig(HarnessAgentConfig):
class PiAgentTemplate(HarnessAgentTemplate):
"""Pi's config. Built-in tools by name plus resolved specs delivered natively (Pi has no
MCP; the runner registers them through the Pi extension). Pi does not gate tool use, so
no permission policy applies.
Expand Down Expand Up @@ -805,7 +805,7 @@ def wire_prompt(self) -> Dict[str, Any]:
return out


class ClaudeAgentConfig(HarnessAgentConfig):
class ClaudeAgentTemplate(HarnessAgentTemplate):
"""Claude's config. No Pi built-ins; tools are delivered over MCP, and
``permission_policy`` answers Claude's tool-use prompts in a headless run."""

Expand Down Expand Up @@ -862,7 +862,7 @@ def wire_harness_files(self) -> Dict[str, Any]:
return {"harnessFiles": files}


class AgentaAgentConfig(PiAgentConfig):
class AgentaAgentTemplate(PiAgentTemplate):
"""The Agenta harness's config. It *is* a Pi config (same engine, same tool delivery and
system-prompt layers). ``skills`` ride the inherited :meth:`wire_skills` seam as resolved
inline packages, not through ``wire_tools`` (skills are not tools)."""
Expand All @@ -887,7 +887,7 @@ class SessionConfig(BaseModel):

model_config = ConfigDict(populate_by_name=True)

agent: AgentConfig
agent: AgentTemplate
secrets: Dict[str, str] = Field(default_factory=dict)
# ``resolved_connection`` carries the least-privilege output of a ``ConnectionResolver``.
# ``secrets`` is the compatibility alias for ``resolved_connection.env`` during the
Expand Down Expand Up @@ -951,8 +951,8 @@ def _as_list(raw: Any) -> List[Any]:
def _split_model_ref(data: Any) -> Any:
"""Populate ``model_ref`` from a structured ``model`` and keep ``model`` a plain string.

Shared ``mode="before"`` validator body for :class:`AgentConfig` and
:class:`HarnessAgentConfig`. The lowest-risk wiring (no behavior change in Slice 1):
Shared ``mode="before"`` validator body for :class:`AgentTemplate` and
:class:`HarnessAgentTemplate`. The lowest-risk wiring (no behavior change in Slice 1):

- ``model`` is a dict or a :class:`ModelRef` -> set ``model_ref`` from it and project
``model`` to its plain ``provider/model`` string. A structured config gains a typed ref
Expand All @@ -978,12 +978,12 @@ def _split_model_ref(data: Any) -> Any:

def _parse_mcp_servers_raw(
params: Dict[str, Any],
defaults: AgentConfig,
defaults: AgentTemplate,
) -> List[Any]:
"""Pull the raw ``mcp_servers`` list from a request/config dict, falling back to defaults.

Reads ``mcp_servers`` from the ``agent`` element when present, else the flat request.
Canonical validation happens on :class:`AgentConfig` construction."""
Canonical validation happens on :class:`AgentTemplate` construction."""
agent = params.get("agent")
source = agent if isinstance(agent, dict) else params
raw = source.get("mcp_servers")
Expand All @@ -994,13 +994,13 @@ def _parse_mcp_servers_raw(

def _parse_skills_raw(
params: Dict[str, Any],
defaults: AgentConfig,
defaults: AgentTemplate,
) -> List[Any]:
"""Pull the raw ``skills`` list from a request/config dict, falling back to defaults.

Reads ``skills`` from the ``agent`` element when present, else the flat request. Mirrors
the MCP path so an unparsed ``skills`` is not silently dropped; canonical validation happens
on :class:`AgentConfig` construction. Each entry is a concrete inline ``SkillTemplate`` by the
on :class:`AgentTemplate` construction. Each entry is a concrete inline ``SkillTemplate`` by the
time the request is built (any ``@ag.embed`` reference resolved server-side first)."""
agent = params.get("agent")
source = agent if isinstance(agent, dict) else params
Expand All @@ -1012,7 +1012,7 @@ def _parse_skills_raw(

def _parse_harness_kwargs(
params: Dict[str, Any],
defaults: AgentConfig,
defaults: AgentTemplate,
) -> Dict[str, Dict[str, Any]]:
"""Pull the per-harness options bag from a request/config dict, falling back to defaults.

Expand All @@ -1037,7 +1037,7 @@ def _parse_harness_kwargs(

def _parse_run_selection(
params: Dict[str, Any],
defaults: AgentConfig,
defaults: AgentTemplate,
) -> Tuple[str, str, "PermissionPolicy"]:
"""Pull the run-selection trio (harness / sandbox / permission_policy) from a request dict.

Expand All @@ -1056,7 +1056,7 @@ def _parse_run_selection(

def _parse_sandbox_permission(
params: Dict[str, Any],
defaults: AgentConfig,
defaults: AgentTemplate,
) -> Optional[SandboxPermission]:
"""Pull the sandbox permission object from a request/config dict, falling back to defaults.

Expand Down Expand Up @@ -1095,7 +1095,7 @@ def _system_text(messages: Optional[List[Any]]) -> str:

def _parse_agent_fields(
params: Dict[str, Any],
defaults: AgentConfig,
defaults: AgentTemplate,
) -> Tuple[Optional[str], Optional[str], Any]:
"""Pull (instructions, model, tools) from a request/config dict, with fallbacks."""
agent = params.get("agent")
Expand Down
Loading
Loading