Skip to content

fix(sdk): ship Pi's built-in tools in the default agent template (#5590) - #5597

Merged
mmabrouk merged 5 commits into
release/v0.107.0from
fix/pi-default-builtins
Aug 1, 2026
Merged

fix(sdk): ship Pi's built-in tools in the default agent template (#5590)#5597
mmabrouk merged 5 commits into
release/v0.107.0from
fix/pi-default-builtins

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

Context

A saved agent could not run a shell command or write a file anywhere except the playground. Asked to do either, it answered that it had no shell or filesystem tool available.

Pi keeps seven tools inside the harness, and the run request names which of them to switch on. The runner treats a missing tools field as "use Pi's defaults" and an empty array as "grant nothing", and the Pi extension then deletes every non-granted tool from the model's list. The default agent template shipped an empty array, and the Python side always sends the field, so every saved agent said "grant nothing".

The playground hid this. Its build kit overlay adds read and bash back on every run, and nothing else does. So an agent worked while you built it and failed the moment a schedule ran it.

Changes

build_agent_v0_default() now ships Pi's own four defaults.

Before:

"tools": []

After:

"tools": [
  {"type": "builtin", "name": "read"},
  {"type": "builtin", "name": "bash"},
  {"type": "builtin", "name": "edit"},
  {"type": "builtin", "name": "write"}
]

The runner's rules are untouched. An empty list still means grant nothing, and the permission mode still gates bash, edit and write behind an approval. Having a tool is not the same as being allowed to use it.

Three supporting fixes ship with it:

The Claude harness warns only about built-ins outside that exact set. Built-ins are a Pi idea and Claude drops them, so without this every Claude run would log a warning about the four tools the template now always carries.

The built-in tool picker no longer tells you that clearing your selection leaves Pi's defaults on. It does the opposite.

The release gate seeds no longer hand-write the empty list, so the gate stops reporting green on the exact shape this change replaces.

Scope is deliberately new agents only. Agents saved before this keep their empty list and stay broken outside the playground. On one development deployment that is 66 of 73 Pi agents, so it is most of them. Repairing them is its own decision, filed as #5595 with the counting query.

Before merging

Do not deploy this to a shared deployment that still enables the local sandbox. The default sandbox is local, which runs on the runner host with no jail, and read never asks under the default permission mode. So a new agent could read host files unattended.

This adds no capability an author did not already have. Both the tool list and the sandbox are author-writable today, so anyone could already save the same agent and schedule it. What changes is what an unwitting author gets by default. Confirm AGENTA_RUNNER_ENABLED_SANDBOX_PROVIDERS excludes local before deploying, and remember that leaving it unset means local is enabled.

Tests

A crossing test now runs the real chain from the shipped default through the harness adapter to the /run body and asserts the four names arrive. Neither existing suite crossed that boundary, which is why both stayed green while every agent shipped with no tools.

The four names are pinned by a shared golden fixture that the Python and TypeScript suites both read, so the two languages cannot drift apart silently.

Runner 1308 passing, SDK 2330, API 1495, and 1427 across three web packages. Every error in the Python suites is an acceptance test asking for a live stack.

Verified on a running deployment: a fresh account's /inspect returns the four built-in entries as the default a new agent pre-fills with.

Reviewed by Codex at high effort before implementation, and by two independent reviewers after. Their findings and what was accepted or rejected are recorded in docs/design/agent-workflows/projects/default-agent-builtins/status.md.

What to QA

  • Create a new agent and open its tools. It lists read, bash, edit and write.
  • Ask it to write a file in the playground. It asks for approval first, then writes it.
  • Put that same agent on a schedule that writes a file. The run reaches the tool instead of reporting that it has none.
  • Regression: an agent you saved before this change behaves exactly as it did. It is not repaired and it is not made worse.
  • Regression: a Claude agent runs normally and its logs carry no new warning about built-in tools.

Closes #5590

@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jul 30, 2026
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Aug 1, 2026 6:00pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • New Pi agents now start with the built-in read, bash, edit, and write tools.
    • Explicitly selecting no tools continues to grant no built-ins.
    • Default tools are preserved when configuration omits the tools field, while custom selections override them.
    • Built-in tools now display clearer names and guidance in the interface.
  • Bug Fixes

    • Improved Claude compatibility warnings to trigger only when configured built-ins differ from Pi’s defaults.
    • Prevented duplicate built-in tools in agent requests.
  • Documentation

    • Clarified default tool behavior, permission handling, and configuration semantics across agent workflow documentation.

Walkthrough

The PR gives newly created Pi agents the read, bash, edit, and write built-ins. It propagates this default through SDK, service, runner, frontend, harness, release-gate, tests, and documentation paths while preserving explicit empty-list semantics.

Changes

Default Pi built-in tool flow

Layer / File(s) Summary
Default grant contract and propagation
sdks/python/..., services/oss/..., services/runner/..., api/oss/...
Defines the shared four-tool set and propagates it through default templates, service fallbacks, runner configuration, catalog materialization, and wire serialization.
Harness warning and permission gating
sdks/python/..., services/runner/tests/...
Claude warnings now use exact-set comparison. Tests cover warning contents and Pi gating under allow and allow_reads.
Frontend preservation and release-gate alignment
web/packages/..., .agents/skills/...
Frontend creation preserves and deduplicates built-ins. UI copy reflects empty-list behavior. Release-gate seeds use harness-specific defaults.
Behavior and interface documentation
docs/design/..., sdks/python/...
Documents explicit defaults, grant-list semantics, parity requirements, permission behavior, and the lack of automatic migration for existing agents.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.22% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #5590 by granting new Pi agents read, bash, edit, and write tools outside the playground while preserving existing semantics.
Out of Scope Changes check ✅ Passed The code, tests, fixtures, UI updates, release-gate changes, and documentation support the default Pi built-ins objective and related compatibility requirements.
Title check ✅ Passed The title clearly summarizes the main change: adding Pi built-in tools to the default agent template.
Description check ✅ Passed The description explains the default-tool change, supporting fixes, scope, deployment risk, and validation steps.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pi-default-builtins

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/oss/src/agent/config.py (1)

111-116: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve default tools when agent.json omits the key.

tools is initialized with DEFAULT_TOOLS, but an existing agent.json without a tools field overwrites that value with []. This leaves partial templates with no Pi tools, while explicit "tools": [] should still retain the documented grant-nothing semantics. Add coverage for this case as well.

Proposed fix
         meta = json.loads(meta_path.read_text(encoding="utf-8"))
         model = meta.get("model") or DEFAULT_MODEL
-        tools = meta.get("tools", []) or []
+        if "tools" in meta:
+            tools = meta["tools"] or []
🧹 Nitpick comments (1)
web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts (1)

58-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove any from the new workspace-package tests.

These casts can hide a broken agent-tools payload shape—the primary contract these tests are meant to protect.

  • web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts#L58-L60: use a minimal unknown-based workflow/config shape and narrow before reading parameters.agent.
  • web/packages/agenta-playground/tests/unit/agentRequest.test.ts#L362-L372: type the request parameters/tools shape and use a narrow type guard for tool names.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 729092dc-5e31-4cba-af34-95f5b67f75c9

📥 Commits

Reviewing files that changed from the base of the PR and between 53aaf6f and 6144bf9.

📒 Files selected for processing (36)
  • .agents/skills/agent-release-gate/resources/qa_probe.py
  • .agents/skills/agent-release-gate/resources/qa_product.py
  • api/oss/tests/pytest/unit/resources/test_workflow_catalog.py
  • docs/design/agent-workflows/documentation/agent-configuration.md
  • docs/design/agent-workflows/documentation/tools.md
  • docs/design/agent-workflows/interfaces/README.md
  • docs/design/agent-workflows/interfaces/in-service/harness-adapters.md
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
  • docs/design/agent-workflows/projects/default-agent-builtins/README.md
  • docs/design/agent-workflows/projects/default-agent-builtins/context.md
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md
  • docs/design/agent-workflows/projects/default-agent-builtins/open-questions.md
  • docs/design/agent-workflows/projects/default-agent-builtins/plan.md
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md
  • docs/design/agent-workflows/projects/default-agent-builtins/status.md
  • docs/design/agent-workflows/projects/default-agent-builtins/testing.md
  • sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py
  • sdks/python/agenta/sdk/agents/adapters/harnesses.py
  • sdks/python/agenta/sdk/agents/pi_builtins.py
  • sdks/python/agenta/sdk/utils/types.py
  • sdks/python/oss/tests/pytest/unit/agents/golden/pi_default_active_builtins.json
  • sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py
  • sdks/python/oss/tests/pytest/unit/agents/test_pi_builtins_parity.py
  • sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py
  • services/oss/src/agent/config.py
  • services/oss/tests/pytest/unit/agent/test_config_template_fallback.py
  • services/oss/tests/pytest/unit/agent/test_default_agent_template.py
  • services/runner/config/agent.json
  • services/runner/src/engines/sandbox_agent/run-plan.ts
  • services/runner/tests/unit/pi-default-builtins-parity.test.ts
  • services/runner/tests/unit/sandbox-agent-run-plan.test.ts
  • web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/PiSettingsControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/itemDescriptors.tsx
  • web/packages/agenta-entity-ui/tests/unit/itemDescriptors.test.ts
  • web/packages/agenta-playground/tests/unit/agentRequest.test.ts

Comment thread docs/design/agent-workflows/interfaces/README.md Outdated
Comment thread docs/design/agent-workflows/projects/default-agent-builtins/plan.md Outdated
Comment thread sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py Outdated
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-08-01T18:14:30.335Z

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
web/packages/agenta-playground/tests/unit/agentRequest.test.ts (1)

362-372: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid introducing any in this workspace-package test.

Use the existing request type or narrow unknown values before accessing parameters.agent.tools; the new as any and (tool: any) suppress schema regressions that this test should catch.

As per coding guidelines, workspace packages should avoid any.

Source: Coding guidelines

web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts (1)

58-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the test helper type-safe.

Record<string, any> and as any suppress TypeScript checks, while optional chaining can still return undefined. Define the small agent-config shape needed by these assertions and fail explicitly when the entity is missing.

Proposed fix
-function readAgentConfig(localId: string): Record<string, any> {
-    const entity = getDefaultStore().get(workflowLocalServerDataAtomFamily(localId)) as any
-    return entity?.data?.parameters?.agent
+type AgentConfig = {
+    harness?: {kind?: string}
+    llm?: {model?: string; provider?: string}
+    tools?: Array<{type: "builtin"; name: string}>
+}
+
+function readAgentConfig(localId: string): AgentConfig {
+    const entity = getDefaultStore().get(workflowLocalServerDataAtomFamily(localId)) as
+        | {data?: {parameters?: {agent?: AgentConfig}}}
+        | undefined
+    const agent = entity?.data?.parameters?.agent
+    if (!agent) throw new Error(`Agent workflow ${localId} was not created`)
+    return agent
}

As per coding guidelines, workspace package TypeScript code should avoid any.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a01f2878-bfdb-495e-a417-7f955d1cf5e8

📥 Commits

Reviewing files that changed from the base of the PR and between 6144bf9 and 4c6fd34.

📒 Files selected for processing (36)
  • .agents/skills/agent-release-gate/resources/qa_probe.py
  • .agents/skills/agent-release-gate/resources/qa_product.py
  • api/oss/tests/pytest/unit/resources/test_workflow_catalog.py
  • docs/design/agent-workflows/documentation/agent-configuration.md
  • docs/design/agent-workflows/documentation/tools.md
  • docs/design/agent-workflows/interfaces/README.md
  • docs/design/agent-workflows/interfaces/in-service/harness-adapters.md
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
  • docs/design/agent-workflows/projects/default-agent-builtins/README.md
  • docs/design/agent-workflows/projects/default-agent-builtins/context.md
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md
  • docs/design/agent-workflows/projects/default-agent-builtins/open-questions.md
  • docs/design/agent-workflows/projects/default-agent-builtins/plan.md
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md
  • docs/design/agent-workflows/projects/default-agent-builtins/status.md
  • docs/design/agent-workflows/projects/default-agent-builtins/testing.md
  • sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py
  • sdks/python/agenta/sdk/agents/adapters/harnesses.py
  • sdks/python/agenta/sdk/agents/pi_builtins.py
  • sdks/python/agenta/sdk/utils/types.py
  • sdks/python/oss/tests/pytest/unit/agents/golden/pi_default_active_builtins.json
  • sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py
  • sdks/python/oss/tests/pytest/unit/agents/test_pi_builtins_parity.py
  • sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py
  • services/oss/src/agent/config.py
  • services/oss/tests/pytest/unit/agent/test_config_template_fallback.py
  • services/oss/tests/pytest/unit/agent/test_default_agent_template.py
  • services/runner/config/agent.json
  • services/runner/src/engines/sandbox_agent/run-plan.ts
  • services/runner/tests/unit/pi-default-builtins-parity.test.ts
  • services/runner/tests/unit/sandbox-agent-run-plan.test.ts
  • web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/PiSettingsControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/itemDescriptors.tsx
  • web/packages/agenta-entity-ui/tests/unit/itemDescriptors.test.ts
  • web/packages/agenta-playground/tests/unit/agentRequest.test.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • docs/design/agent-workflows/interfaces/README.md
  • docs/design/agent-workflows/projects/default-agent-builtins/context.md
  • docs/design/agent-workflows/documentation/agent-configuration.md
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/PiSettingsControl.tsx
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
  • sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py

Comment thread .agents/skills/agent-release-gate/resources/qa_probe.py Outdated
Comment thread docs/design/agent-workflows/projects/default-agent-builtins/research.md Outdated
Comment thread docs/design/agent-workflows/projects/default-agent-builtins/status.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

♻️ Duplicate comments (2)
docs/design/agent-workflows/projects/default-agent-builtins/research.md (1)

62-65: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Limit allow_reads to granted tools.

The default grant contains read, not grep, find, or ls. State that the granted read-only built-in runs without asking. The current wording implies that all four tools are active.

docs/design/agent-workflows/projects/default-agent-builtins/testing.md (1)

70-70: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the shipped constant name in the test example.

PI_DEFAULT_BUILTIN_NAMES does not exist. Use PI_DEFAULT_ACTIVE_BUILTINS so the example matches the implementation.

🟡 Minor comments (13)
services/runner/src/engines/sandbox_agent/run-plan.ts-321-337 (1)

321-337: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require a terminal approval reply before accepting an empty request.

carriesApprovalReplyOnly(request) returns true when any message contains an approved tool_result. It does not confirm that the current continuation is that approval reply.

For example, a historical approval followed by a later unresolved assistant tool_call passes this condition. The request then bypasses the empty-prompt rejection. Restrict the helper to the terminal approval-resume envelope, and add a regression test for this sequence.

docs/design/agent-workflows/projects/default-agent-builtins/design.md-297-315 (1)

297-315: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the conflicting constant-location guidance.

Lines 297-301 place PI_DEFAULT_ACTIVE_BUILTINS in agenta_builtins.py. Lines 308-315 correctly reject that location. Document only the implemented location, sdks/python/agenta/sdk/agents/pi_builtins.py.

docs/design/agent-workflows/projects/default-agent-builtins/research.md-107-117 (1)

107-117: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Refresh the design workspace to match the implemented state.

Several sections still describe the pre-change empty-tools implementation or pending work. This can mislead future implementers and make release evidence appear incomplete.

  • docs/design/agent-workflows/projects/default-agent-builtins/research.md#L107-L117: document the four-tool default and current consumers.
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md#L199-L204: remove the claim that the service fallback still uses tools: [].
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md#L206-L210: update the build-an-agent skill example status.
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md#L332-L342: state that existing fixtures are unchanged and that the new parity fixture was added.
  • docs/design/agent-workflows/projects/default-agent-builtins/plan.md#L12-L24: replace the source-reading test plan with the shared golden-fixture approach.
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md#L139-L149: update the independent default copies.
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md#L158-L171: update the Claude warning description.
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md#L211-L251: update the test inventory and crossing-test coverage.
  • docs/design/agent-workflows/projects/default-agent-builtins/status.md#L3-L4: replace the uncommitted/no-PR state with the current PR state.
  • docs/design/agent-workflows/projects/default-agent-builtins/status.md#L78-L79: reconcile the manual-verification status with testing.md.
  • docs/design/agent-workflows/projects/default-agent-builtins/testing.md#L136-L155: mark catalog and frontend-factory coverage as implemented.
  • docs/design/agent-workflows/projects/default-agent-builtins/testing.md#L179-L185: mark the release-gate seed update as complete.
docs/design/agent-workflows/projects/agent-multi-modality/plan.md-48-71 (1)

48-71: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark Stage 0 as complete.

This section still says paste and drag-to-attach are ungated and lists their gating as optional work. protocols/stage-0.md and plans/stage-1-implementation.md say the guard landed in commit f2aa193cb2 and was verified.

Leaving this text unchanged can trigger duplicate implementation or an incorrect rollout decision. Update the Stage 0 section to describe the completed work and retain only any remaining documentation cleanup.

api/oss/src/core/sessions/attachments/service.py-200-202 (1)

200-202: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Report the actually missing attachment id.

reference_ready returns None when any requested id is not ready. The service then reports ordered_ids[0], which is normally a valid attachment. Clients and logs receive a wrong id for multi-attachment references. Return the resolved ids from the DAO, or re-query to compute the missing set before raising.

api/pyproject.toml-48-48 (1)

48-48: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Avoid unintentionally permitting Python-incompatible puremagic majors.

puremagic>=1.30 allows puremagic>=2, but the 2.x line requires Python >=3.12 while this project supports Python >=3.11. Add a cap that keeps the dependency constrained to compatible majors, such as puremagic>=1.30,<2.

api/oss/tests/pytest/unit/sessions/test_attachment_dao_integration.py-29-35 (1)

29-35: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Close an existing engine before resetting the module global.

The fixture sets engine_module._transactions_engine = None at setup. If another module already created an engine, that engine loses its last reference without close(), and its pooled connections stay open for the run. Close it if present.

🛡️ Proposed change
 async def _fresh_engine_per_test():
+    if engine_module._transactions_engine is not None:
+        await engine_module._transactions_engine.close()
     engine_module._transactions_engine = None
     yield
api/oss/src/apis/fastapi/sessions/router.py-1019-1039 (1)

1019-1039: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Run the permission check before request-size validation.

The route validates Content-Length and raises AttachmentLengthRequired or AttachmentTooLarge at Lines 1019-1036, but it checks Permission.EDIT_SESSIONS only at Line 1039. A caller without the session permission receives 411 or 413 and can infer limits.max_raw_bytes. Move _validate_session_id_http and _check above the size validation. The size validation still runs before request.form(), so the bounded-read guarantee stays intact.

🔒 Proposed reordering
     ) -> SessionAttachmentResponse:
+        _validate_session_id_http(session_id)
+        await self._check(request, Permission.EDIT_SESSIONS)
+
         content_length = request.headers.get("content-length")
         if content_length is None:
             raise AttachmentLengthRequired()
@@
         if request_size > max_request_bytes:
             raise AttachmentTooLarge(size=request_size, limit=max_request_bytes)
 
-        _validate_session_id_http(session_id)
-        await self._check(request, Permission.EDIT_SESSIONS)
-
         form = await request.form()
api/oss/tests/pytest/unit/mounts/test_protected_mount_policy.py-541-567 (1)

541-567: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The test pins behavior that contradicts a service comment.

Lines 550 and 557 assert that archive_session_mounts and unarchive_session_mounts exclude the protected mount. That is the actual behavior, because both methods read through mounts_dao.query_mounts, which now applies the SQL protected-mount exclusion.

The comment at api/oss/src/core/mounts/service.py lines 748-749 states that session lifecycle bypasses the filter "to retain protected mounts for teardown and archival". Only teardown bypasses it. delete_session_mounts uses delete_by_session_id and is unfiltered, which line 563 confirms. Archival is filtered. Correct the comment to say teardown only, so the next reader does not assume archived sessions also archive their attachment mount.

api/oss/src/core/mounts/service.py-596-647 (1)

596-647: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add the mounts_store None guard to the new attachment operations.

self.mounts_store is optional (line 409). _bucket() only validates self.bucket. If a deployment configures a bucket without a store, these three methods raise AttributeError on self.mounts_store.put_object. handle_mount_exceptions does not map AttributeError, so the caller receives 500 instead of the 503 that MountStorageUnavailable produces. sign_mount_credentials already performs this check at line 860.

🛡️ Proposed guard
     ) -> None:
         validate_file_path(path)
+        if self.mounts_store is None:
+            raise MountStorageUnavailable()
         mount = await self._resolve_mount(
             project_id=project_id,
             mount_id=mount_id,
             allow_protected=True,
         )
services/runner/src/engines/sandbox_agent/attachments.ts-412-449 (1)

412-449: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Align model modality tokens with the catalog field and gate missing capability flags.

The catalog schema and ModelCatalogEntry.modalities use "image" and "text", while modelModalityState also treats "audio", "document", and "documents" as supported. Add explicit validation/mapping for those catalog tokens, and treat malformed modelCapabilities.modelCapabilities.inputModalities as a clear mismatch instead of letting the gate degrade attachments to workspace_only.

Source: Linters/SAST tools

services/runner/src/sessions/attachments.ts-109-115 (1)

109-115: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Strip Content-Type parameters from mediaType.

content-type can carry parameters, for example image/png; charset=binary. The raw header value becomes the authoritative mediaType for the fetched attachment. Any downstream comparison against a MIME allowlist (native image delivery, capability gating) then fails for a value that is in fact supported. Parse the essence before the first ; and lowercase it.

🛠️ Proposed fix
-    const mediaType = response.headers.get("content-type")?.trim();
+    const mediaType = response.headers
+      .get("content-type")
+      ?.split(";")[0]
+      .trim()
+      .toLowerCase();
services/runner/src/engines/sandbox_agent/run-turn.ts-305-322 (1)

305-322: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Respect resolved model image support for legacy inline images.

attachmentCapabilityGate treats missing or mismatched inputModalities as workspace_only, but this loop always gates legacy inline images with { inputModalities: ["image"] }. If the run model only advertises text, inline images still arrive as native image blocks while an identical image sent with an attachment fails gating; use request.modelCapabilities here if resolved image support must apply to legacy inline images.

🧹 Nitpick comments (14)
api/oss/src/dbs/postgres/sessions/attachments/dao.py (1)

241-257: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Deduplicate ids inside reference_ready.

The readiness check compares row count with len(attachment_ids). A duplicate id in the input makes the counts differ and returns None, which callers translate into a not-found error. SessionAttachmentsService.reference_attachments currently deduplicates first, so behavior is correct today. Deduplicate here as well so the DAO contract does not depend on caller preprocessing.

♻️ Proposed hardening
     ) -> Optional[List[Attachment]]:
         if not attachment_ids:
             return []
 
+        unique_ids = list(dict.fromkeys(attachment_ids))
         async with self.engine.session() as session:
             result = await session.execute(
                 select(SessionAttachmentDBE)
                 .where(
                     SessionAttachmentDBE.project_id == project_id,
                     SessionAttachmentDBE.session_id == session_id,
-                    SessionAttachmentDBE.id.in_(attachment_ids),
+                    SessionAttachmentDBE.id.in_(unique_ids),
                     SessionAttachmentDBE.state == AttachmentState.READY.value,
                 )
                 .with_for_update()
             )
             attachment_dbes = list(result.scalars().all())
-            if len(attachment_dbes) != len(attachment_ids):
+            if len(attachment_dbes) != len(unique_ids):
                 return None
api/oss/src/core/sessions/attachments/interfaces.py (1)

119-134: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider moving original deletion out of the DAO contract.

reap_stale_pending and sweep_unreferenced_ready accept a delete_original callback. The DAO therefore performs object-store side effects while it deletes rows. An alternative is to return the stale rows from the DAO and let the service or sweep task delete the originals. That keeps the DAO restricted to persistence and makes the deletion order explicit and testable.

If you keep the callback, document the ordering guarantee (object delete before or after row delete) in the interface docstring, because it determines whether a failure leaves an orphaned object or an orphaned row.

api/oss/src/tasks/asyncio/sessions/attachment_sweep.py (1)

135-153: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Floor the sleep interval to avoid a hot loop on misconfiguration.

sweep_interval_seconds comes from configuration. If an operator sets it to 0, this loop repeatedly acquires the Redis lock and scans the database with no pause. lease_ttl_seconds already applies a floor at line 70. Apply an equivalent floor to the sleep.

♻️ Proposed floor for the sweep interval
 ) -> None:
+    interval_seconds = max(sweep_interval_seconds, 1)
     while True:
         try:
             await run_attachment_sweep(
@@
             )
         except asyncio.CancelledError:
             raise
         except Exception as error:
             log.error(
                 "attachment_sweep: error during sweep pass: %s",
                 error,
                 exc_info=True,
             )
-        await asyncio.sleep(sweep_interval_seconds)
+        await asyncio.sleep(interval_seconds)
api/oss/tests/pytest/unit/sessions/test_attachment_dao_integration.py (1)

208-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Bound the reference_ready await.

reference_ready runs while the sweep task waits on allow_object_delete. If a future change keeps the tombstone transaction open across delete_original, this call blocks on the row lock and the test hangs, because the event is set only after the call returns. Wrap the call in asyncio.wait_for, as the quota test at Lines 248-263 already does.

♻️ Proposed change
-    claimed = await dao.reference_ready(
-        project_id=attachment_scope["project_id"],
-        session_id=session_id,
-        attachment_ids=[ready.id],
-        referenced_at=datetime.now(timezone.utc),
-    )
+    claimed = await asyncio.wait_for(
+        dao.reference_ready(
+            project_id=attachment_scope["project_id"],
+            session_id=session_id,
+            attachment_ids=[ready.id],
+            referenced_at=datetime.now(timezone.utc),
+        ),
+        timeout=30,
+    )
api/oss/tests/pytest/unit/sessions/test_attachment_state_machine.py (1)

549-562: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the bounded read with an understated Content-Length.

The current tests cover the header-based rejection only. _read_bounded is the defense when a client sends a small Content-Length and a larger body. Add a case that sends content_length below the real body size and asserts a 413 response, so the byte-level bound stays covered.

api/oss/tests/pytest/unit/sessions/test_attachment_sweep.py (1)

141-159: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the existing pytest-asyncio test style for new session unit tests.

api/pytest.ini sets asyncio_mode = auto, and pytest-asyncio is the configured dependency; pytest-anyio is not listed. Prefer async def tests without @pytest.mark.anyio/anyio_backend unless anyio-specific backend support is required.

api/oss/src/apis/fastapi/sessions/models.py (1)

193-205: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Compute count instead of relying on manual assignment at each call site.

count defaults to 0 in both SessionAttachmentResponse and SessionAttachmentsResponse. Nothing in these models enforces that count matches the actual payload. If a future call site forgets to set count explicitly, the response silently reports 0 while attachment/attachments still carries data.

♻️ Proposed fix using computed values
+from pydantic import model_validator
+
 class SessionAttachmentResponse(BaseModel):
-    count: int = 0
+    count: Literal[1] = 1
     attachment: SessionAttachment


 class SessionAttachmentsResponse(BaseModel):
-    count: int = 0
     attachments: List[SessionAttachment] = Field(default_factory=list)
+
+    `@property`
+    def count(self) -> int:
+        return len(self.attachments)
hosting/docker-compose/oss/nginx/nginx.conf (1)

35-35: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider scoping client_max_body_size to the /api/ location.

This directive is set at server scope, so it also raises the body-size ceiling for /services/completion/ and /services/chat/, which have no relationship to attachments. Move the 32m limit into the location /api/ block to keep the increase scoped to the feature that needs it.

♻️ Proposed fix to scope the limit
-        # Client max body size
-        client_max_body_size 32m;
-
         # Proxy to FastAPI backend
         location /api/ {
             limit_req zone=api burst=20 nodelay;
+            client_max_body_size 32m;
 
             proxy_pass http://api:8000/;
api/oss/src/core/mounts/service.py (1)

482-501: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider constraining purpose to the known constant.

purpose is an open Optional[str] here. Any value disables the reserved-name guard on line 495. Today only get_or_create_attachment_mount passes it, so this is not reachable from clients. A Literal[ATTACHMENTS_MOUNT_PURPOSE] type (or an enum) would make the invariant explicit and prevent a future caller from minting an unclassified attachments mount.

api/oss/src/dbs/postgres/mounts/mappings.py (1)

42-42: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Optional: purpose is not backfilled on re-bind.

This mapping only supplies purpose in the insert(...).values() part. The on_conflict_do_update set_ in api/oss/src/dbs/postgres/mounts/dao.py (lines 85-93) does not include purpose. A pre-migration attachments row therefore keeps purpose = NULL across every re-bind, so it depends on the transitional slug fallback permanently.

Classification is still correct, because is_protected_mount and the DAO LIKE predicate both cover the legacy slug. If you want the durable column to converge, add "purpose" to the conflict set_, or backfill it in the migration.

api/oss/tests/pytest/unit/mounts/test_protected_mount_policy.py (1)

84-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The double duplicates the SQL filter, so the service filter is untested.

Line 110 applies is_protected_mount inside the DAO double. This mirrors the real SQL predicate, which is good for test_fetch_and_query_hide_protected_mounts. It also means test_fetch_and_query_hide_protected_mounts would still pass if the Python filter at api/oss/src/core/mounts/service.py line 749 were deleted.

The comment in the service calls that filter the defense against DAO drift, so consider one extra test that returns a protected mount from the double unfiltered and asserts query_mounts still hides it.

services/runner/src/engines/sandbox_agent/attachments.ts (2)

455-519: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

requireNative does not gate the adapter and model branches.

requireNative is read only inside the transport branch. If the transport advertises the kind but the adapter or the model modality blocks native delivery, the gate returns outcome: "workspace_only" even when the caller demanded native delivery. A future caller that sets requireNative: true would therefore degrade silently, which is the exact outcome the flag exists to prevent.

Related: "model_modality_unsupported" is declared at Line 37 but no branch returns it. Either produce it or remove it.

No production caller passes requireNative today, so this is a contract gap rather than a live defect. Consider adding gate tests for the adapter and model branches with requireNative: true in services/runner/tests/unit/attachment-capability-gate.test.ts.

♻️ Proposed fix to honor `requireNative` on every branch
 export function attachmentCapabilityGate(input: {
   acpAgent: string;
   provider?: string;
   capabilities: HarnessCapabilities;
   modelCapabilities?: AgentRunRequest["modelCapabilities"];
   mediaType: string;
   byteLength: number;
   requireNative?: boolean;
 }): AttachmentGate {
   const kind = attachmentKind(input.mediaType);
+  const degrade = (
+    reasonCode: AttachmentDeliveryReasonCode,
+    missing: string,
+  ): AttachmentGate =>
+    input.requireNative
+      ? { outcome: "failed", reasonCode: "contract_violation", kind, missing }
+      : { outcome: "workspace_only", reasonCode, kind };
+
   if (!transportSupports(kind, input.capabilities)) {
-    if (input.requireNative) {
-      return {
-        outcome: "failed",
-        reasonCode: "contract_violation",
-        kind,
-        missing: "transport capability",
-      };
-    }
-    return {
-      outcome: "workspace_only",
-      reasonCode: "transport_unsupported",
-      kind,
-    };
+    return degrade("transport_unsupported", "transport capability");
   }
 
   if (!adapterSupports(input.acpAgent, kind)) {
-    return {
-      outcome: "workspace_only",
-      reasonCode: "adapter_unsupported",
-      kind,
-    };
+    return degrade("adapter_unsupported", "adapter capability");
   }
 
   const modelState = modelModalityState(
     input.modelCapabilities?.inputModalities,
     kind,
   );
   if (modelState === "unknown") {
-    return {
-      outcome: "workspace_only",
-      reasonCode: "model_modality_unknown",
-      kind,
-    };
+    return degrade("model_modality_unknown", "declared model modality");
   }

501-516: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider a provider-independent inline ceiling.

The base64 ceiling applies only when the provider is Anthropic. For every other provider an image of any size is inlined into the prompt, so the runner base64-encodes the whole buffer and holds both copies in memory. A large upload then fails at the provider instead of degrading to workspace_only. Add an absolute maximum that applies to all providers, and keep the Anthropic value as the stricter case.

services/runner/tests/unit/session-pool.test.ts (1)

331-340: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert order sensitivity too.

The test name states "ordered user attachment ids", but the assertion only compares two different ids. Add a case with the same two ids in swapped order across two user messages. That case proves userAttachmentIds keeps positional identity.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 423b23a0-d9c4-43c0-b015-1e3ba85c5e27

📥 Commits

Reviewing files that changed from the base of the PR and between 4c6fd34 and bb7728c.

⛔ Files ignored due to path filters (1)
  • api/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (128)
  • .agents/skills/agent-release-gate/resources/qa_probe.py
  • .agents/skills/agent-release-gate/resources/qa_product.py
  • api/entrypoints/routers.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000020_add_session_attachments.py
  • api/oss/src/apis/fastapi/mounts/models.py
  • api/oss/src/apis/fastapi/mounts/router.py
  • api/oss/src/apis/fastapi/sessions/models.py
  • api/oss/src/apis/fastapi/sessions/router.py
  • api/oss/src/core/mounts/dtos.py
  • api/oss/src/core/mounts/service.py
  • api/oss/src/core/mounts/types.py
  • api/oss/src/core/sessions/attachments/__init__.py
  • api/oss/src/core/sessions/attachments/dtos.py
  • api/oss/src/core/sessions/attachments/interfaces.py
  • api/oss/src/core/sessions/attachments/media.py
  • api/oss/src/core/sessions/attachments/service.py
  • api/oss/src/core/sessions/attachments/types.py
  • api/oss/src/core/sessions/interactions/dtos.py
  • api/oss/src/dbs/postgres/mounts/dao.py
  • api/oss/src/dbs/postgres/mounts/dbas.py
  • api/oss/src/dbs/postgres/mounts/mappings.py
  • api/oss/src/dbs/postgres/sessions/attachments/__init__.py
  • api/oss/src/dbs/postgres/sessions/attachments/dao.py
  • api/oss/src/dbs/postgres/sessions/attachments/dbas.py
  • api/oss/src/dbs/postgres/sessions/attachments/dbes.py
  • api/oss/src/dbs/postgres/sessions/attachments/mappings.py
  • api/oss/src/tasks/asyncio/sessions/attachment_sweep.py
  • api/oss/src/utils/env.py
  • api/oss/tests/pytest/acceptance/mounts/test_attachments_mount_hidden.py
  • api/oss/tests/pytest/acceptance/sessions/test_session_attachment_teardown.py
  • api/oss/tests/pytest/acceptance/sessions/test_session_attachments.py
  • api/oss/tests/pytest/unit/mounts/test_agent_mounts.py
  • api/oss/tests/pytest/unit/mounts/test_protected_mount_policy.py
  • api/oss/tests/pytest/unit/resources/test_workflow_catalog.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_dao_integration.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_filename.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_media.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_quotas.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_state_machine.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_sweep.py
  • api/oss/tests/pytest/unit/sessions/test_interaction_request_tool_call_id.py
  • api/oss/tests/pytest/unit/sessions/test_records_truncation.py
  • api/pyproject.toml
  • docs/design/agent-workflows/documentation/agent-configuration.md
  • docs/design/agent-workflows/documentation/tools.md
  • docs/design/agent-workflows/interfaces/README.md
  • docs/design/agent-workflows/interfaces/in-service/harness-adapters.md
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
  • docs/design/agent-workflows/projects/agent-multi-modality/README.md
  • docs/design/agent-workflows/projects/agent-multi-modality/context.md
  • docs/design/agent-workflows/projects/agent-multi-modality/decisions.md
  • docs/design/agent-workflows/projects/agent-multi-modality/design.md
  • docs/design/agent-workflows/projects/agent-multi-modality/plan.md
  • docs/design/agent-workflows/projects/agent-multi-modality/plans/stage-1-implementation.md
  • docs/design/agent-workflows/projects/agent-multi-modality/protocols/stage-0.md
  • docs/design/agent-workflows/projects/agent-multi-modality/protocols/stage-1.md
  • docs/design/agent-workflows/projects/agent-multi-modality/research.md
  • docs/design/agent-workflows/projects/agent-multi-modality/scope.md
  • docs/design/agent-workflows/projects/agent-multi-modality/status.md
  • docs/design/agent-workflows/projects/default-agent-builtins/README.md
  • docs/design/agent-workflows/projects/default-agent-builtins/context.md
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md
  • docs/design/agent-workflows/projects/default-agent-builtins/open-questions.md
  • docs/design/agent-workflows/projects/default-agent-builtins/plan.md
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md
  • docs/design/agent-workflows/projects/default-agent-builtins/status.md
  • docs/design/agent-workflows/projects/default-agent-builtins/testing.md
  • hosting/docker-compose/oss/nginx/nginx.conf
  • sdks/python/agenta/sdk/agents/__init__.py
  • sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py
  • sdks/python/agenta/sdk/agents/adapters/harnesses.py
  • sdks/python/agenta/sdk/agents/errors.py
  • sdks/python/agenta/sdk/agents/handler.py
  • sdks/python/agenta/sdk/agents/pi_builtins.py
  • sdks/python/agenta/sdk/utils/types.py
  • sdks/python/oss/tests/pytest/unit/agents/golden/pi_default_active_builtins.json
  • sdks/python/oss/tests/pytest/unit/agents/test_agent_composition_seam.py
  • sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py
  • sdks/python/oss/tests/pytest/unit/agents/test_pi_builtins_parity.py
  • sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py
  • services/oss/src/agent/app.py
  • services/oss/src/agent/config.py
  • services/oss/tests/pytest/unit/agent/test_config_template_fallback.py
  • services/oss/tests/pytest/unit/agent/test_default_agent_template.py
  • services/oss/tests/pytest/unit/agent/test_select_backend.py
  • services/runner/config/agent.json
  • services/runner/src/engines/sandbox_agent/acp-interactions.ts
  • services/runner/src/engines/sandbox_agent/attachments.ts
  • services/runner/src/engines/sandbox_agent/capabilities.ts
  • services/runner/src/engines/sandbox_agent/engine.ts
  • services/runner/src/engines/sandbox_agent/reconstruct-history.ts
  • services/runner/src/engines/sandbox_agent/run-plan.ts
  • services/runner/src/engines/sandbox_agent/run-turn.ts
  • services/runner/src/engines/sandbox_agent/runtime-contracts.ts
  • services/runner/src/engines/sandbox_agent/session-identity.ts
  • services/runner/src/engines/sandbox_agent/transcript.ts
  • services/runner/src/protocol.ts
  • services/runner/src/server.ts
  • services/runner/src/sessions/attachments.ts
  • services/runner/src/sessions/interactions.ts
  • services/runner/src/sessions/reconstruct.ts
  • services/runner/tests/unit/attachment-capability-gate.test.ts
  • services/runner/tests/unit/attachment-client.test.ts
  • services/runner/tests/unit/attachment-delivery-events.test.ts
  • services/runner/tests/unit/attachment-materialize.test.ts
  • services/runner/tests/unit/attachment-path-safety.test.ts
  • services/runner/tests/unit/attachment-prompt-blocks.test.ts
  • services/runner/tests/unit/current-user-turn.test.ts
  • services/runner/tests/unit/pi-default-builtins-parity.test.ts
  • services/runner/tests/unit/reconstruct-resume-nonfatal.test.ts
  • services/runner/tests/unit/sandbox-agent-acp-interactions.test.ts
  • services/runner/tests/unit/sandbox-agent-capabilities.test.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts
  • services/runner/tests/unit/sandbox-agent-run-plan.test.ts
  • services/runner/tests/unit/server.test.ts
  • services/runner/tests/unit/session-keepalive-approval.test.ts
  • services/runner/tests/unit/session-persist.test.ts
  • services/runner/tests/unit/session-pool.test.ts
  • services/runner/tests/unit/session-reconstruct-history.test.ts
  • services/runner/tests/unit/session-reconstruct.test.ts
  • services/runner/tests/unit/transcript.test.ts
  • services/runner/tests/unit/wire-contract.test.ts
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/PiSettingsControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/itemDescriptors.tsx
  • web/packages/agenta-entity-ui/tests/unit/itemDescriptors.test.ts
  • web/packages/agenta-playground/tests/unit/agentRequest.test.ts
🚧 Files skipped from review as they are similar to previous changes (26)
  • docs/design/agent-workflows/interfaces/README.md
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/PiSettingsControl.tsx
  • services/runner/tests/unit/pi-default-builtins-parity.test.ts
  • .agents/skills/agent-release-gate/resources/qa_product.py
  • sdks/python/oss/tests/pytest/unit/agents/golden/pi_default_active_builtins.json
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/itemDescriptors.tsx
  • sdks/python/oss/tests/pytest/unit/agents/test_pi_builtins_parity.py
  • services/oss/tests/pytest/unit/agent/test_config_template_fallback.py
  • services/runner/config/agent.json
  • .agents/skills/agent-release-gate/resources/qa_probe.py
  • web/packages/agenta-playground/tests/unit/agentRequest.test.ts
  • sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py
  • api/oss/tests/pytest/unit/resources/test_workflow_catalog.py
  • docs/design/agent-workflows/projects/default-agent-builtins/context.md
  • web/packages/agenta-entity-ui/tests/unit/itemDescriptors.test.ts
  • sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py
  • sdks/python/agenta/sdk/agents/pi_builtins.py
  • docs/design/agent-workflows/documentation/agent-configuration.md
  • sdks/python/agenta/sdk/utils/types.py
  • sdks/python/agenta/sdk/agents/adapters/harnesses.py
  • services/oss/src/agent/config.py
  • web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts
  • docs/design/agent-workflows/projects/default-agent-builtins/README.md
  • sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py
  • services/oss/tests/pytest/unit/agent/test_default_agent_template.py
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 8

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

♻️ Duplicate comments (2)
docs/design/agent-workflows/projects/default-agent-builtins/research.md (1)

62-65: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Limit allow_reads to granted tools.

The default grant contains read, not grep, find, or ls. State that the granted read-only built-in runs without asking. The current wording implies that all four tools are active.

docs/design/agent-workflows/projects/default-agent-builtins/testing.md (1)

70-70: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the shipped constant name in the test example.

PI_DEFAULT_BUILTIN_NAMES does not exist. Use PI_DEFAULT_ACTIVE_BUILTINS so the example matches the implementation.

🟡 Minor comments (13)
services/runner/src/engines/sandbox_agent/run-plan.ts-321-337 (1)

321-337: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require a terminal approval reply before accepting an empty request.

carriesApprovalReplyOnly(request) returns true when any message contains an approved tool_result. It does not confirm that the current continuation is that approval reply.

For example, a historical approval followed by a later unresolved assistant tool_call passes this condition. The request then bypasses the empty-prompt rejection. Restrict the helper to the terminal approval-resume envelope, and add a regression test for this sequence.

docs/design/agent-workflows/projects/default-agent-builtins/design.md-297-315 (1)

297-315: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the conflicting constant-location guidance.

Lines 297-301 place PI_DEFAULT_ACTIVE_BUILTINS in agenta_builtins.py. Lines 308-315 correctly reject that location. Document only the implemented location, sdks/python/agenta/sdk/agents/pi_builtins.py.

docs/design/agent-workflows/projects/default-agent-builtins/research.md-107-117 (1)

107-117: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Refresh the design workspace to match the implemented state.

Several sections still describe the pre-change empty-tools implementation or pending work. This can mislead future implementers and make release evidence appear incomplete.

  • docs/design/agent-workflows/projects/default-agent-builtins/research.md#L107-L117: document the four-tool default and current consumers.
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md#L199-L204: remove the claim that the service fallback still uses tools: [].
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md#L206-L210: update the build-an-agent skill example status.
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md#L332-L342: state that existing fixtures are unchanged and that the new parity fixture was added.
  • docs/design/agent-workflows/projects/default-agent-builtins/plan.md#L12-L24: replace the source-reading test plan with the shared golden-fixture approach.
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md#L139-L149: update the independent default copies.
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md#L158-L171: update the Claude warning description.
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md#L211-L251: update the test inventory and crossing-test coverage.
  • docs/design/agent-workflows/projects/default-agent-builtins/status.md#L3-L4: replace the uncommitted/no-PR state with the current PR state.
  • docs/design/agent-workflows/projects/default-agent-builtins/status.md#L78-L79: reconcile the manual-verification status with testing.md.
  • docs/design/agent-workflows/projects/default-agent-builtins/testing.md#L136-L155: mark catalog and frontend-factory coverage as implemented.
  • docs/design/agent-workflows/projects/default-agent-builtins/testing.md#L179-L185: mark the release-gate seed update as complete.
docs/design/agent-workflows/projects/agent-multi-modality/plan.md-48-71 (1)

48-71: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Mark Stage 0 as complete.

This section still says paste and drag-to-attach are ungated and lists their gating as optional work. protocols/stage-0.md and plans/stage-1-implementation.md say the guard landed in commit f2aa193cb2 and was verified.

Leaving this text unchanged can trigger duplicate implementation or an incorrect rollout decision. Update the Stage 0 section to describe the completed work and retain only any remaining documentation cleanup.

api/oss/src/core/sessions/attachments/service.py-200-202 (1)

200-202: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Report the actually missing attachment id.

reference_ready returns None when any requested id is not ready. The service then reports ordered_ids[0], which is normally a valid attachment. Clients and logs receive a wrong id for multi-attachment references. Return the resolved ids from the DAO, or re-query to compute the missing set before raising.

api/pyproject.toml-48-48 (1)

48-48: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Avoid unintentionally permitting Python-incompatible puremagic majors.

puremagic>=1.30 allows puremagic>=2, but the 2.x line requires Python >=3.12 while this project supports Python >=3.11. Add a cap that keeps the dependency constrained to compatible majors, such as puremagic>=1.30,<2.

api/oss/tests/pytest/unit/sessions/test_attachment_dao_integration.py-29-35 (1)

29-35: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Close an existing engine before resetting the module global.

The fixture sets engine_module._transactions_engine = None at setup. If another module already created an engine, that engine loses its last reference without close(), and its pooled connections stay open for the run. Close it if present.

🛡️ Proposed change
 async def _fresh_engine_per_test():
+    if engine_module._transactions_engine is not None:
+        await engine_module._transactions_engine.close()
     engine_module._transactions_engine = None
     yield
api/oss/src/apis/fastapi/sessions/router.py-1019-1039 (1)

1019-1039: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Run the permission check before request-size validation.

The route validates Content-Length and raises AttachmentLengthRequired or AttachmentTooLarge at Lines 1019-1036, but it checks Permission.EDIT_SESSIONS only at Line 1039. A caller without the session permission receives 411 or 413 and can infer limits.max_raw_bytes. Move _validate_session_id_http and _check above the size validation. The size validation still runs before request.form(), so the bounded-read guarantee stays intact.

🔒 Proposed reordering
     ) -> SessionAttachmentResponse:
+        _validate_session_id_http(session_id)
+        await self._check(request, Permission.EDIT_SESSIONS)
+
         content_length = request.headers.get("content-length")
         if content_length is None:
             raise AttachmentLengthRequired()
@@
         if request_size > max_request_bytes:
             raise AttachmentTooLarge(size=request_size, limit=max_request_bytes)
 
-        _validate_session_id_http(session_id)
-        await self._check(request, Permission.EDIT_SESSIONS)
-
         form = await request.form()
api/oss/tests/pytest/unit/mounts/test_protected_mount_policy.py-541-567 (1)

541-567: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The test pins behavior that contradicts a service comment.

Lines 550 and 557 assert that archive_session_mounts and unarchive_session_mounts exclude the protected mount. That is the actual behavior, because both methods read through mounts_dao.query_mounts, which now applies the SQL protected-mount exclusion.

The comment at api/oss/src/core/mounts/service.py lines 748-749 states that session lifecycle bypasses the filter "to retain protected mounts for teardown and archival". Only teardown bypasses it. delete_session_mounts uses delete_by_session_id and is unfiltered, which line 563 confirms. Archival is filtered. Correct the comment to say teardown only, so the next reader does not assume archived sessions also archive their attachment mount.

api/oss/src/core/mounts/service.py-596-647 (1)

596-647: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add the mounts_store None guard to the new attachment operations.

self.mounts_store is optional (line 409). _bucket() only validates self.bucket. If a deployment configures a bucket without a store, these three methods raise AttributeError on self.mounts_store.put_object. handle_mount_exceptions does not map AttributeError, so the caller receives 500 instead of the 503 that MountStorageUnavailable produces. sign_mount_credentials already performs this check at line 860.

🛡️ Proposed guard
     ) -> None:
         validate_file_path(path)
+        if self.mounts_store is None:
+            raise MountStorageUnavailable()
         mount = await self._resolve_mount(
             project_id=project_id,
             mount_id=mount_id,
             allow_protected=True,
         )
services/runner/src/engines/sandbox_agent/attachments.ts-412-449 (1)

412-449: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Align model modality tokens with the catalog field and gate missing capability flags.

The catalog schema and ModelCatalogEntry.modalities use "image" and "text", while modelModalityState also treats "audio", "document", and "documents" as supported. Add explicit validation/mapping for those catalog tokens, and treat malformed modelCapabilities.modelCapabilities.inputModalities as a clear mismatch instead of letting the gate degrade attachments to workspace_only.

Source: Linters/SAST tools

services/runner/src/sessions/attachments.ts-109-115 (1)

109-115: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Strip Content-Type parameters from mediaType.

content-type can carry parameters, for example image/png; charset=binary. The raw header value becomes the authoritative mediaType for the fetched attachment. Any downstream comparison against a MIME allowlist (native image delivery, capability gating) then fails for a value that is in fact supported. Parse the essence before the first ; and lowercase it.

🛠️ Proposed fix
-    const mediaType = response.headers.get("content-type")?.trim();
+    const mediaType = response.headers
+      .get("content-type")
+      ?.split(";")[0]
+      .trim()
+      .toLowerCase();
services/runner/src/engines/sandbox_agent/run-turn.ts-305-322 (1)

305-322: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Respect resolved model image support for legacy inline images.

attachmentCapabilityGate treats missing or mismatched inputModalities as workspace_only, but this loop always gates legacy inline images with { inputModalities: ["image"] }. If the run model only advertises text, inline images still arrive as native image blocks while an identical image sent with an attachment fails gating; use request.modelCapabilities here if resolved image support must apply to legacy inline images.

🧹 Nitpick comments (14)
api/oss/src/dbs/postgres/sessions/attachments/dao.py (1)

241-257: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Deduplicate ids inside reference_ready.

The readiness check compares row count with len(attachment_ids). A duplicate id in the input makes the counts differ and returns None, which callers translate into a not-found error. SessionAttachmentsService.reference_attachments currently deduplicates first, so behavior is correct today. Deduplicate here as well so the DAO contract does not depend on caller preprocessing.

♻️ Proposed hardening
     ) -> Optional[List[Attachment]]:
         if not attachment_ids:
             return []
 
+        unique_ids = list(dict.fromkeys(attachment_ids))
         async with self.engine.session() as session:
             result = await session.execute(
                 select(SessionAttachmentDBE)
                 .where(
                     SessionAttachmentDBE.project_id == project_id,
                     SessionAttachmentDBE.session_id == session_id,
-                    SessionAttachmentDBE.id.in_(attachment_ids),
+                    SessionAttachmentDBE.id.in_(unique_ids),
                     SessionAttachmentDBE.state == AttachmentState.READY.value,
                 )
                 .with_for_update()
             )
             attachment_dbes = list(result.scalars().all())
-            if len(attachment_dbes) != len(attachment_ids):
+            if len(attachment_dbes) != len(unique_ids):
                 return None
api/oss/src/core/sessions/attachments/interfaces.py (1)

119-134: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider moving original deletion out of the DAO contract.

reap_stale_pending and sweep_unreferenced_ready accept a delete_original callback. The DAO therefore performs object-store side effects while it deletes rows. An alternative is to return the stale rows from the DAO and let the service or sweep task delete the originals. That keeps the DAO restricted to persistence and makes the deletion order explicit and testable.

If you keep the callback, document the ordering guarantee (object delete before or after row delete) in the interface docstring, because it determines whether a failure leaves an orphaned object or an orphaned row.

api/oss/src/tasks/asyncio/sessions/attachment_sweep.py (1)

135-153: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Floor the sleep interval to avoid a hot loop on misconfiguration.

sweep_interval_seconds comes from configuration. If an operator sets it to 0, this loop repeatedly acquires the Redis lock and scans the database with no pause. lease_ttl_seconds already applies a floor at line 70. Apply an equivalent floor to the sleep.

♻️ Proposed floor for the sweep interval
 ) -> None:
+    interval_seconds = max(sweep_interval_seconds, 1)
     while True:
         try:
             await run_attachment_sweep(
@@
             )
         except asyncio.CancelledError:
             raise
         except Exception as error:
             log.error(
                 "attachment_sweep: error during sweep pass: %s",
                 error,
                 exc_info=True,
             )
-        await asyncio.sleep(sweep_interval_seconds)
+        await asyncio.sleep(interval_seconds)
api/oss/tests/pytest/unit/sessions/test_attachment_dao_integration.py (1)

208-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Bound the reference_ready await.

reference_ready runs while the sweep task waits on allow_object_delete. If a future change keeps the tombstone transaction open across delete_original, this call blocks on the row lock and the test hangs, because the event is set only after the call returns. Wrap the call in asyncio.wait_for, as the quota test at Lines 248-263 already does.

♻️ Proposed change
-    claimed = await dao.reference_ready(
-        project_id=attachment_scope["project_id"],
-        session_id=session_id,
-        attachment_ids=[ready.id],
-        referenced_at=datetime.now(timezone.utc),
-    )
+    claimed = await asyncio.wait_for(
+        dao.reference_ready(
+            project_id=attachment_scope["project_id"],
+            session_id=session_id,
+            attachment_ids=[ready.id],
+            referenced_at=datetime.now(timezone.utc),
+        ),
+        timeout=30,
+    )
api/oss/tests/pytest/unit/sessions/test_attachment_state_machine.py (1)

549-562: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the bounded read with an understated Content-Length.

The current tests cover the header-based rejection only. _read_bounded is the defense when a client sends a small Content-Length and a larger body. Add a case that sends content_length below the real body size and asserts a 413 response, so the byte-level bound stays covered.

api/oss/tests/pytest/unit/sessions/test_attachment_sweep.py (1)

141-159: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the existing pytest-asyncio test style for new session unit tests.

api/pytest.ini sets asyncio_mode = auto, and pytest-asyncio is the configured dependency; pytest-anyio is not listed. Prefer async def tests without @pytest.mark.anyio/anyio_backend unless anyio-specific backend support is required.

api/oss/src/apis/fastapi/sessions/models.py (1)

193-205: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Compute count instead of relying on manual assignment at each call site.

count defaults to 0 in both SessionAttachmentResponse and SessionAttachmentsResponse. Nothing in these models enforces that count matches the actual payload. If a future call site forgets to set count explicitly, the response silently reports 0 while attachment/attachments still carries data.

♻️ Proposed fix using computed values
+from pydantic import model_validator
+
 class SessionAttachmentResponse(BaseModel):
-    count: int = 0
+    count: Literal[1] = 1
     attachment: SessionAttachment


 class SessionAttachmentsResponse(BaseModel):
-    count: int = 0
     attachments: List[SessionAttachment] = Field(default_factory=list)
+
+    `@property`
+    def count(self) -> int:
+        return len(self.attachments)
hosting/docker-compose/oss/nginx/nginx.conf (1)

35-35: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider scoping client_max_body_size to the /api/ location.

This directive is set at server scope, so it also raises the body-size ceiling for /services/completion/ and /services/chat/, which have no relationship to attachments. Move the 32m limit into the location /api/ block to keep the increase scoped to the feature that needs it.

♻️ Proposed fix to scope the limit
-        # Client max body size
-        client_max_body_size 32m;
-
         # Proxy to FastAPI backend
         location /api/ {
             limit_req zone=api burst=20 nodelay;
+            client_max_body_size 32m;
 
             proxy_pass http://api:8000/;
api/oss/src/core/mounts/service.py (1)

482-501: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider constraining purpose to the known constant.

purpose is an open Optional[str] here. Any value disables the reserved-name guard on line 495. Today only get_or_create_attachment_mount passes it, so this is not reachable from clients. A Literal[ATTACHMENTS_MOUNT_PURPOSE] type (or an enum) would make the invariant explicit and prevent a future caller from minting an unclassified attachments mount.

api/oss/src/dbs/postgres/mounts/mappings.py (1)

42-42: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Optional: purpose is not backfilled on re-bind.

This mapping only supplies purpose in the insert(...).values() part. The on_conflict_do_update set_ in api/oss/src/dbs/postgres/mounts/dao.py (lines 85-93) does not include purpose. A pre-migration attachments row therefore keeps purpose = NULL across every re-bind, so it depends on the transitional slug fallback permanently.

Classification is still correct, because is_protected_mount and the DAO LIKE predicate both cover the legacy slug. If you want the durable column to converge, add "purpose" to the conflict set_, or backfill it in the migration.

api/oss/tests/pytest/unit/mounts/test_protected_mount_policy.py (1)

84-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The double duplicates the SQL filter, so the service filter is untested.

Line 110 applies is_protected_mount inside the DAO double. This mirrors the real SQL predicate, which is good for test_fetch_and_query_hide_protected_mounts. It also means test_fetch_and_query_hide_protected_mounts would still pass if the Python filter at api/oss/src/core/mounts/service.py line 749 were deleted.

The comment in the service calls that filter the defense against DAO drift, so consider one extra test that returns a protected mount from the double unfiltered and asserts query_mounts still hides it.

services/runner/src/engines/sandbox_agent/attachments.ts (2)

455-519: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

requireNative does not gate the adapter and model branches.

requireNative is read only inside the transport branch. If the transport advertises the kind but the adapter or the model modality blocks native delivery, the gate returns outcome: "workspace_only" even when the caller demanded native delivery. A future caller that sets requireNative: true would therefore degrade silently, which is the exact outcome the flag exists to prevent.

Related: "model_modality_unsupported" is declared at Line 37 but no branch returns it. Either produce it or remove it.

No production caller passes requireNative today, so this is a contract gap rather than a live defect. Consider adding gate tests for the adapter and model branches with requireNative: true in services/runner/tests/unit/attachment-capability-gate.test.ts.

♻️ Proposed fix to honor `requireNative` on every branch
 export function attachmentCapabilityGate(input: {
   acpAgent: string;
   provider?: string;
   capabilities: HarnessCapabilities;
   modelCapabilities?: AgentRunRequest["modelCapabilities"];
   mediaType: string;
   byteLength: number;
   requireNative?: boolean;
 }): AttachmentGate {
   const kind = attachmentKind(input.mediaType);
+  const degrade = (
+    reasonCode: AttachmentDeliveryReasonCode,
+    missing: string,
+  ): AttachmentGate =>
+    input.requireNative
+      ? { outcome: "failed", reasonCode: "contract_violation", kind, missing }
+      : { outcome: "workspace_only", reasonCode, kind };
+
   if (!transportSupports(kind, input.capabilities)) {
-    if (input.requireNative) {
-      return {
-        outcome: "failed",
-        reasonCode: "contract_violation",
-        kind,
-        missing: "transport capability",
-      };
-    }
-    return {
-      outcome: "workspace_only",
-      reasonCode: "transport_unsupported",
-      kind,
-    };
+    return degrade("transport_unsupported", "transport capability");
   }
 
   if (!adapterSupports(input.acpAgent, kind)) {
-    return {
-      outcome: "workspace_only",
-      reasonCode: "adapter_unsupported",
-      kind,
-    };
+    return degrade("adapter_unsupported", "adapter capability");
   }
 
   const modelState = modelModalityState(
     input.modelCapabilities?.inputModalities,
     kind,
   );
   if (modelState === "unknown") {
-    return {
-      outcome: "workspace_only",
-      reasonCode: "model_modality_unknown",
-      kind,
-    };
+    return degrade("model_modality_unknown", "declared model modality");
   }

501-516: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider a provider-independent inline ceiling.

The base64 ceiling applies only when the provider is Anthropic. For every other provider an image of any size is inlined into the prompt, so the runner base64-encodes the whole buffer and holds both copies in memory. A large upload then fails at the provider instead of degrading to workspace_only. Add an absolute maximum that applies to all providers, and keep the Anthropic value as the stricter case.

services/runner/tests/unit/session-pool.test.ts (1)

331-340: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert order sensitivity too.

The test name states "ordered user attachment ids", but the assertion only compares two different ids. Add a case with the same two ids in swapped order across two user messages. That case proves userAttachmentIds keeps positional identity.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 423b23a0-d9c4-43c0-b015-1e3ba85c5e27

📥 Commits

Reviewing files that changed from the base of the PR and between 4c6fd34 and bb7728c.

⛔ Files ignored due to path filters (1)
  • api/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (128)
  • .agents/skills/agent-release-gate/resources/qa_probe.py
  • .agents/skills/agent-release-gate/resources/qa_product.py
  • api/entrypoints/routers.py
  • api/oss/databases/postgres/migrations/core_oss/versions/oss000000020_add_session_attachments.py
  • api/oss/src/apis/fastapi/mounts/models.py
  • api/oss/src/apis/fastapi/mounts/router.py
  • api/oss/src/apis/fastapi/sessions/models.py
  • api/oss/src/apis/fastapi/sessions/router.py
  • api/oss/src/core/mounts/dtos.py
  • api/oss/src/core/mounts/service.py
  • api/oss/src/core/mounts/types.py
  • api/oss/src/core/sessions/attachments/__init__.py
  • api/oss/src/core/sessions/attachments/dtos.py
  • api/oss/src/core/sessions/attachments/interfaces.py
  • api/oss/src/core/sessions/attachments/media.py
  • api/oss/src/core/sessions/attachments/service.py
  • api/oss/src/core/sessions/attachments/types.py
  • api/oss/src/core/sessions/interactions/dtos.py
  • api/oss/src/dbs/postgres/mounts/dao.py
  • api/oss/src/dbs/postgres/mounts/dbas.py
  • api/oss/src/dbs/postgres/mounts/mappings.py
  • api/oss/src/dbs/postgres/sessions/attachments/__init__.py
  • api/oss/src/dbs/postgres/sessions/attachments/dao.py
  • api/oss/src/dbs/postgres/sessions/attachments/dbas.py
  • api/oss/src/dbs/postgres/sessions/attachments/dbes.py
  • api/oss/src/dbs/postgres/sessions/attachments/mappings.py
  • api/oss/src/tasks/asyncio/sessions/attachment_sweep.py
  • api/oss/src/utils/env.py
  • api/oss/tests/pytest/acceptance/mounts/test_attachments_mount_hidden.py
  • api/oss/tests/pytest/acceptance/sessions/test_session_attachment_teardown.py
  • api/oss/tests/pytest/acceptance/sessions/test_session_attachments.py
  • api/oss/tests/pytest/unit/mounts/test_agent_mounts.py
  • api/oss/tests/pytest/unit/mounts/test_protected_mount_policy.py
  • api/oss/tests/pytest/unit/resources/test_workflow_catalog.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_dao_integration.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_filename.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_media.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_quotas.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_state_machine.py
  • api/oss/tests/pytest/unit/sessions/test_attachment_sweep.py
  • api/oss/tests/pytest/unit/sessions/test_interaction_request_tool_call_id.py
  • api/oss/tests/pytest/unit/sessions/test_records_truncation.py
  • api/pyproject.toml
  • docs/design/agent-workflows/documentation/agent-configuration.md
  • docs/design/agent-workflows/documentation/tools.md
  • docs/design/agent-workflows/interfaces/README.md
  • docs/design/agent-workflows/interfaces/in-service/harness-adapters.md
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
  • docs/design/agent-workflows/projects/agent-multi-modality/README.md
  • docs/design/agent-workflows/projects/agent-multi-modality/context.md
  • docs/design/agent-workflows/projects/agent-multi-modality/decisions.md
  • docs/design/agent-workflows/projects/agent-multi-modality/design.md
  • docs/design/agent-workflows/projects/agent-multi-modality/plan.md
  • docs/design/agent-workflows/projects/agent-multi-modality/plans/stage-1-implementation.md
  • docs/design/agent-workflows/projects/agent-multi-modality/protocols/stage-0.md
  • docs/design/agent-workflows/projects/agent-multi-modality/protocols/stage-1.md
  • docs/design/agent-workflows/projects/agent-multi-modality/research.md
  • docs/design/agent-workflows/projects/agent-multi-modality/scope.md
  • docs/design/agent-workflows/projects/agent-multi-modality/status.md
  • docs/design/agent-workflows/projects/default-agent-builtins/README.md
  • docs/design/agent-workflows/projects/default-agent-builtins/context.md
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md
  • docs/design/agent-workflows/projects/default-agent-builtins/open-questions.md
  • docs/design/agent-workflows/projects/default-agent-builtins/plan.md
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md
  • docs/design/agent-workflows/projects/default-agent-builtins/status.md
  • docs/design/agent-workflows/projects/default-agent-builtins/testing.md
  • hosting/docker-compose/oss/nginx/nginx.conf
  • sdks/python/agenta/sdk/agents/__init__.py
  • sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py
  • sdks/python/agenta/sdk/agents/adapters/harnesses.py
  • sdks/python/agenta/sdk/agents/errors.py
  • sdks/python/agenta/sdk/agents/handler.py
  • sdks/python/agenta/sdk/agents/pi_builtins.py
  • sdks/python/agenta/sdk/utils/types.py
  • sdks/python/oss/tests/pytest/unit/agents/golden/pi_default_active_builtins.json
  • sdks/python/oss/tests/pytest/unit/agents/test_agent_composition_seam.py
  • sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py
  • sdks/python/oss/tests/pytest/unit/agents/test_pi_builtins_parity.py
  • sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py
  • services/oss/src/agent/app.py
  • services/oss/src/agent/config.py
  • services/oss/tests/pytest/unit/agent/test_config_template_fallback.py
  • services/oss/tests/pytest/unit/agent/test_default_agent_template.py
  • services/oss/tests/pytest/unit/agent/test_select_backend.py
  • services/runner/config/agent.json
  • services/runner/src/engines/sandbox_agent/acp-interactions.ts
  • services/runner/src/engines/sandbox_agent/attachments.ts
  • services/runner/src/engines/sandbox_agent/capabilities.ts
  • services/runner/src/engines/sandbox_agent/engine.ts
  • services/runner/src/engines/sandbox_agent/reconstruct-history.ts
  • services/runner/src/engines/sandbox_agent/run-plan.ts
  • services/runner/src/engines/sandbox_agent/run-turn.ts
  • services/runner/src/engines/sandbox_agent/runtime-contracts.ts
  • services/runner/src/engines/sandbox_agent/session-identity.ts
  • services/runner/src/engines/sandbox_agent/transcript.ts
  • services/runner/src/protocol.ts
  • services/runner/src/server.ts
  • services/runner/src/sessions/attachments.ts
  • services/runner/src/sessions/interactions.ts
  • services/runner/src/sessions/reconstruct.ts
  • services/runner/tests/unit/attachment-capability-gate.test.ts
  • services/runner/tests/unit/attachment-client.test.ts
  • services/runner/tests/unit/attachment-delivery-events.test.ts
  • services/runner/tests/unit/attachment-materialize.test.ts
  • services/runner/tests/unit/attachment-path-safety.test.ts
  • services/runner/tests/unit/attachment-prompt-blocks.test.ts
  • services/runner/tests/unit/current-user-turn.test.ts
  • services/runner/tests/unit/pi-default-builtins-parity.test.ts
  • services/runner/tests/unit/reconstruct-resume-nonfatal.test.ts
  • services/runner/tests/unit/sandbox-agent-acp-interactions.test.ts
  • services/runner/tests/unit/sandbox-agent-capabilities.test.ts
  • services/runner/tests/unit/sandbox-agent-orchestration.test.ts
  • services/runner/tests/unit/sandbox-agent-run-plan.test.ts
  • services/runner/tests/unit/server.test.ts
  • services/runner/tests/unit/session-keepalive-approval.test.ts
  • services/runner/tests/unit/session-persist.test.ts
  • services/runner/tests/unit/session-pool.test.ts
  • services/runner/tests/unit/session-reconstruct-history.test.ts
  • services/runner/tests/unit/session-reconstruct.test.ts
  • services/runner/tests/unit/transcript.test.ts
  • services/runner/tests/unit/wire-contract.test.ts
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/PiSettingsControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/itemDescriptors.tsx
  • web/packages/agenta-entity-ui/tests/unit/itemDescriptors.test.ts
  • web/packages/agenta-playground/tests/unit/agentRequest.test.ts
🚧 Files skipped from review as they are similar to previous changes (26)
  • docs/design/agent-workflows/interfaces/README.md
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/PiSettingsControl.tsx
  • services/runner/tests/unit/pi-default-builtins-parity.test.ts
  • .agents/skills/agent-release-gate/resources/qa_product.py
  • sdks/python/oss/tests/pytest/unit/agents/golden/pi_default_active_builtins.json
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/itemDescriptors.tsx
  • sdks/python/oss/tests/pytest/unit/agents/test_pi_builtins_parity.py
  • services/oss/tests/pytest/unit/agent/test_config_template_fallback.py
  • services/runner/config/agent.json
  • .agents/skills/agent-release-gate/resources/qa_probe.py
  • web/packages/agenta-playground/tests/unit/agentRequest.test.ts
  • sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py
  • api/oss/tests/pytest/unit/resources/test_workflow_catalog.py
  • docs/design/agent-workflows/projects/default-agent-builtins/context.md
  • web/packages/agenta-entity-ui/tests/unit/itemDescriptors.test.ts
  • sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py
  • sdks/python/agenta/sdk/agents/pi_builtins.py
  • docs/design/agent-workflows/documentation/agent-configuration.md
  • sdks/python/agenta/sdk/utils/types.py
  • sdks/python/agenta/sdk/agents/adapters/harnesses.py
  • services/oss/src/agent/config.py
  • web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts
  • docs/design/agent-workflows/projects/default-agent-builtins/README.md
  • sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py
  • services/oss/tests/pytest/unit/agent/test_default_agent_template.py
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
🛑 Comments failed to post (8)
api/oss/src/core/sessions/attachments/media.py (1)

112-141: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect the session attachment download route response headers.
set -euo pipefail

fd -t f 'router.py' api/oss/src/apis/fastapi/sessions | while IFS= read -r file; do
  rg -n -C 12 'attachment' "$file"
done

# Look for content-type / disposition handling anywhere in the attachment paths.
rg -n -i 'content-disposition|content_type|media_type=' --glob 'api/oss/src/apis/fastapi/**/*.py'

Repository: Agenta-AI/agenta

Length of output: 14120


🏁 Script executed:

#!/bin/bash
# Inspect the response content-type and sanitize any active-media active-content handling.
set -euo pipefail

sed -n '950,1092p' api/oss/src/apis/fastapi/sessions/router.py

printf '\n--- classification constants and functions ---\n'
sed -n '1,190p' api/oss/src/core/sessions/attachments/media.py

printf '\n--- content model / fetch path ---\n'
rg -n -C 8 'AttachmentContent|fetch_attachment_content|media_type=content|media_type =' api/oss/src/core/sessions/attachments api/oss/src/apis/fastapi/sessions

Repository: Agenta-AI/agenta

Length of output: 20703


Sanitize active-media content types before download.

download_session_attachment_content returns uploads as the stored media_type. classify can expose text/html or image/svg+xml, and fallback can retain caller-declared text/* media types. For active-media types, either strip the upload from safe media types before classification or return application/octet-stream from this endpoint.

docs/design/agent-workflows/projects/agent-multi-modality/design.md (1)

388-393: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Define the text attachment path separately from native documents.

text/* and application/json are listed under document, but the same design says small text can be inlined as text. The Stage 2 plan treats documents as workspace-only until adapter support. If the runner derives kind = document from the stored type, text attachments can be incorrectly blocked or sent through embeddedContext.

Define a separate text path, or document an explicit exception for text and JSON. Apply the same rule in plan.md and plans/stage-1-implementation.md.

Also applies to: 729-742

docs/design/agent-workflows/projects/agent-multi-modality/plans/stage-1-implementation.md (1)

120-123: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align the sweep contract with the implemented phased deletion.

These sections say the sweep holds row locks while deleting objects and re-checks state inside the deleting transaction. protocols/stage-1.md records a different implementation: tombstone rows, commit, delete objects outside the transaction, then remove rows.

These algorithms have different lock duration and failure semantics. Use the phased tombstone algorithm as the single contract, or update the protocol and tests to the lock-held algorithm.

Also applies to: 515-531

docs/design/agent-workflows/projects/agent-multi-modality/protocols/stage-1.md (1)

220-239: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the future-dated QA claim.

The review date is July 31, 2026, but this section reports live QA on August 1, 2026 and says the results were re-verified before the PR left draft.

Change the date to the actual execution date or mark the QA as pending. Release evidence must not use a future date.

docs/design/agent-workflows/projects/agent-multi-modality/research.md (1)

648-653: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Correct the replay claim.

D12 states that cold replay is mention-first. It restores the working copy and textual mention; it does not resend inline ACP content. Limit this statement to the running turn.

Proposed wording
-  and survives a replay.
+  and is deterministic for the running turn; cold replay restores a working copy and textual mention.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

The conclusion the design draws: use both, for different purposes. Inline delivery (Mechanism A) is
the only path that guarantees perception: it happens deterministically at the prompt-build seam the
runner owns, works the same on every harness that advertises the capability, and is deterministic for
the running turn; cold replay restores a working copy and textual mention. The disk copy (Mechanism B)
serves tool use, and on some harnesses it adds a second, agent-driven route to perception, which is a
useful fallback rather than a substitute. This is not redundant. The two mechanisms serve the two
separate outcomes from [context.md](context.md): perception and tool
docs/design/agent-workflows/projects/agent-multi-modality/status.md (1)

41-43: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align the stage tracker with the first-release contract.

scope.md says the first release includes browser dictation and that attachment attempts must not silently do nothing. This table places dictation in Stage 2 and makes Stage 0 optional, although Stage 0 contains the paste and drag gate. Move these requirements into required Stage 1, or revise the scope before implementation starts.

services/runner/src/server.ts (1)

1136-1148: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Handle a failed attachment claim.

claimAttachments returns false when the reference call fails. The result is discarded here, so the turn continues with attachments that the API still treats as unreferenced. The attachment sweep can then delete those rows and blobs while the persisted user record still points at them, which breaks both this turn's materialization and later history replay.

The claim also runs after the user record is persisted. If the claim fails, the durable record already asserts a reference that the API never recorded.

Claim first, then persist, and act on the failure (fail the turn or record the degraded state explicitly).

🛠️ Proposed ordering and failure handling
     if (tailIsFreshUserMessage(request)) {
+      if (turn.attachments.length > 0) {
+        const claimed = await claimAttachments(
+          sessionId,
+          turn.attachments.map((attachment) => attachment.attachmentId),
+          watchdog.credential,
+        );
+        if (!claimed) {
+          const message = "Could not reference this turn's attachments.";
+          persist({ type: "error", message }, "agent");
+          await flush();
+          await watchdog.release().catch(() => {});
+          writeRecord({
+            kind: "result",
+            result: { ok: false, error: message, events: [] },
+          });
+          res.end();
+          return;
+        }
+      }
       persist(
         { type: "message", text: turn.text, attachments: turn.attachments },
         "user",
       );
-      if (turn.attachments.length > 0) {
-        await claimAttachments(
-          sessionId,
-          turn.attachments.map((attachment) => attachment.attachmentId),
-          watchdog.credential,
-        );
-      }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if (tailIsFreshUserMessage(request)) {
      if (turn.attachments.length > 0) {
        const claimed = await claimAttachments(
          sessionId,
          turn.attachments.map((attachment) => attachment.attachmentId),
          watchdog.credential,
        );
        if (!claimed) {
          const message = "Could not reference this turn's attachments.";
          persist({ type: "error", message }, "agent");
          await flush();
          await watchdog.release().catch(() => {});
          writeRecord({
            kind: "result",
            result: { ok: false, error: message, events: [] },
          });
          res.end();
          return;
        }
      }
      persist(
        { type: "message", text: turn.text, attachments: turn.attachments },
        "user",
      );
    }
web/oss/src/components/AgentChatSlice/AgentConversation.tsx (1)

1778-1779: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Enforce uploadsEnabled at the shared attachment boundaries.

attachmentsBlocked() protects paste and drag-and-drop, but addFiles() and handleSubmit() remain ungated. ComposerAttachments receives onAdd={addFiles} unconditionally, and handleSubmit() still converts staged files into fileParts when uploadsEnabled is false.

Apply the flag at the shared mutation and submission boundaries. Keep voice audio as an explicit separate path if NEXT_PUBLIC_AGENT_VOICE_INPUT is intentionally independent. Otherwise, the “every attach path” guarantee is not enforced.

Also applies to: 1815-1815

@mmabrouk

mmabrouk commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
docs/design/agent-workflows/projects/default-agent-builtins/testing.md (1)

66-69: 🎯 Functional Correctness | 🟡 Minor

State that this test verifies grants, not execution permission.

The test proves that the default carries read, bash, edit, and write grants. Under allow_reads, bash, edit, and write still require approval. Replace “must be able to” with wording such as “must carry grants for” to avoid implying unattended execution.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c628177-556c-49bb-bc3d-089c99368c44

📥 Commits

Reviewing files that changed from the base of the PR and between bb7728c and 200c360.

📒 Files selected for processing (36)
  • .agents/skills/agent-release-gate/resources/qa_probe.py
  • .agents/skills/agent-release-gate/resources/qa_product.py
  • api/oss/tests/pytest/unit/resources/test_workflow_catalog.py
  • docs/design/agent-workflows/documentation/agent-configuration.md
  • docs/design/agent-workflows/documentation/tools.md
  • docs/design/agent-workflows/interfaces/README.md
  • docs/design/agent-workflows/interfaces/in-service/harness-adapters.md
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
  • docs/design/agent-workflows/projects/default-agent-builtins/README.md
  • docs/design/agent-workflows/projects/default-agent-builtins/context.md
  • docs/design/agent-workflows/projects/default-agent-builtins/design.md
  • docs/design/agent-workflows/projects/default-agent-builtins/open-questions.md
  • docs/design/agent-workflows/projects/default-agent-builtins/plan.md
  • docs/design/agent-workflows/projects/default-agent-builtins/research.md
  • docs/design/agent-workflows/projects/default-agent-builtins/status.md
  • docs/design/agent-workflows/projects/default-agent-builtins/testing.md
  • sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py
  • sdks/python/agenta/sdk/agents/adapters/harnesses.py
  • sdks/python/agenta/sdk/agents/pi_builtins.py
  • sdks/python/agenta/sdk/utils/types.py
  • sdks/python/oss/tests/pytest/unit/agents/golden/pi_default_active_builtins.json
  • sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py
  • sdks/python/oss/tests/pytest/unit/agents/test_pi_builtins_parity.py
  • sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py
  • services/oss/src/agent/config.py
  • services/oss/tests/pytest/unit/agent/test_config_template_fallback.py
  • services/oss/tests/pytest/unit/agent/test_default_agent_template.py
  • services/runner/config/agent.json
  • services/runner/src/engines/sandbox_agent/run-plan.ts
  • services/runner/tests/unit/pi-default-builtins-parity.test.ts
  • services/runner/tests/unit/sandbox-agent-run-plan.test.ts
  • web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/PiSettingsControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/itemDescriptors.tsx
  • web/packages/agenta-entity-ui/tests/unit/itemDescriptors.test.ts
  • web/packages/agenta-playground/tests/unit/agentRequest.test.ts
🚧 Files skipped from review as they are similar to previous changes (24)
  • services/runner/tests/unit/pi-default-builtins-parity.test.ts
  • docs/design/agent-workflows/documentation/agent-configuration.md
  • services/oss/tests/pytest/unit/agent/test_default_agent_template.py
  • api/oss/tests/pytest/unit/resources/test_workflow_catalog.py
  • .agents/skills/agent-release-gate/resources/qa_product.py
  • services/runner/config/agent.json
  • sdks/python/oss/tests/pytest/unit/agents/test_wire_contract.py
  • sdks/python/oss/tests/pytest/unit/agents/test_pi_builtins_parity.py
  • web/packages/agenta-entity-ui/tests/unit/itemDescriptors.test.ts
  • sdks/python/agenta/sdk/agents/pi_builtins.py
  • docs/design/agent-workflows/interfaces/README.md
  • services/oss/src/agent/config.py
  • sdks/python/agenta/sdk/utils/types.py
  • sdks/python/oss/tests/pytest/unit/agents/golden/pi_default_active_builtins.json
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/itemDescriptors.tsx
  • docs/design/agent-workflows/interfaces/public-edge/agent-config-schema.md
  • sdks/python/agenta/sdk/agents/adapters/harnesses.py
  • docs/design/agent-workflows/projects/default-agent-builtins/context.md
  • sdks/python/oss/tests/pytest/unit/agents/test_harness_adapters.py
  • web/packages/agenta-playground/tests/unit/agentRequest.test.ts
  • sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py
  • web/packages/agenta-entities/tests/unit/create-ephemeral-app-from-template.test.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/PiSettingsControl.tsx
  • docs/design/agent-workflows/projects/default-agent-builtins/README.md

Comment on lines +196 to +210
Two copies of the default do not go through the builder and are worth naming so a reader does not
assume they moved.

`services/oss/src/agent/config.py:106` supplies `tools: []` as the request-time fallback for a
request that carries no agent template at all (threaded through
`services/oss/src/agent/app.py:58` into `AgentTemplate.from_params`). This path is reached only by
a caller that posts an agent invocation with no `parameters.agent`, which the platform does not
do. Leave it alone in this change. Aligning it is a separate cleanup with its own risk, and it is
listed in [open-questions.md](open-questions.md).

`sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py:126` holds a documentation copy of the
config shape inside the build-an-agent skill, and its example shows `"tools": []`. That example
teaches the builder agent what a config looks like, so it should show the new default. It is a
text change with a drift test already in place
(`sdks/python/oss/tests/pytest/unit/agents/test_agenta_builtins_reference_files.py`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Refresh the consumer inventory after the implementation.

This section still says that services/oss/src/agent/config.py and sdks/python/agenta/sdk/agents/adapters/agenta_builtins.py use tools: []. The current change updates the service fallback and the build-agent fixture to the four Pi built-ins. Mark these entries as implemented and describe their current values.

Comment on lines +331 to +341
## No pinned wire contract moves

The `/run` request field stays `tools?: string[]`
(`services/runner/src/protocol.ts:469`). No field is added, removed, renamed, or retyped.

The shared golden fixtures under `sdks/python/oss/tests/pytest/unit/agents/golden/` do not change.
`test_wire_contract.py` builds its Pi payload from a hand-written
`PiAgentTemplate(builtin_tools=["read", "write"])` at `:125` and never touches
`build_agent_v0_default` or `AgentTemplate.from_params`, so the golden is unaffected.
`services/runner/tests/unit/wire-contract.test.ts` reads the same files and asserts on the parsed
request, not on a run plan built from it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Qualify the golden-fixture statement.

The PR adds sdks/python/oss/tests/pytest/unit/agents/golden/pi_default_active_builtins.json. Replace “The shared golden fixtures ... do not change” with wording that says the existing run-request fixtures remain unchanged and the new parity fixture is added.

Comment on lines +84 to +92
Every agent saved since the empty default shipped carries `tools: []` and keeps failing outside the
playground until its author edits it. Repairing them means reinterpreting a stored value, and there
is no signal that separates "the default put an empty list here" from "I deselected everything",
because they are the same value. The full reasoning is in
[design.md](design.md#repair-agents-already-saved).

Revisit if the count of affected agents turns out to be large. The narrow version, backfilling only
Pi agents whose tools list is empty and whose revision predates the fix, is implementable as its
own change.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Limit the migration statement to default-derived Pi agents.

“Every agent saved since the empty default shipped” also includes agents with explicit tool lists or other harnesses. The objective states that existing configurations retain their values. Change this to “agents created from the empty Pi default” so the repair scope remains precise.

Comment on lines +137 to +144
## Blocking nothing, waiting on nothing

The open questions in [open-questions.md](open-questions.md) did not block the work; the first of
them determines whether follow-up work is needed for the scheduled-run half of
[#5562](https://github.com/Agenta-AI/agenta/issues/5562).

When the issue is closed, say that this fixes newly created agents only. It does not repair agents
already saved, and it does not make an unattended write-capable run complete on its own.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Rename this section because pre-ship conditions remain open.

Line 79-80 and Line 122-125 state that manual verification and the local-sandbox deployment decision remain open. The heading “Blocking nothing, waiting on nothing” contradicts those statements and can make the change appear ready for deployment before the host-file access risk is addressed. Rename the heading to distinguish completed implementation work from remaining release checks.

Suggested wording
-## Blocking nothing, waiting on nothing
+## No implementation blockers remain
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Blocking nothing, waiting on nothing
The open questions in [open-questions.md](open-questions.md) did not block the work; the first of
them determines whether follow-up work is needed for the scheduled-run half of
[#5562](https://github.com/Agenta-AI/agenta/issues/5562).
When the issue is closed, say that this fixes newly created agents only. It does not repair agents
already saved, and it does not make an unattended write-capable run complete on its own.
## No implementation blockers remain
The open questions in [open-questions.md](open-questions.md) did not block the work; the first of
them determines whether follow-up work is needed for the scheduled-run half of
[`#5562`](https://github.com/Agenta-AI/agenta/issues/5562).
When the issue is closed, say that this fixes newly created agents only. It does not repair agents
already saved, and it does not make an unattended write-capable run complete on its own.

@mmabrouk
mmabrouk force-pushed the fix/pi-default-builtins branch from 200c360 to 4708ca5 Compare August 1, 2026 16:35
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Aug 1, 2026
@mmabrouk
mmabrouk changed the base branch from fix/sandbox-not-allowed-slug to wp2-runner-delivery August 1, 2026 16:37
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Aug 1, 2026
A saved agent shipped `tools: []`, which the runner reads as "grant Pi no
built-in tools", so the agent had no read, bash, edit or write anywhere except
the playground. The playground hid it: its build kit overlay adds read and bash
back on every run, and nothing else does.

The default template now ships Pi's own four defaults as typed builtin entries.
The runner's grant semantics are untouched, and the permission mode still gates
bash, edit and write behind an approval.

Also: the Claude harness only warns about built-ins that differ from that exact
set, the built-in picker no longer claims an empty selection leaves Pi's
defaults on, and a built-in row shows its own name.

Closes #5590
@mmabrouk
mmabrouk force-pushed the fix/pi-default-builtins branch from 4708ca5 to 356aa03 Compare August 1, 2026 17:58
@mmabrouk
mmabrouk changed the base branch from wp2-runner-delivery to release/v0.107.0 August 1, 2026 18:13
@mmabrouk
mmabrouk merged commit 752961e into release/v0.107.0 Aug 1, 2026
40 of 41 checks passed
@mmabrouk
mmabrouk deleted the fix/pi-default-builtins branch August 1, 2026 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(bug) Pi agents run with no read, bash, edit or write tools outside the playground

1 participant