Skip to content

feat(frontend): agent config playground controls#4775

Open
mmabrouk wants to merge 1 commit into
mainfrom
feat/agent-playground-ui
Open

feat(frontend): agent config playground controls#4775
mmabrouk wants to merge 1 commit into
mainfrom
feat/agent-playground-ui

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Jun 19, 2026

Copy link
Copy Markdown
Member

Agent-workflows: functional PR set

Sliced by functional area, final code only (no intermediate churn). Most PRs are independent off main; two pairs are stacked. This PR's base is main.

Context

The playground needs to edit a typed agent config: instructions, model, tools, MCP servers, and runtime settings (harness, sandbox, permission policy). The backend already exposes an agent_config catalog type generated from the SDK model, and ships a thin x-ag-type-ref: "agent_config" marker on the field. This PR is the frontend that resolves that marker and renders the form. It branches from main and is one functional slice of the agent-workflows feature, showing the final code for the playground controls.

What this changes

The schema renderer now recognizes the agent_config marker and dispatches the whole config object to one composite control, AgentConfigControl. That control does not invent new widgets. It reuses the model selector, the tool picker, the enum selects, and a textarea, and adds one new piece: McpServerItemControl, a JSON editor for a single MCP server entry. The create-app dropdown and the create-app type modal both gain an "Agent" option with a robot icon. An agent app runs in chat mode: is_chat is now true for type chat or agent.

Key architectural decision to review

The form is driven by the catalog type, not hand-built. The backend marks the field with x-ag-type-ref, and the frontend dispatches on that marker (SchemaPropertyRenderer.tsx:130). The control then reads each sub-field's schema off schema.properties and renders it with an existing control. This keeps the form in sync with the typed schema as long as the property names match: agents_md, model, tools, mcp_servers, harness, sandbox, permission_policy. The tradeoff is the coupling lives in property-name string keys, not in types. If the SDK renames a field, the control silently drops it rather than failing the build. Scrutinize whether that contract is documented and tested well enough to survive a backend rename, and whether the two read-back fallbacks (agents_md falling back to instructions) are the right place for legacy compatibility.

The second decision worth a look: tools and mcp_servers are both flat arrays on the config, edited as raw JSON, because the backend resolver parses them. The MCP editor only propagates onChange when the text parses as JSON, and keeps invalid text local until it does. That matches how ToolItemControl already works, so it is consistent, but it means a half-typed server entry is held outside the form value.

How to review this PR

Read AgentConfigControl.tsx first. Check that every sub-control receives the matching props.<field> schema and that setField merges rather than replaces the config object. Confirm the tool helpers (handleAddTool, handleRemoveToolByName, selectedToolNames) all key off toolName, which reads function.name. A tool object missing that path would be invisible to the selected-set and the remove-by-name path, which is the likely regression.

Then read McpServerItemControl.tsx. The lastExternalRef guard resets the editor text only on external value changes (add, remove, reorder) and not on the user's own keystrokes. Check that the parse-then-propagate path does not clobber in-progress edits, and that an empty string maps to {} rather than throwing.

Then appUtils.ts:211 for the is_chat change, and the two create-app menus plus iconHelpers.tsx for the Agent option and robot icon.

Tests / notes

No automated tests ship with this slice. Watch the schema-name contract: the control depends on the SDK field names staying stable, and a rename surfaces as a missing sub-control rather than a type error.

@vercel

vercel Bot commented Jun 19, 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, Comment Jun 19, 2026 4:29pm

Request Review

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. feature Frontend labels Jun 19, 2026
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an "agent" app type to the platform. The AppType union gains "agent", ephemeral app creation sets is_chat for agent type, creation entry points (dropdown and modal) expose a new Agent option with RobotIcon, and two new schema-driven UI components (McpServerItemControl, AgentConfigControl) are added and wired into SchemaPropertyRenderer via an "agent_config" control type.

Changes

Agent App Type

Layer / File(s) Summary
Agent type registration and creation entry points
web/packages/agenta-entities/src/workflow/state/appUtils.ts, web/oss/src/components/pages/prompts/assets/iconHelpers.tsx, web/oss/src/components/pages/app-management/components/CreateAppDropdown/index.tsx, web/oss/src/components/pages/app-management/modals/CreateAppTypeModal/index.tsx
AppType union adds "agent"; createEphemeralAppFromTemplate sets flags.is_chat true for both "chat" and "agent"; getAppTypeIcon returns RobotIcon for agent; both the dropdown and the type-selection modal gain an Agent entry.
McpServerItemControl
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/McpServerItemControl.tsx
New memoized component for editing a single MCP server entry: normalizes value via toServerObj, tracks external vs. local editor text, propagates only valid JSON via onChange, renders with SharedEditor or a textarea fallback.
AgentConfigControl
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentConfigControl.tsx
New composite component for editing an agent_config record: normalizes config, manages tools and mcp_servers arrays with add/change/delete callbacks, derives agentsMd with instructions fallback, and renders instructions, tool controls, MCP server rows, and harness/sandbox/permission enum selects.
SchemaPropertyRenderer wiring and exports
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/SchemaPropertyRenderer.tsx, web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/index.ts
SchemaPropertyRenderer detects x-ag-type-ref/x-ag-type equal to "agent_config" in getControlType and dispatches to AgentConfigControl in the switch. Barrel file re-exports McpServerItemControl and AgentConfigControl with their Props types.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'agent config playground controls' accurately summarizes the main change: adding UI components to manage agent configuration settings in a playground environment.
Docstring Coverage ✅ Passed Docstring coverage is 60.00% which is sufficient. The required threshold is 60.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing agent-workflow UI controls and configuration management changes reflected in the file summaries.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-playground-ui

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 and usage tips.

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
web/oss/src/components/pages/app-management/modals/CreateAppTypeModal/index.tsx (1)

4-5: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the comment to reflect three app types.

The comment references "two built-in app types (Chat / Completion)" but the modal now includes a third type (Agent).

📝 Proposed fix
- * Onboarding modal that surfaces the two built-in app types (Chat /
- * Completion) as large, equal-weight choices. Used by the welcome-card
+ * Onboarding modal that surfaces the built-in app types (Chat, Completion,
+ * and Agent) as large, equal-weight choices. Used by the welcome-card
🧹 Nitpick comments (2)
web/oss/src/components/pages/app-management/modals/CreateAppTypeModal/index.tsx (1)

126-126: ⚡ Quick win

Consider grid layout with three items.

The grid uses grid-cols-2 but now displays three options, which will create an asymmetric 2+1 layout. Consider switching to grid-cols-3 for equal spacing or use a flex layout that wraps naturally.

♻️ Proposed fix for three-column grid
-            <div className="grid grid-cols-2 gap-3">
+            <div className="grid grid-cols-3 gap-3">
web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentConfigControl.tsx (1)

49-56: ⚡ Quick win

Consider memoizing AgentConfigControl for performance.

This is a complex composite control with multiple child controls and array mappings. Wrapping the component with React.memo would prevent unnecessary re-renders when parent props haven't changed.

♻️ Proposed fix
-export function AgentConfigControl({
+export const AgentConfigControl = memo(function AgentConfigControl({
     schema,
     value,
     onChange,
     withTooltip,
     disabled,
     className,
 }: AgentConfigControlProps) {
+    // ... component body
+})
-}

Don't forget to import memo:

-import {useCallback, useMemo} from "react"
+import {memo, useCallback, useMemo} from "react"

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 20bbe915-9e47-49b1-baf5-80f46cfeddd5

📥 Commits

Reviewing files that changed from the base of the PR and between a97e608 and 77b6dcb.

📒 Files selected for processing (8)
  • web/oss/src/components/pages/app-management/components/CreateAppDropdown/index.tsx
  • web/oss/src/components/pages/app-management/modals/CreateAppTypeModal/index.tsx
  • web/oss/src/components/pages/prompts/assets/iconHelpers.tsx
  • web/packages/agenta-entities/src/workflow/state/appUtils.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentConfigControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/McpServerItemControl.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/SchemaPropertyRenderer.tsx
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/index.ts

Comment on lines +62 to +65
const setField = useCallback(
(key: string, fieldValue: unknown) => onChange({...config, [key]: fieldValue}),
[config, onChange],
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stabilize the setField callback to prevent unnecessary re-renders.

The setField callback includes config in its dependency array, causing it to be recreated on every config change. This breaks referential stability and can trigger unnecessary re-renders in child components that receive callbacks derived from setField (like setTools).

🚀 Proposed fix using functional update
 const setField = useCallback(
-    (key: string, fieldValue: unknown) => onChange({...config, [key]: fieldValue}),
-    [config, onChange],
+    (key: string, fieldValue: unknown) => {
+        onChange((prev) => ({...(prev ?? {}), [key]: fieldValue}))
+    },
+    [onChange],
 )

If onChange doesn't support functional updates, wrap it:

+const stableOnChange = useCallback((updater: (prev: Record<string, unknown>) => Record<string, unknown>) => {
+    onChange(updater(config))
+}, [onChange, config])
+
 const setField = useCallback(
-    (key: string, fieldValue: unknown) => onChange({...config, [key]: fieldValue}),
-    [config, onChange],
+    (key: string, fieldValue: unknown) => stableOnChange((prev) => ({...prev, [key]: fieldValue})),
+    [stableOnChange],
 )

@mmabrouk

Copy link
Copy Markdown
Member Author

Reviewer guide: interesting code

The agent config form is schema-generated, not hand-built. Read these in order:

  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/SchemaPropertyRenderer.tsx:130 — the agent_config marker dispatch (x-ag-type-ref first, then x-ag-type); the render case is at line 430.
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentConfigControl.tsx:62setField keeps the rest of the config intact on every write; the tool and mcp_servers handlers below it write flat arrays the backend resolver parses.
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentConfigControl.tsx:149agents_md is the catalog field; instructions is read only as a legacy fallback, and the control writes agents_md only.
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/McpServerItemControl.tsx:69handleEditorChange emits onChange only when the JSON parses; the useEffect above re-syncs editor text on external add/remove/reorder. Watch for an edit loop or stale text here.
  • web/packages/agenta-entities/src/workflow/state/appUtils.ts:211is_chat covers agent, so the agent app routes through chat mode.

if (xAgTypeRef === "code" || xAgType === "code") {
return "code"
}
if (xAgTypeRef === "agent_config" || xAgType === "agent_config") {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is the whole schema-to-form contract: an `agent_config` x-ag-type-ref (or x-ag-type) routes the field to AgentConfigControl. The typed shape stays in the SDK `AgentConfigSchema`; the playground just follows the marker. Confirm the agent service actually ships this ref on the config field, otherwise the field falls through to a generic control.

const agentsMd =
(config.agents_md as string | null | undefined) ??
(config.instructions as string | null | undefined) ??
null

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Read fallback: the editor populates from `agents_md`, then `instructions` as a legacy key. But every write goes to `agents_md` only (setField below). So a config stored under the old `instructions` key shows in the editor, and the first edit silently migrates it to `agents_md`. Confirm `agents_md` is the field the backend reads, so this migration lands somewhere valid.

}
}, [value])

const handleEditorChange = useCallback(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

`onChange` fires only when the text parses as JSON; invalid text stays in the editor and is not propagated. That keeps a half-typed server out of the config, but it also means a user can leave the editor showing text that was never saved. Worth confirming the surrounding form signals unsaved/invalid state, otherwise an MCP server edit can look applied but silently not be.

@mmabrouk

Copy link
Copy Markdown
Member Author

Reviewer guide: interesting code

Suggested reading order and the load-bearing lines:

  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/SchemaPropertyRenderer.tsx:130 — the dispatch. x-ag-type-ref: "agent_config" (or x-ag-type) routes the whole field to one composite control. This is the seam between the backend catalog type and the form.
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentConfigControl.tsx:41toolName reads function.name. Every tool helper (selected-set, remove-by-name) keys off it; a tool object missing that path is invisible to those paths.
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentConfigControl.tsx:148agents_md falls back to the legacy instructions key on read, but always writes back to agents_md. Worth confirming this one-way migration is intended.
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/McpServerItemControl.tsx:62 — the lastExternalRef guard distinguishes external value changes from the user's own keystrokes, so typing does not reset the editor. Parse-then-propagate keeps invalid JSON local until it parses.
  • web/packages/agenta-entities/src/workflow/state/appUtils.ts:211 — an agent app is chat-mode: is_chat is now true for chat or agent. The backend also infers is_chat from messages-in.

if (xAgTypeRef === "code" || xAgType === "code") {
return "code"
}
if (xAgTypeRef === "agent_config" || xAgType === "agent_config") {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is the dispatch seam. x-ag-type-ref: "agent_config" routes the whole field object to one composite control instead of per-property rendering. The control then reads each sub-field's schema off schema.properties by name, so the form stays correct only as long as the SDK field names hold. A backend rename surfaces as a missing sub-control, not a type error.

}

/** Read the function name of a tool object (the gateway slug for Composio tools). */
function toolName(tool: unknown): string | undefined {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Every tool helper keys off this toolName (it reads function.name): the selected-set, remove-by-name, and the visible/added check all depend on it. A tool object that lacks function.name is silently invisible to those paths, so it can neither be deduped nor removed by name. Worth confirming every tool shape the picker emits carries that field.

// Reset the editor text when the value changes from outside (add/remove/reorder).
const lastExternalRef = useRef<string>(safeStringify(serverObj ?? {}))
useEffect(() => {
const next = safeStringify(toServerObj(value) ?? {})

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The lastExternalRef guard is the subtle part: it resets the editor text only when the value changes from outside (add/remove/reorder), not on the user's own keystrokes. onChange propagates only when the text parses as JSON, so a half-typed server entry is held local and never reaches the form value. Same model as ToolItemControl, just flagging that in-progress invalid JSON is intentionally not persisted.

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Codex subagent review for #4775

I found one blocking correctness concern, but GitHub would not allow this account to submit REQUEST_CHANGES on its own PR, so this is posted as a comment review.

Findings:

  • P1 web/oss/src/components/pages/app-management/components/CreateAppDropdown/index.tsx:37 and web/oss/src/components/pages/app-management/modals/CreateAppTypeModal/index.tsx:51 - The Agent option is unconditional, but createEphemeralAppFromTemplate only succeeds if matchTemplateForType finds an application catalog template for agent. I verified this against the actual backend stack rather than relying only on #4779 docs: #4772's services/oss/src/agent/app.py registers the handler directly and explicitly notes there is no agenta:builtin:agent:v0 / first-class workflow type yet. With this PR and no agent catalog template, users can select Agent and always hit Couldn't start app creation — please retry. Please gate the Agent option on a real catalog template, or land the first-class template in the dependency, so the UI does not surface a dead create path.

  • P2 web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentConfigControl.tsx:215 - This ToolSelectorPopover omits onRemoveBuiltinTool, while the existing PromptSchemaControl passes it. Built-in tools still render as selected through selectedTools, but clicking the checked built-in item does nothing because BuiltinToolsPane only removes via onRemoveBuiltinTool. Users can still remove the card manually, but the selector toggle is broken and inconsistent with prompt tools. Please add the same payload-based remove handler used by the prompt control.

  • P2 web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/McpServerItemControl.tsx:69 - The MCP editor treats any parseable JSON as valid and calls onChange, including arrays and primitives. mcp_servers is an array of server objects, so storing [], 1, or "x" creates an invalid config rather than keeping the editor state local/invalid. Please only propagate object values (empty string -> {} is fine), or attach a validation schema that rejects non-object JSON.

I did not run tests; this was a GitHub diff/context review. Residual risk: the agent_config property-name contract still needs coverage because a backend rename would silently drop a subcontrol.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Frontend size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant