diff --git a/packages/adapters/claude-local/src/ui/build-config.ts b/packages/adapters/claude-local/src/ui/build-config.ts index 748c8dabd2d..f7f20bca44f 100644 --- a/packages/adapters/claude-local/src/ui/build-config.ts +++ b/packages/adapters/claude-local/src/ui/build-config.ts @@ -66,8 +66,6 @@ export function buildClaudeLocalConfig(v: CreateConfigValues): Record = {}; if (v.cwd) ac.cwd = v.cwd; if (v.instructionsFilePath) ac.instructionsFilePath = v.instructionsFilePath; - if (v.promptTemplate) ac.promptTemplate = v.promptTemplate; - if (v.bootstrapPrompt) ac.bootstrapPromptTemplate = v.bootstrapPrompt; if (v.model) ac.model = v.model; if (v.thinkingEffort) ac.effort = v.thinkingEffort; if (v.chrome) ac.chrome = true; diff --git a/packages/adapters/codex-local/src/ui/build-config.ts b/packages/adapters/codex-local/src/ui/build-config.ts index 8721ba768fb..78e5e806f88 100644 --- a/packages/adapters/codex-local/src/ui/build-config.ts +++ b/packages/adapters/codex-local/src/ui/build-config.ts @@ -70,8 +70,6 @@ export function buildCodexLocalConfig(v: CreateConfigValues): Record = {}; if (v.cwd) ac.cwd = v.cwd; if (v.instructionsFilePath) ac.instructionsFilePath = v.instructionsFilePath; - if (v.promptTemplate) ac.promptTemplate = v.promptTemplate; - if (v.bootstrapPrompt) ac.bootstrapPromptTemplate = v.bootstrapPrompt; ac.model = v.model || DEFAULT_CODEX_LOCAL_MODEL; if (v.thinkingEffort) ac.modelReasoningEffort = v.thinkingEffort; ac.timeoutSec = 0; diff --git a/packages/adapters/cursor-local/src/ui/build-config.ts b/packages/adapters/cursor-local/src/ui/build-config.ts index d64f9ee2829..0bc09e83123 100644 --- a/packages/adapters/cursor-local/src/ui/build-config.ts +++ b/packages/adapters/cursor-local/src/ui/build-config.ts @@ -61,8 +61,6 @@ export function buildCursorLocalConfig(v: CreateConfigValues): Record = {}; if (v.cwd) ac.cwd = v.cwd; if (v.instructionsFilePath) ac.instructionsFilePath = v.instructionsFilePath; - if (v.promptTemplate) ac.promptTemplate = v.promptTemplate; - if (v.bootstrapPrompt) ac.bootstrapPromptTemplate = v.bootstrapPrompt; ac.model = v.model || DEFAULT_CURSOR_LOCAL_MODEL; const mode = normalizeMode(v.thinkingEffort); if (mode) ac.mode = mode; diff --git a/packages/adapters/gemini-local/src/ui/build-config.ts b/packages/adapters/gemini-local/src/ui/build-config.ts index 5ce333a41ed..aa5c84d5652 100644 --- a/packages/adapters/gemini-local/src/ui/build-config.ts +++ b/packages/adapters/gemini-local/src/ui/build-config.ts @@ -55,8 +55,6 @@ export function buildGeminiLocalConfig(v: CreateConfigValues): Record = {}; if (v.cwd) ac.cwd = v.cwd; if (v.instructionsFilePath) ac.instructionsFilePath = v.instructionsFilePath; - if (v.promptTemplate) ac.promptTemplate = v.promptTemplate; - if (v.bootstrapPrompt) ac.bootstrapPromptTemplate = v.bootstrapPrompt; ac.model = v.model || DEFAULT_GEMINI_LOCAL_MODEL; ac.timeoutSec = 0; ac.graceSec = 15; diff --git a/packages/adapters/opencode-local/src/ui/build-config.ts b/packages/adapters/opencode-local/src/ui/build-config.ts index fa941ed2303..a6ab2728880 100644 --- a/packages/adapters/opencode-local/src/ui/build-config.ts +++ b/packages/adapters/opencode-local/src/ui/build-config.ts @@ -54,8 +54,6 @@ export function buildOpenCodeLocalConfig(v: CreateConfigValues): Record = {}; if (v.cwd) ac.cwd = v.cwd; if (v.instructionsFilePath) ac.instructionsFilePath = v.instructionsFilePath; - if (v.promptTemplate) ac.promptTemplate = v.promptTemplate; - if (v.bootstrapPrompt) ac.bootstrapPromptTemplate = v.bootstrapPrompt; if (v.model) ac.model = v.model; if (v.thinkingEffort) ac.variant = v.thinkingEffort; ac.dangerouslySkipPermissions = v.dangerouslySkipPermissions; diff --git a/packages/adapters/pi-local/src/ui/build-config.ts b/packages/adapters/pi-local/src/ui/build-config.ts index 5748064eefc..470ecedaf0f 100644 --- a/packages/adapters/pi-local/src/ui/build-config.ts +++ b/packages/adapters/pi-local/src/ui/build-config.ts @@ -47,8 +47,6 @@ export function buildPiLocalConfig(v: CreateConfigValues): Record = {}; if (v.cwd) ac.cwd = v.cwd; if (v.instructionsFilePath) ac.instructionsFilePath = v.instructionsFilePath; - if (v.promptTemplate) ac.promptTemplate = v.promptTemplate; - if (v.bootstrapPrompt) ac.bootstrapPromptTemplate = v.bootstrapPrompt; if (v.model) ac.model = v.model; if (v.thinkingEffort) ac.thinking = v.thinkingEffort; diff --git a/packages/shared/src/adapter-types.test.ts b/packages/shared/src/adapter-types.test.ts index 4be83236598..49646cb8f85 100644 --- a/packages/shared/src/adapter-types.test.ts +++ b/packages/shared/src/adapter-types.test.ts @@ -26,6 +26,20 @@ describe("dynamic adapter type validation schemas", () => { ).toThrow(); }); + it("accepts an explicit managed instructions bundle for new agents", () => { + expect( + createAgentSchema.parse({ + name: "Bundle Agent", + adapterType: "codex_local", + instructionsBundle: { + files: { + "AGENTS.md": "Use AGENTS.md.", + }, + }, + }).instructionsBundle?.files["AGENTS.md"], + ).toBe("Use AGENTS.md."); + }); + it("accepts external adapter types in invite acceptance schema", () => { expect( acceptInviteSchema.parse({ diff --git a/packages/shared/src/validators/agent.ts b/packages/shared/src/validators/agent.ts index 43d78a3b072..3f33ae777bd 100644 --- a/packages/shared/src/validators/agent.ts +++ b/packages/shared/src/validators/agent.ts @@ -44,6 +44,13 @@ const adapterConfigSchema = z.record(z.unknown()).superRefine((value, ctx) => { } }); +export const createAgentInstructionsBundleSchema = z.object({ + entryFile: z.string().trim().min(1).optional(), + files: z.record(z.string()).refine((files) => Object.keys(files).length > 0, { + message: "instructionsBundle.files must contain at least one file", + }), +}); + export const createAgentSchema = z.object({ name: z.string().min(1), role: z.enum(AGENT_ROLES).optional().default("general"), @@ -54,6 +61,7 @@ export const createAgentSchema = z.object({ desiredSkills: z.array(z.string().min(1)).optional(), adapterType: agentAdapterTypeSchema, adapterConfig: adapterConfigSchema.optional().default({}), + instructionsBundle: createAgentInstructionsBundleSchema.optional(), runtimeConfig: z.record(z.unknown()).optional().default({}), defaultEnvironmentId: z.string().uuid().optional().nullable(), budgetMonthlyCents: z.number().int().nonnegative().optional().default(0), diff --git a/server/src/__tests__/agent-skills-routes.test.ts b/server/src/__tests__/agent-skills-routes.test.ts index 5bf6311a836..4dd60ce861f 100644 --- a/server/src/__tests__/agent-skills-routes.test.ts +++ b/server/src/__tests__/agent-skills-routes.test.ts @@ -481,8 +481,11 @@ describe.sequential("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", - adapterConfig: { - promptTemplate: "You are QA.", + adapterConfig: {}, + instructionsBundle: { + files: { + "AGENTS.md": "You are QA.", + }, }, })); @@ -504,7 +507,7 @@ describe.sequential("agent skill routes", () => { }); }); - it("clears legacy prompt templates when directly created local agents already provide instructions", async () => { + it("rejects legacy prompt templates for directly created local agents", async () => { const res = await requestApp(await createApp(), (baseUrl) => request(baseUrl) .post("/api/companies/company-1/agents") .send({ @@ -518,21 +521,10 @@ describe.sequential("agent skill routes", () => { }, })); - expect([200, 201], JSON.stringify(res.body)).toContain(res.status); + expect(res.status, JSON.stringify(res.body)).toBe(422); + expect(res.body.error).toContain("New agents must use instructionsBundle/AGENTS.md"); + expect(mockAgentService.create).not.toHaveBeenCalled(); expect(mockAgentInstructionsService.materializeManagedBundle).not.toHaveBeenCalled(); - expect(mockAgentService.update).toHaveBeenCalledWith( - "11111111-1111-4111-8111-111111111111", - expect.objectContaining({ - adapterConfig: expect.objectContaining({ - instructionsFilePath: "/tmp/existing/AGENTS.md", - }), - }), - ); - const updateInput = mockAgentService.update.mock.calls.at(-1)?.[1] as - | { adapterConfig?: Record } - | undefined; - expect(updateInput?.adapterConfig?.promptTemplate).toBeUndefined(); - expect(updateInput?.adapterConfig?.bootstrapPromptTemplate).toBeUndefined(); }); it("materializes the bundled CEO instruction set for default CEO agents", async () => { @@ -683,8 +675,11 @@ describe.sequential("agent skill routes", () => { name: "QA Agent", role: "engineer", adapterType: "claude_local", - adapterConfig: { - promptTemplate: "You are QA.", + adapterConfig: {}, + instructionsBundle: { + files: { + "AGENTS.md": "You are QA.", + }, }, }); @@ -707,7 +702,7 @@ describe.sequential("agent skill routes", () => { expect(approvalInput?.payload?.adapterConfig?.promptTemplate).toBeUndefined(); }); - it("clears legacy prompt templates from hire approval payloads that already provide instructions", async () => { + it("rejects legacy prompt templates for hire approval payloads", async () => { const res = await request(await createApp(createDb(true))) .post("/api/companies/company-1/agent-hires") .send({ @@ -721,22 +716,9 @@ describe.sequential("agent skill routes", () => { }, }); - expect(res.status, JSON.stringify(res.body)).toBe(201); + expect(res.status, JSON.stringify(res.body)).toBe(422); + expect(res.body.error).toContain("New agents must use instructionsBundle/AGENTS.md"); + expect(mockAgentService.create).not.toHaveBeenCalled(); expect(mockAgentInstructionsService.materializeManagedBundle).not.toHaveBeenCalled(); - expect(mockApprovalService.create).toHaveBeenCalledWith( - "company-1", - expect.objectContaining({ - payload: expect.objectContaining({ - adapterConfig: expect.objectContaining({ - instructionsFilePath: "/tmp/existing/AGENTS.md", - }), - }), - }), - ); - const approvalInput = mockApprovalService.create.mock.calls.at(-1)?.[1] as - | { payload?: { adapterConfig?: Record } } - | undefined; - expect(approvalInput?.payload?.adapterConfig?.promptTemplate).toBeUndefined(); - expect(approvalInput?.payload?.adapterConfig?.bootstrapPromptTemplate).toBeUndefined(); }); }); diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index 3a587755a44..78172f321f1 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -690,7 +690,10 @@ export function agentRoutes( role: string; adapterType: string; adapterConfig: unknown; - }>(agent: T): Promise { + }>( + agent: T, + input?: { files: Record; entryFile?: string }, + ): Promise { if (!adapterSupportsInstructionsBundle(agent.adapterType)) { return agent; } @@ -715,24 +718,33 @@ export function agentRoutes( return (updated as T | null) ?? { ...agent, adapterConfig: nextAdapterConfig }; } - const promptTemplate = typeof adapterConfig.promptTemplate === "string" - ? adapterConfig.promptTemplate - : ""; - const files = promptTemplate.trim().length === 0 - ? await loadDefaultAgentInstructionsBundle(resolveDefaultAgentInstructionsBundleRole(agent.role)) - : { "AGENTS.md": promptTemplate }; + const files = input?.files + ?? await loadDefaultAgentInstructionsBundle(resolveDefaultAgentInstructionsBundleRole(agent.role)); const materialized = await instructions.materializeManagedBundle( agent, files, - { entryFile: "AGENTS.md", replaceExisting: false }, + { entryFile: input?.entryFile ?? "AGENTS.md", replaceExisting: false }, ); const nextAdapterConfig = { ...materialized.adapterConfig }; delete nextAdapterConfig.promptTemplate; + delete nextAdapterConfig.bootstrapPromptTemplate; const updated = await svc.update(agent.id, { adapterConfig: nextAdapterConfig }); return (updated as T | null) ?? { ...agent, adapterConfig: nextAdapterConfig }; } + function assertNoNewAgentLegacyPromptTemplate(adapterType: string, adapterConfig: Record) { + if (!adapterSupportsInstructionsBundle(adapterType)) return; + if ( + Object.prototype.hasOwnProperty.call(adapterConfig, "promptTemplate") + || Object.prototype.hasOwnProperty.call(adapterConfig, "bootstrapPromptTemplate") + ) { + throw unprocessable( + "New agents must use instructionsBundle/AGENTS.md instead of adapterConfig.promptTemplate or bootstrapPromptTemplate", + ); + } + } + async function assertCanManageInstructionsPath(req: Request, targetAgent: { id: string; companyId: string }) { assertCompanyAccess(req, targetAgent.companyId); if (req.actor.type !== "board") { @@ -1474,11 +1486,16 @@ export function agentRoutes( const sourceIssueIds = parseSourceIssueIds(req.body); const { desiredSkills: requestedDesiredSkills, + instructionsBundle, sourceIssueId: _sourceIssueId, sourceIssueIds: _sourceIssueIds, ...hireInput } = req.body; hireInput.adapterType = assertKnownAdapterType(hireInput.adapterType); + assertNoNewAgentLegacyPromptTemplate( + hireInput.adapterType, + (hireInput.adapterConfig ?? {}) as Record, + ); assertNoAgentHostWorkspaceCommandMutation( req, collectAgentAdapterWorkspaceCommandPaths(hireInput.adapterConfig), @@ -1531,7 +1548,7 @@ export function agentRoutes( spentMonthlyCents: 0, lastHeartbeatAt: null, }); - const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent); + const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent, instructionsBundle); let approval: Awaited> | null = null; const actor = getActorInfo(req); @@ -1661,9 +1678,14 @@ export function agentRoutes( const { desiredSkills: requestedDesiredSkills, + instructionsBundle, ...createInput } = req.body; createInput.adapterType = assertKnownAdapterType(createInput.adapterType); + assertNoNewAgentLegacyPromptTemplate( + createInput.adapterType, + (createInput.adapterConfig ?? {}) as Record, + ); assertNoAgentHostWorkspaceCommandMutation( req, collectAgentAdapterWorkspaceCommandPaths(createInput.adapterConfig), @@ -1706,7 +1728,7 @@ export function agentRoutes( spentMonthlyCents: 0, lastHeartbeatAt: null, }); - const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent); + const agent = await materializeDefaultInstructionsBundleForNewAgent(createdAgent, instructionsBundle); const actor = getActorInfo(req); await logActivity(db, { diff --git a/skills/paperclip-create-agent/SKILL.md b/skills/paperclip-create-agent/SKILL.md index 6fc4539b3ef..ee1be7e61a7 100644 --- a/skills/paperclip-create-agent/SKILL.md +++ b/skills/paperclip-create-agent/SKILL.md @@ -85,7 +85,7 @@ curl -sS "$PAPERCLIP_API_URL/llms/agent-icons.txt" \ - capabilities - managed instructions bundle (`AGENTS.md`) for adapters that support it; avoid durable `promptTemplate` config - for coding or execution agents, include the Paperclip execution contract: start actionable work in the same heartbeat; do not stop at a plan unless planning was requested; leave durable progress with a clear next action; use child issues for long or parallel delegated work instead of polling; mark blocked work with owner/action; respect budget, pause/cancel, approval gates, and company boundaries -- instruction text such as `AGENTS.md` built from step 4; for local managed-bundle adapters, agent-authenticated hire requests may send the adapted `AGENTS.md` in `adapterConfig.promptTemplate` only as a creation-time transport because the server materializes it into managed `AGENTS.md` and removes the legacy field. Do not set `promptTemplate` when you are already setting an instructions bundle/path. +- instruction text such as `AGENTS.md` built from step 4; for local managed-bundle adapters, send this as top-level `instructionsBundle.files["AGENTS.md"]`. Do not set `adapterConfig.promptTemplate` or `bootstrapPromptTemplate` for new agents. - source issue linkage (`sourceIssueId` or `sourceIssueIds`) when this hire came from an issue ### 7. Review the draft against the quality checklist @@ -109,6 +109,7 @@ curl -sS -X POST "$PAPERCLIP_API_URL/api/companies/$PAPERCLIP_COMPANY_ID/agent-h "desiredSkills": ["vercel-labs/agent-browser/agent-browser"], "adapterType": "codex_local", "adapterConfig": {"cwd": "/abs/path/to/repo", "model": "o4-mini"}, + "instructionsBundle": {"files": {"AGENTS.md": "You are the CTO..."}}, "runtimeConfig": {"heartbeat": {"enabled": false, "wakeOnDemand": true}}, "sourceIssueId": "" }' diff --git a/skills/paperclip-create-agent/references/agent-instruction-templates.md b/skills/paperclip-create-agent/references/agent-instruction-templates.md index 3bb267ef729..5a29e2312f9 100644 --- a/skills/paperclip-create-agent/references/agent-instruction-templates.md +++ b/skills/paperclip-create-agent/references/agent-instruction-templates.md @@ -41,7 +41,7 @@ If you are hiring a role that is not in this index, do not force a fit. Use the ## How to apply an exact template 1. Open the matching reference in `references/agents/`. -2. Copy that template into the new agent's instruction bundle (usually `AGENTS.md`). For agent-authenticated hire requests using local managed-bundle adapters, `adapterConfig.promptTemplate` is only a creation-time transport for the adapted template; Paperclip materializes it into managed `AGENTS.md` and removes the legacy field. Do not combine `promptTemplate` with an explicit instructions bundle/path. +2. Copy that template into the new agent's instruction bundle (usually `AGENTS.md`). For hire requests using local managed-bundle adapters, send the adapted template as top-level `instructionsBundle.files["AGENTS.md"]`. Do not put new-agent instructions in `adapterConfig.promptTemplate`. 3. Replace placeholders like `{{companyName}}`, `{{managerTitle}}`, `{{issuePrefix}}`, and URLs. 4. Remove tools or workflows the target adapter cannot use. 5. Keep the Paperclip heartbeat requirement and the task-comment requirement. diff --git a/skills/paperclip-create-agent/references/api-reference.md b/skills/paperclip-create-agent/references/api-reference.md index f14177be9a0..422e0be98cd 100644 --- a/skills/paperclip-create-agent/references/api-reference.md +++ b/skills/paperclip-create-agent/references/api-reference.md @@ -42,8 +42,13 @@ Request body matches agent create shape: "adapterType": "claude_local", "adapterConfig": { "cwd": "/absolute/path", - "model": "claude-sonnet-4-5-20250929", - "promptTemplate": "You are CTO..." + "model": "claude-sonnet-4-5-20250929" + }, + "instructionsBundle": { + "entryFile": "AGENTS.md", + "files": { + "AGENTS.md": "You are CTO..." + } }, "runtimeConfig": { "heartbeat": { diff --git a/skills/paperclip-create-agent/references/baseline-role-guide.md b/skills/paperclip-create-agent/references/baseline-role-guide.md index 3178ecba106..b2ce1402a05 100644 --- a/skills/paperclip-create-agent/references/baseline-role-guide.md +++ b/skills/paperclip-create-agent/references/baseline-role-guide.md @@ -118,7 +118,7 @@ How the agent verifies its own work before marking an issue done or handing it t - **Over-generic prompts.** "Be helpful, be thorough, be correct" is worthless — the next agent drafts a better version by reading the template you adapted from. Write role-specific guidance only. - **Lens dumping.** Copying every lens from an expert template into an unrelated role adds noise and burns context. Five well-chosen lenses beat fifteen irrelevant ones. - **Permission sprawl.** Do not grant write access, admin endpoints, or broad skill sets "just in case." Grant exactly what the role needs. -- **Secrets in adapter config.** Do not embed long-lived tokens, API keys, or private URLs in `adapterConfig` or `promptTemplate` when environment injection or a scoped skill can carry the capability instead. +- **Secrets in agent config.** Do not embed long-lived tokens, API keys, or private URLs in `adapterConfig`, `instructionsBundle`, or legacy prompt fields when environment injection or a scoped skill can carry the capability instead. - **Silent timer heartbeats.** A timer heartbeat burns budget every interval. If the role has no scheduled work, leave it off. - **Bypassing governance.** Never skip `sourceIssueId`, reporting line, icon, or approval flow to ship faster. Hires without these are hard to audit and hard to hand off. - **Copying another company's prompt verbatim.** Placeholders like `{{companyName}}`, `{{managerTitle}}`, and `{{issuePrefix}}` must be replaced with this company's values before submitting the hire. diff --git a/skills/paperclip-create-agent/references/draft-review-checklist.md b/skills/paperclip-create-agent/references/draft-review-checklist.md index 18c097099ba..b1408513530 100644 --- a/skills/paperclip-create-agent/references/draft-review-checklist.md +++ b/skills/paperclip-create-agent/references/draft-review-checklist.md @@ -57,13 +57,13 @@ Use it for every path: exact template, adjacent template, or generic fallback. - [ ] `sourceIssueId` (or `sourceIssueIds`) is set when the hire was triggered by an issue - [ ] `desiredSkills` lists only skills that already exist in the company library, or will be installed first via the company-skills workflow - [ ] Adapter config matches this Paperclip instance (cwd, model, credentials) per `/llms/agent-configuration/.txt` -- [ ] Local managed-bundle adapters end up with managed `AGENTS.md` config, not durable `adapterConfig.promptTemplate`; use `promptTemplate` only as the creation-time transport when the API caller cannot set bundle files directly +- [ ] Local managed-bundle adapters send custom instructions through top-level `instructionsBundle.files["AGENTS.md"]` and do not set `adapterConfig.promptTemplate` or `bootstrapPromptTemplate` - [ ] Placeholders like `{{companyName}}`, `{{managerTitle}}`, `{{issuePrefix}}`, and any URL stubs are replaced with real values ## H. Safety and permissions (least privilege) - [ ] The hire grants only the access the role needs — no "just in case" permissions -- [ ] No secrets are embedded in plain text in `adapterConfig`, creation-time instruction text, or any legacy prompt field; prefer environment-injected credentials or scoped skills +- [ ] No secrets are embedded in plain text in `adapterConfig`, `instructionsBundle`, or any legacy prompt field; prefer environment-injected credentials or scoped skills - [ ] Any `desiredSkills` or adapter settings that expand external-system access, browser/network reach, filesystem scope, or secret-handling capability are individually justified in the hire comment - [ ] `runtimeConfig.heartbeat.enabled` is `false` unless the role genuinely needs scheduled recurring work AND `intervalSec` is justified in the hire comment - [ ] `AGENTS.md` explicitly names anything the role must never do (external posts, shared infra changes, destructive ops without approval) diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index b34eee103d6..e4fdd29d96b 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -724,28 +724,6 @@ export function AgentConfigForm(props: AgentConfigFormProps) { )} - {/* Prompt template (create mode only — edit mode shows this in Identity) */} - {isLocal && isCreate && ( - <> - - set!({ promptTemplate: v })} - placeholder="You are agent {{ agent.name }}. Your role is {{ agent.role }}..." - contentClassName="min-h-[88px] text-sm font-mono" - imageUploadHandler={async (file) => { - const namespace = "agents/drafts/prompt-template"; - const asset = await uploadMarkdownImage.mutateAsync({ file, namespace }); - return asset.contentPath; - }} - /> - -
- Prompt template is replayed on every heartbeat. Prefer small task framing and variables like {"{{ context.* }}"} or {"{{ run.* }}"}; avoid repeating stable instructions here. -
- - )} - {/* Adapter-specific fields are rendered inside Permissions & Configuration */}