Skip to content
Open
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
14 changes: 5 additions & 9 deletions docs/design/agent-workflows/documentation/adapters/agenta.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ adapter](pi.md) and produces a Pi-shaped config, so it inherits everything Pi do
tools, the system-prompt layers, tracing). What it adds is a fixed set of Agenta-shipped
extras that the agent author cannot turn off:

- **Forced tools**: always unioned into the agent's resolved tools. At minimum `read`
(Pi only renders the skills section when `read` is enabled) and `bash` (so skills can run
their helper scripts).
- **Forced skills**: Agenta-shipped Pi skills loaded on every run.
- **A base AGENTS.md preamble**: the author's `instructions` are appended after it.
- **A base persona**: forced onto Pi's `append_system`, with any author-supplied
Expand All @@ -25,15 +22,14 @@ The forced *policy* lives in the SDK harness layer, in one editable module:
`SessionConfig`, exactly where `PiHarness` and `ClaudeHarness` do their own translation.

The forced skill *files* live with the runner that runs Pi, under
`services/agent/skills/<name>/` (each a directory with a `SKILL.md`). Skills are real files on
`services/runner/skills/<name>/` (each a directory with a `SKILL.md`). Skills are real files on
disk because they reference relative scripts and assets, so they cannot ride the wire as
text. The contract between the two halves is the skill **name**: `AGENTA_FORCED_SKILLS` lists
names, and each must match a committed directory under the runner's skills root.

Because the Agenta harness IS Pi, its tools are delivered the Pi-native way (through the
extension on the ACP path), never over MCP. There is no forced tool set any more: the runner
activates all seven Pi built-ins on every Pi run, so `read` and `bash` are there without anything
forcing them.
extension on the ACP path), never over MCP. The runner activates all seven Pi built-ins on every
Pi run, so `read` and `bash` are there without additional configuration.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## How a skill reaches the model

Expand All @@ -47,7 +43,7 @@ runner lays the bundled directories into the Pi agent dir.
2. `runSandboxAgent` resolves each name against its bundled `skills/` root
(`engines/skills.ts`, override with `AGENTA_AGENT_SKILLS_DIR`) and writes the directories
into the Pi agent dir's `skills/` (user scope).
3. Pi loads them, and because the forced `read` tool is enabled, surfaces them in the system
3. Pi loads them, and because the native `read` tool is active, surfaces them in the system
prompt. The model reads a skill's `SKILL.md` on demand (progressive disclosure).

## Two prompt layers, kept distinct
Expand Down Expand Up @@ -86,7 +82,7 @@ removed after the run. A plain `pi_core` run is unchanged (it installs only the
the shared agent dir).

The base AGENTS.md preamble rides the wire as `agentsMd` (written into the session `cwd`), and
the forced `read` / `bash` tools are Pi defaults under pi-acp. The persona rides the wire as
the active `read` / `bash` built-ins are Pi defaults under pi-acp. The persona rides the wire as
`appendSystemPrompt` and the engine writes it into the per-run Pi agent dir as
`APPEND_SYSTEM.md` (`engines/sandbox_agent/pi-assets.ts`), so Pi loads it on the run. Daytona
skill uploads are UTF-8 text only (`writeFsFile` takes a string body); binary skill assets are
Expand Down
38 changes: 20 additions & 18 deletions docs/design/agent-workflows/documentation/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ because it is the seam between the two lives of a tool:

The resolved specs are also defined in `tools/models.py` (`CallbackToolSpec`, `CodeToolSpec`,
`ClientToolSpec`), and the matching TypeScript shape is `ResolvedToolSpec` in
`services/agent/src/protocol.ts`. A run bundles them as a `ResolvedToolSet`: the list of specs
`services/runner/src/protocol.ts`. A run bundles them as a `ResolvedToolSet`: the list of specs
and one `ToolCallback` (the endpoint callback tools post back to).

## How tools get resolved (the service side)
Expand Down Expand Up @@ -177,13 +177,13 @@ specs in `customTools`, the callback in `toolCallback`, and resolved MCP servers

The runner has to hand resolved tools to a harness, and harnesses do not accept tools the same
way. The runner branches on a capability, `mcpTools`, not on the harness name (the branch is
`buildSessionMcpServers` in `services/agent/src/engines/sandbox_agent/mcp.ts`). A harness that
`buildSessionMcpServers` in `services/runner/src/engines/sandbox_agent/mcp.ts`). A harness that
reports it can take tools over MCP gets them that way; a harness that cannot gets them
natively. Today that splits cleanly into two paths.

- **Pi takes native tools.** Pi has an extension API, so the runner registers each resolved
spec as a Pi tool directly. The bundled Pi extension
(`services/agent/src/extensions/agenta.ts`) reads the public specs from
(`services/runner/src/extensions/agenta.ts`) reads the public specs from
`AGENTA_TOOL_PUBLIC_SPECS` and registers them from inside Pi, then Pi runs the tool body the
runner gives it. Pi gets no MCP server at all here: `buildSessionMcpServers` returns an empty
list for Pi, so neither the synthetic `agenta-tools` server nor any user MCP server is
Expand Down Expand Up @@ -213,7 +213,7 @@ natively. Today that splits cleanly into two paths.
[runner-to-MCP interface page](../interfaces/cross-service/runner-to-mcp-server.md).

Both paths funnel execution through one function, `runResolvedTool` in
`services/agent/src/tools/dispatch.ts`. It is the single place that branches on `kind`, so how
`services/runner/src/tools/dispatch.ts`. It is the single place that branches on `kind`, so how
a tool type executes is defined once, not three times.

## Execution, type by type
Expand All @@ -225,7 +225,7 @@ picks the tool and supplies the arguments, who actually runs it, and where?

Execution is a callback. The harness selects the tool and supplies arguments, but the runner
does not run the integration. The tool body POSTs the call to Agenta's `POST /tools/call`
(`services/agent/src/tools/callback.ts`, `callAgentaTool`), sending the `call_ref` slug and
(`services/runner/src/tools/callback.ts`, `callAgentaTool`), sending the `call_ref` slug and
the model's arguments in an OpenAI-style envelope. The API re-resolves the connection, runs the
Composio action through the provider adapter (`execute_tool` in `core/tools/service.py`), and
returns the result, which the runner hands back to the model verbatim.
Expand Down Expand Up @@ -305,7 +305,7 @@ new endpoint and no hidden logic. It resolves to a `CallbackToolSpec` carrying a
descriptor (`{method, path, body?, context?, args_into?}`) instead of a `call_ref`, so the runner
calls the endpoint directly with the run's caller credential. There is no `/tools/call` hop. The
SSRF guard binds the call to the run's own Agenta origin and confines it to the API mount
(`directCallUrl` in `services/agent/src/tools/direct.ts`); the same dispatch handles the Daytona
(`directCallUrl` in `services/runner/src/tools/direct.ts`); the same dispatch handles the Daytona
relay path. The runner needs no platform-specific code — it dispatches any `call` opaquely (the
branch already exists for reference tools).

Expand All @@ -321,7 +321,7 @@ direct call.
### Code tools: the runner runs them locally

Execution is a local subprocess inside the runner. `runCodeTool`
(`services/agent/src/tools/code.ts`) writes the snippet to a temp file, spawns `python3` or
(`services/runner/src/tools/code.ts`) writes the snippet to a temp file, spawns `python3` or
`node`, passes the model's arguments as JSON on stdin, and reads the JSON result from stdout.
There is no callback. The code runs where the harness runs.

Expand All @@ -335,7 +335,7 @@ Node as `main(inputs)`. A non-zero exit or a timeout becomes a tool error so the
continues rather than crashing the run.

The production image ships the interpreters: the runner Dockerfile installs `python3`
(`services/agent/docker/Dockerfile`), and `node` is already present. An earlier missing
(`services/runner/docker/Dockerfile.gh`), and `node` is already present. An earlier missing
`python3` made Python code tools fail with `spawn python3 ENOENT`; that is fixed. One real
constraint remains: the child only has the interpreter and the tool's own secrets, with no
package-install step and no `NODE_PATH` to the runner's modules. So a code tool is limited to
Expand Down Expand Up @@ -383,7 +383,7 @@ member of `RenderHint` that asks the frontend to draw the connect widget.
### Built-in tools: the harness runs them natively, gated through the same relay

Execution is the harness's own. A built-in tool is just a name. The runner adds it to the
session's allowlist and Pi runs its own implementation of `read`, `write`, `web_search`, and so
session's allowlist and Pi runs its own implementation of `read`, `write`, `bash`, and so
on. Nothing is resolved and nothing is delivered. Note that built-ins are a Pi concept here;
they are not delivered to non-Pi harnesses over ACP, which bring their own native tool set.

Expand Down Expand Up @@ -413,8 +413,10 @@ parity test pins that copy against the same fixture.

Because activation is unconditional, the seven names are reserved. Pi registers custom tools in
the same registry as its builtins, so a custom tool named `read` would replace the builtin `read`
silently. `ToolResolver` refuses such a config with `ReservedToolNameError`, and the extension
skips a colliding spec rather than registering it.
silently. The SDK's `ToolResolver` refuses such a declared custom tool with
`ReservedToolNameError`, and the extension skips a colliding spec rather than registering it. The
runner also folds colliding names into the built-in identity when matching permissions on an
unvalidated `/run` payload, so the same defense applies at the execution boundary.

The wire's `tools` field is deprecated. A current runner ignores it. The SDK still fills it with
all seven names so a runner from before this change — which read it as a grant list — activates
Expand Down Expand Up @@ -659,18 +661,18 @@ never drift from the files that exist. The canonical playbook format lives in th
| Discovery endpoint + reserved-handler dispatch | `api/oss/src/apis/fastapi/tools/router.py` (`/tools/discover`, `_call_reserved_agenta_tool`) |
| Server-side platform-op handlers (reserved-ref registry, `test_run`) | `api/oss/src/core/tools/platform_handlers.py` |
| Build-kit overlay defaults (`DEFAULT_BUILD_KIT_OPS` + skill/tool embeds) | `api/oss/src/apis/fastapi/applications/overlay.py` |
| Wire contract | `services/agent/src/protocol.ts`, `sdks/python/agenta/sdk/agents/utils/wire.py` |
| Tool-delivery fork (branch on `mcpTools`) | `services/agent/src/engines/sandbox_agent/mcp.ts` |
| Runtime dispatch (branch on `kind`) | `services/agent/src/tools/dispatch.ts` |
| Callback transport | `services/agent/src/tools/callback.ts` |
| Code execution | `services/agent/src/tools/code.ts` |
| Wire contract | `services/runner/src/protocol.ts`, `sdks/python/agenta/sdk/agents/utils/wire.py` |
| Tool-delivery fork (branch on `mcpTools`) | `services/runner/src/engines/sandbox_agent/mcp.ts` |
| Runtime dispatch (branch on `kind`) | `services/runner/src/tools/dispatch.ts` |
| Callback transport | `services/runner/src/tools/callback.ts` |
| Code execution | `services/runner/src/tools/code.ts` |
| Daytona/non-Pi relay (runner-side loop) | `services/runner/src/tools/relay.ts` |
| In-sandbox relay writer + wire protocol | `services/runner/src/tools/relay-client.ts`, `relay-protocol.ts` |
| Relay wake sources (local `fs.watch`, Daytona watch exec) | `services/runner/src/tools/relay-watch.ts` |
| Pi native delivery | `services/agent/src/extensions/agenta.ts` |
| Pi native delivery | `services/runner/src/extensions/agenta.ts` |
| `agenta-tools` channel for non-Pi harnesses (local loopback HTTP) | `services/runner/src/tools/mcp-bridge.ts`, `services/runner/src/tools/tool-mcp-http.ts` |
| `agenta-tools` channel on Daytona (in-sandbox stdio shim: entrypoint, env contract, upload) | `services/runner/src/tools/tool-mcp-stdio.ts`, `services/runner/src/tools/tool-mcp-env.ts`, `services/runner/src/engines/sandbox_agent/tool-mcp-assets.ts` |
| Capability probe | `services/agent/src/engines/sandbox_agent/capabilities.ts` |
| Capability probe | `services/runner/src/engines/sandbox_agent/capabilities.ts` |
| Permission decision (shared by both gates) | `services/runner/src/permission-plan.ts` |
| ACP responder (`ApprovalResponder`) | `services/runner/src/responder.ts` |
| Tool relay enforcement | `services/runner/src/tools/relay.ts` |
Expand Down
16 changes: 8 additions & 8 deletions docs/design/agent-workflows/interfaces/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ page. `Status` is read from each page's prose: **stable** (wired and unlikely to
| [`/inspect`](public-edge/workflow-inspect.md) | public | `agent/schemas.py`, `agent/app.py` (builtin-URI binding), `models/workflows.py`, `decorators/routing.py` | stable | `unit/agents/test_dtos_agent_config.py`, `unit/agent/test_builtin_uri_binding.py` |
| [`/messages`](public-edge/agent-messages.md) | public | `adapters/vercel/{routing,messages,stream}.py`, `agentRequest.ts` | evolving (create-or-resume not observable until storage lands) | `utils/test_messages_endpoint.py`, `unit/agents/test_ui_messages.py` |
| [Agent config schema](public-edge/agent-config-schema.md) | public | `agent/schemas.py`, `sdk/utils/types.py`, `agents/dtos.py` (`HARNESS_IDENTITIES`), `sdk/agents/pi_builtins.py` (`PI_BUILTIN_TOOL_NAMES`) | stable | `unit/agents/test_dtos_agent_config.py`, `unit/agents/test_harness_identity.py`, `unit/agents/test_pi_builtins_parity.py` + `golden/pi_builtin_tools.json`, `services/oss/tests/pytest/unit/agent/test_default_agent_template.py` |
| [`/run`](cross-service/service-to-agent-runner.md) | cross-service (the spine) | `protocol.ts`, `utils/wire.py`, `utils/ts_runner.py`, `server.ts`/`cli.ts` | stable (pinned by golden) | `unit/agents/test_wire_contract.py` + `golden/`, `services/agent/tests/unit/wire-contract.test.ts` |
| [Runner to harness](cross-service/runner-to-harness.md) | cross-service (ACP) | `engines/sandbox_agent.ts` + `sandbox_agent/{run-plan,capabilities,permissions}.ts` | evolving | `services/agent/tests/unit/sandbox-agent-*.test.ts` |
| [`/run`](cross-service/service-to-agent-runner.md) | cross-service (the spine) | `protocol.ts`, `utils/wire.py`, `utils/ts_runner.py`, `server.ts`/`cli.ts` | stable (pinned by golden) | `unit/agents/test_wire_contract.py` + `golden/`, `services/runner/tests/unit/wire-contract.test.ts` |
| [Runner to harness](cross-service/runner-to-harness.md) | cross-service (ACP) | `engines/sandbox_agent.ts` + `sandbox_agent/{run-plan,capabilities,permissions}.ts` | evolving | `services/runner/tests/unit/sandbox-agent-*.test.ts` |
| [Runner to MCP server](cross-service/runner-to-mcp-server.md) | cross-service | `agents/mcp/`, `engines/sandbox_agent/{mcp,tool-mcp-assets,relay-guard}.ts`, `tools/{mcp-bridge,tool-mcp-http,tool-mcp-stdio,tool-mcp-env,relay,relay-client,relay-protocol,relay-watch}.ts` | evolving (internal channel delivered locally over loopback HTTP and on Daytona via the in-sandbox stdio shim, `client` tools included — a client call parks via a paused relay answer; user stdio disabled) | `services/runner/tests/unit/{mcp-servers,session-mcp-layering,tool-mcp-assets,tool-mcp-stdio,tool-relay-guard}.test.ts` |
| [Runner to tool callback](cross-service/runner-to-tool-callback.md) | cross-service | `tools/{callback,dispatch,direct}.ts`, `apis/fastapi/tools/router.py` (`/tools/call`, `/tools/discover`, `_call_reserved_agenta_tool`), `core/tools/{discovery,service,platform_handlers}.py`, `agent/tools/resolver.py` | evolving (the `call` descriptor is wired and platform ops emit it; the legacy `tools.agenta.find_capabilities` route is deleted; reserved refs now dispatch server handlers, resolution flag-gated off until the runner half lands) | `services/agent/tests/unit/{code-tool,extension-tools}.test.ts`, `api unit/tools/{test_workflow_tool_call,test_discovery,test_platform_handlers}.py`, `unit/agents/platform/test_op_catalog.py` |
| [Service and runner trace export](cross-service/service-and-runner-trace-export.md) | cross-service | `agent/tracing.py`, `tracing/otel.ts`, `extensions/agenta.ts` | stable | `services/agent/tests/unit/` |
| [Runner to tool callback](cross-service/runner-to-tool-callback.md) | cross-service | `tools/{callback,dispatch,direct}.ts`, `apis/fastapi/tools/router.py` (`/tools/call`, `/tools/discover`, `_call_reserved_agenta_tool`), `core/tools/{discovery,service,platform_handlers}.py`, `agent/tools/resolver.py` | evolving (the `call` descriptor is wired and platform ops emit it; the legacy `tools.agenta.find_capabilities` route is deleted; reserved refs now dispatch server handlers, resolution flag-gated off until the runner half lands) | `services/runner/tests/unit/{code-tool,extension-tools}.test.ts`, `api unit/tools/{test_workflow_tool_call,test_discovery,test_platform_handlers}.py`, `unit/agents/platform/test_op_catalog.py` |
| [Service and runner trace export](cross-service/service-and-runner-trace-export.md) | cross-service | `agent/tracing.py`, `tracing/otel.ts`, `extensions/agenta.ts` | stable | `services/runner/tests/unit/` |
| [Service to vault and tool providers](cross-service/service-to-vault-and-tool-providers.md) | cross-service (external) | `agent/app.py`, `platform/{resolve,connections}.py`, `agents/capabilities.py`, `tools/router.py` | stable | `unit/agents/connections/`, `unit/agents/platform/`, `unit/agents/tools/` |
| [Agent service handler](in-service/agent-service-handler.md) | in-service | `services/oss/src/agent/app.py` | stable | `services/oss/tests/pytest/unit/agent/` |
| [Neutral runtime DTOs](in-service/neutral-runtime-dtos.md) | in-service | `agents/dtos.py` | stable | `unit/agents/test_dtos_*.py`, `test_harness_identity.py` |
Expand All @@ -60,11 +60,11 @@ page. `Status` is read from each page's prose: **stable** (wired and unlikely to
| [Tool models and resolution](in-service/tool-models-and-resolution.md) | in-service | `agents/tools/models.py`, `platform/{gateway,workflow,op_catalog,platform_tools}.py`, `agent/tools/resolver.py` | evolving | `unit/agents/tools/`, `unit/agents/platform/test_op_catalog.py` |
| [MCP models and resolution](in-service/mcp-models-and-resolution.md) | in-service | `agents/mcp/{models,resolver,wire}.py` | evolving (stdio wired; remote deferred; resolution feature-gated) | `unit/agents/mcp/` |
| [Model connection resolution](in-service/model-connection-resolution.md) | in-service | `agent/app.py`, `agents/connections/`, `platform/{resolve,connections}.py`, `agents/capabilities.py` | stable | `unit/agents/connections/` |
| [Runner engine internals](in-service/runner-engine-internals.md) | in-service (runner) | `server.ts`, `cli.ts`, `engines/sandbox_agent.ts` | stable | `services/agent/tests/unit/{server,cli}.test.ts` |
| [Permission responder](in-service/permission-responder.md) | in-service (runner) | `responder.ts`, `engines/sandbox_agent/permissions.ts` | stable | `services/agent/tests/unit/{responder,sandbox-agent-permissions}.test.ts` |
| [Sandbox permission](in-service/sandbox-permission.md) | in-service (runner) | `agents/dtos.py`, `protocol.ts`, `engines/sandbox_agent/{provider,run-plan}.ts` | evolving (network enforced on Daytona only; local rejected; filesystem nowhere) | `services/agent/tests/unit/{sandbox-agent-provider,sandbox-agent-run-plan}.test.ts` |
| [Runner engine internals](in-service/runner-engine-internals.md) | in-service (runner) | `server.ts`, `cli.ts`, `engines/sandbox_agent.ts` | stable | `services/runner/tests/unit/{server,cli}.test.ts` |
| [Permission responder](in-service/permission-responder.md) | in-service (runner) | `responder.ts`, `engines/sandbox_agent/permissions.ts` | stable | `services/runner/tests/unit/{responder,sandbox-agent-permissions}.test.ts` |
| [Sandbox permission](in-service/sandbox-permission.md) | in-service (runner) | `agents/dtos.py`, `protocol.ts`, `engines/sandbox_agent/{provider,run-plan}.ts` | evolving (network enforced on Daytona only; local rejected; filesystem nowhere) | `services/runner/tests/unit/{sandbox-agent-provider,sandbox-agent-run-plan}.test.ts` |

Paths are relative to the owner package (`sdks/python/agenta/sdk/`, `services/agent/src/`,
Paths are relative to the owner package (`sdks/python/agenta/sdk/`, `services/runner/src/`,
`services/oss/src/`, `api/oss/src/`); test paths are relative to each package's pytest root
unless prefixed.

Expand Down
Loading
Loading