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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions api/oss/src/apis/fastapi/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,24 @@ class SimpleApplicationQueryRequest(BaseModel):
)


class PlaygroundBuildKitContext(BaseModel):
"""Read-only playground build-kit context for one inspect/fetch response."""

agent_template_overlay: Optional[dict] = Field(
default=None,
description="Partial `parameters.agent` overlay applied by the playground only.",
)
Comment on lines +605 to +608

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Model agent_template_overlay explicitly instead of using dict.

This is now a backend/frontend contract, but Optional[dict] leaves the OpenAPI schema and runtime validation opaque. Please promote the overlay shape into concrete Pydantic models (or typed submodels for tools, skills, and sandbox) so downstream clients get a stable contract. As per coding guidelines, "Define explicit request and response models in models.py."

Source: Coding guidelines



class SimpleApplicationAdditionalContext(BaseModel):
"""Platform-supplied read-only context for a simple-application response."""

playground_build_kit: Optional[PlaygroundBuildKitContext] = Field(
default=None,
description="Playground-only build kit data that is never persisted on the app.",
)


class SimpleApplicationResponse(BaseModel):
"""Simple-application single-row response envelope."""

Expand All @@ -613,6 +631,10 @@ class SimpleApplicationResponse(BaseModel):
"revision's `data` merged. `data.url` is the invocation URL."
),
)
additional_context: Optional[SimpleApplicationAdditionalContext] = Field(
default=None,
description="Read-only platform context derived for this response.",
)


class SimpleApplicationsResponse(BaseModel):
Expand Down
47 changes: 47 additions & 0 deletions api/oss/src/apis/fastapi/applications/overlay.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Read-only overlays attached to application inspect/fetch responses."""

from typing import Any, Dict, List

from agenta.sdk.agents.adapters.agenta_builtins import GETTING_STARTED_WITH_AGENTA_SLUG
from agenta.sdk.agents.platform.op_catalog import PLATFORM_OPS

from oss.src.core.workflows.static_catalog import (
STATIC_SLUG_PREFIX,
StaticWorkflowCatalog,
_STATIC_WORKFLOWS,
)


def _workflow_embed(slug: str) -> Dict[str, Any]:
return {"@ag.embed": {"@ag.references": {"workflow": {"slug": slug}}}}


def _reserved_static_tool_slugs() -> List[str]:
catalog = StaticWorkflowCatalog()
slugs: List[str] = []
for slug in _STATIC_WORKFLOWS:
if not slug.startswith(STATIC_SLUG_PREFIX):
continue
revision = catalog.retrieve_revision(slug=slug)
if revision and revision.flags and revision.flags.is_skill:
continue
slugs.append(slug)
Comment on lines +25 to +28

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 | 🟠 Major | ⚡ Quick win

Tighten the reserved-workflow filter.

This currently appends reserved slugs even when retrieve_revision() returns None or revision.flags is missing. The expected overlay only includes confirmed non-skill static workflows, so this can leak invalid tool embeds into the playground.

Suggested fix
         revision = catalog.retrieve_revision(slug=slug)
-        if revision and revision.flags and revision.flags.is_skill:
+        if not revision or not revision.flags or revision.flags.is_skill:
             continue
         slugs.append(slug)
📝 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
revision = catalog.retrieve_revision(slug=slug)
if revision and revision.flags and revision.flags.is_skill:
continue
slugs.append(slug)
revision = catalog.retrieve_revision(slug=slug)
if not revision or not revision.flags or revision.flags.is_skill:
continue
slugs.append(slug)

return slugs


def build_agent_template_overlay() -> Dict[str, Any]:
"""Build the playground-only agent-template overlay from platform-owned sources."""

return {
"tools": [
*[{"type": "platform", "op": op_name} for op_name in PLATFORM_OPS],
*[_workflow_embed(slug) for slug in _reserved_static_tool_slugs()],
],
"skills": [_workflow_embed(GETTING_STARTED_WITH_AGENTA_SLUG)],
"sandbox": {
"permissions": {
"write_files": "allow",
"execute_code": "allow",
}
},
}
10 changes: 10 additions & 0 deletions api/oss/src/apis/fastapi/applications/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,12 @@
SimpleApplicationCreateRequest,
SimpleApplicationEditRequest,
SimpleApplicationQueryRequest,
SimpleApplicationAdditionalContext,
SimpleApplicationResponse,
SimpleApplicationsResponse,
PlaygroundBuildKitContext,
)
from oss.src.apis.fastapi.applications.overlay import build_agent_template_overlay
from oss.src.apis.fastapi.applications.utils import (
parse_application_variant_query_request_from_params,
parse_application_variant_query_request_from_body,
Expand Down Expand Up @@ -1905,6 +1908,13 @@ async def fetch_simple_application(
simple_application_response = SimpleApplicationResponse(
count=1 if simple_application else 0,
application=simple_application,
additional_context=SimpleApplicationAdditionalContext(
playground_build_kit=PlaygroundBuildKitContext(
agent_template_overlay=build_agent_template_overlay(),
),
)
if simple_application
else None,
Comment on lines +1911 to +1917

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Don’t let build-kit synthesis blank the whole fetch response.

fetch_simple_application() is wrapped in @suppress_exceptions(default=SimpleApplicationResponse()). If build_agent_template_overlay() raises, this path now returns an empty 200 response instead of the fetched application. Build the overlay behind a local try/except and fall back to additional_context=None so the inspect path still works. As per path instructions, use @suppress_exceptions(...) only for controlled defaults.

Source: Path instructions

)

return simple_application_response
Expand Down
116 changes: 116 additions & 0 deletions api/oss/tests/pytest/unit/applications/test_build_kit_overlay.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from types import SimpleNamespace
from unittest.mock import AsyncMock
from uuid import uuid4

import pytest

from agenta.sdk.agents.adapters.agenta_builtins import GETTING_STARTED_WITH_AGENTA_SLUG
from agenta.sdk.agents.platform.op_catalog import PLATFORM_OPS

from oss.src.apis.fastapi.applications import router as applications_router_module
from oss.src.apis.fastapi.applications.overlay import build_agent_template_overlay
from oss.src.apis.fastapi.applications.router import SimpleApplicationsRouter
from oss.src.core.applications.dtos import SimpleApplication
from oss.src.core.workflows.static_catalog import (
STATIC_SLUG_PREFIX,
StaticWorkflowCatalog,
_STATIC_WORKFLOWS,
)


def _embed_slug(entry: dict) -> str | None:
refs = entry.get("@ag.embed", {}).get("@ag.references", {})
workflow = refs.get("workflow") or refs.get("workflow_revision") or {}
return workflow.get("slug")


def test_agent_template_overlay_contains_platform_ops_authoring_skill_and_permissions():
overlay = build_agent_template_overlay()

platform_tools = [
tool
for tool in overlay["tools"]
if isinstance(tool, dict) and tool.get("type") == "platform"
]
assert platform_tools == [
{"type": "platform", "op": op_name} for op_name in PLATFORM_OPS
]

assert overlay["skills"] == [
{
"@ag.embed": {
"@ag.references": {
"workflow": {"slug": GETTING_STARTED_WITH_AGENTA_SLUG}
}
}
}
]
assert overlay["sandbox"] == {
"permissions": {"write_files": "allow", "execute_code": "allow"}
}


def test_agent_template_overlay_includes_reserved_static_workflow_tool_embeds():
overlay = build_agent_template_overlay()
tool_embed_slugs = {
_embed_slug(tool)
for tool in overlay["tools"]
if isinstance(tool, dict) and "@ag.embed" in tool
}
catalog = StaticWorkflowCatalog()

expected_slugs = set()
for slug in _STATIC_WORKFLOWS:
revision = catalog.retrieve_revision(slug=slug)
if (
slug.startswith(STATIC_SLUG_PREFIX)
and revision
and revision.flags
and not revision.flags.is_skill
):
expected_slugs.add(slug)

assert tool_embed_slugs == expected_slugs


@pytest.mark.asyncio
async def test_fetch_simple_application_includes_build_kit_context(monkeypatch):
project_id = uuid4()
user_id = uuid4()
application_id = uuid4()

class DummySimpleApplicationsService:
applications_service = object()

async def fetch(self, **kwargs):
assert kwargs["project_id"] == project_id
assert kwargs["application_id"] == application_id
return SimpleApplication(id=application_id, slug="agent")

monkeypatch.setattr(
applications_router_module,
"check_action_access",
AsyncMock(return_value=True),
raising=False,
)

router = SimpleApplicationsRouter(
simple_applications_service=DummySimpleApplicationsService()
)
request = SimpleNamespace(
state=SimpleNamespace(project_id=str(project_id), user_id=str(user_id))
)

response = await router.fetch_simple_application(
request,
application_id=application_id,
)

overlay = (
response.additional_context.playground_build_kit.agent_template_overlay
if response.additional_context
and response.additional_context.playground_build_kit
else None
)
assert response.application is not None
assert overlay == build_agent_template_overlay()
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Default agent config (playground build kit)

The platform tools, the Agenta authoring skill, and the build permissions are a playground build
kit, modeled as an **agent-template overlay**: a partial agent template the platform serves
read-only on the inspect response. The frontend merges the overlay onto `parameters.agent` on a
playground run so the assistant can build and improve the agent, and leaves it out on commit. The
agent service stays dumb: it runs the template it receives. The published agent ships with only
the user's own config.

## Files

- `design.md`: the design. The overlay model (the frontend merges, the backend serves, the
service runs as-is), the overlay shape (a partial `parameters.agent`, skills as `@ag.embed`),
where it lives on the inspect response (`additional_context.playground_build_kit.agent_template_overlay`), the
merge semantics, the frontend run-and-commit behavior, the advanced-drawer UI, what the backend
does not do, the interface notes, and the change set.
- `research.md`: the code trace behind the design. Where the default comes from, how platform
tools and skills are shaped, the run path, and the commit path.
- `status.md`: where the design stands, the contract for the frontend, open questions, and
coordination.

## In one line

The frontend reads a read-only `agent_template_overlay` from the inspect response, merges its
tools, skill, and sandbox permissions onto `parameters.agent` on a kit-on run, and leaves them out
on commit. The backend only serves the overlay. The service never knows it exists.
Loading
Loading