Skip to content

feat(agents): author agent templates from the dashboard - #2148

Closed
kyleseaman wants to merge 1 commit into
mainfrom
feat/agent-template-create
Closed

feat(agents): author agent templates from the dashboard#2148
kyleseaman wants to merge 1 commit into
mainfrom
feat/agent-template-create

Conversation

@kyleseaman

Copy link
Copy Markdown
Collaborator

The gap

An agent template is the kiro spec at ~/.kiro/agents/<name>.json — the system prompt, tool surface, auto-approve list, MCP servers and skill mapping an agent boots from. The Agent Templates tab could list one, retarget its model, edit its skills, and delete it. It could not create one.

server.py registered only three verbs for the spec, and api_agent_detail's PATCH accepted exactly two fields:

GET    /api/agents/detail/{name}
PATCH  /api/agents/detail/{name}     # model, skills
DELETE /api/agents/detail/{name}

So a template could only arrive four ways: kirocrew setup writing kirocrew.json, a package install, an app shipping <app>--<agent>.json, or someone hand-writing the file. The prompt, tools and description rendered into a read-only <pre>.

Crews (POST/PUT/DELETE /api/agents) do have full CRUD, which is what makes this easy to miss — but a crew only binds to a template, and its template picker offers whatever is already installed. "Make a new agent" therefore meant adopting someone else's definition under a new label.

What this adds

  • POST /api/agents/detail writes <name>.json, either from a conservative blank baseline or as a copy of an existing template (from).
  • PATCH accepts prompt and description, so a created template can be corrected without hand-editing JSON. Without this, phase 1 alone would give you a template you could create once and never fix from the UI.

What is deliberately not writable

tools, allowedTools and toolsSettings are not accepted from either verb's request body. Those three are the privilege surface — allowedTools is the auto-approve list and deniedCommands is the guard that actually blocks commands — so a create call cannot mint a spec that auto-approves everything with the deny patterns stripped.

  • A copy inherits them from a spec that already exists on disk and was already trusted with them.
  • A blank template gets a useful tool surface whose auto-approved subset is read-only: fs_read, code, grep, glob. Editing a file or running a command has to be approved the first time.
  • Kiro Crew's own privileged MCP servers (spawn, cron, computer use) are absent from the blank baseline. Getting that surface means duplicating kirocrew, which is an explicit act rather than a default.

Editing those three fields is a separate capability and a real design conversation; this PR does not grant it.

Guards

Each is pinned by a test that was revert-verified — the fix was patched out and the test confirmed to fail:

Guard Why
Name must be safe as a bare filename stem Traversal and separators are rejected before a path is built
Managed spec names and default are reserved Read from agent_files.OWNED_KIRO_AGENT_FILES, not duplicated
A name any existing spec answers to conflicts kiro-cli resolves by the name field, so a second spec declaring a package agent's name makes which one wins a coin flip — a free filename is not enough
O_EXCL on create The collision scan and the write are two steps; only the kernel makes "create only if absent" atomic against a concurrent POST. The test stubs the scan to reproduce that window rather than trusting it
prompt/description refused on Kiro Crew's own specs They are rewritten on every install, so accepting the edit would show a save that silently reverts
Validation before mutation A rejected combined PATCH cannot leave a half-applied model change behind

The managed flag is served by the backend rather than derived client-side, so the editor disables exactly what PATCH refuses instead of keeping a second copy of the owned-file list in TypeScript that would drift. source would not do: it is filename-derived and only marks the two user-facing specs, while the knowledge/research/heartbeat specs present as ordinary built-ins.

Every rejection carries a machine-readable code; the prose is advisory, since the dashboard renders it into a localized UI.

UI

A New template button on the Installed Agents card and a Duplicate action on the selected template, both opening one dialog (name, start-from, description, system prompt). One component for both entry points because they differ only in that one field — two would have to keep the same name validation, conflict messages and tool-surface explainer in sync.

The detail panel's read-only prompt block becomes an editor with explicit Save/Revert. Unlike the model picker and skills editor on the same page, these do not save on change: a prompt is prose typed over many keystrokes, and save-per-keystroke would rewrite a spec kiro-cli reads for every half-finished sentence. A managed template shows the same content with the reason it is locked.

Verification

  • Backend: 56 new tests. All 9 guards revert-verified.
  • Frontend: 16 new tests. All 7 behavioural guards revert-verified.
  • Gates: pytest (34k, failures below), isort, flake8, mypy (825 files), tsc -b, eslint (0 errors), vitest (10,486 pass), jscpd (0 clones), brand gate, and all 13 i18n checks green against origin/main.
  • i18n: 33 new strings extracted through the repo's own codemod, translated into all 10 locales via i18n-translate.mjs and each verified by its own verifier, plus the regenerated pseudolocale. One key that the editor orphaned (pages.agentsPage.system_prompt) was deleted rather than having the dead-key baseline raised.
  • Visual: the dialog, the editor, and the locked managed state were rendered and inspected.

Pre-existing failures, measured not assumed

21 backend tests and one vitest test fail on this host and are not caused by this diff. Both claims were measured rather than reasoned:

  • The 21 reproduce with this branch's Python files reverted to main, and the two that could not be measured that way (test_app_manager, test_search_sessions_cost — main's handlers/__init__.py imports a module absent from this checkout's base) were run in a separate clone at main, where both fail identically.
  • src/i18n/format.test.ts asserts Intl.DurationFormat is absent. This host runs Node 24, which has it; CI runs Node 20, which does not. The same test fails on main under Node 24.

Follow-up

Editors for tools / allowedTools / deniedCommands — the privilege surface this PR deliberately leaves read-only.

An agent template is the kiro spec at `~/.kiro/agents/<name>.json` — the system
prompt, tool surface, auto-approve list, MCP servers and skill mapping an agent
boots from. The Agent Templates tab could list, retarget the model, edit skills
and delete one, but there was no route that CREATED one: `/api/agents/detail`
served only GET/PATCH/DELETE, and PATCH accepted just `model` and `skills`. A
template could therefore only arrive by installing a package, by an app shipping
one, or by hand-writing JSON. Crews have full CRUD, but a crew only BINDS to a
template — its picker offers whatever is already installed — so "make a new
agent" meant adopting someone else's definition under a new label.

Two capabilities close that:

* `POST /api/agents/detail` writes `<name>.json`, either from a conservative
  blank baseline or as a copy of an existing template (`from`).
* `PATCH` accepts `prompt` and `description`, so a created template can be
  corrected without editing JSON by hand.

The privilege surface is deliberately NOT writable through either verb.
`tools`, `allowedTools` and `toolsSettings` are the auto-approve list and the
bash deny patterns, so a request body cannot mint a spec that auto-approves
everything: a copy inherits them from a spec already on disk, and a blank
template gets a tool surface whose auto-approved subset is read-only — editing
a file or running a command has to be approved the first time. Kiro Crew's own
privileged MCP servers are absent from the blank baseline; getting that surface
means duplicating `kirocrew`, which is an explicit act.

Guards, each pinned by a revert-verified test:

* The name must be safe as a bare filename stem, so traversal and separators are
  rejected before any path is built.
* Managed spec names and the built-in `default` are reserved, read from
  `agent_files.OWNED_KIRO_AGENT_FILES` rather than duplicated.
* A name any EXISTING spec already answers to conflicts, not just a filename
  collision — kiro-cli resolves by the `name` field, so a second spec declaring
  a package agent's name makes which one wins a coin flip.
* The file is created with `O_EXCL`. The collision scan and the write are two
  steps and only the kernel can make "create only if absent" atomic against a
  concurrent POST; the test reproduces that window rather than trusting the scan.
* `prompt`/`description` edits are refused on Kiro Crew's own specs, which are
  rewritten on every install — accepting them would show a save that silently
  reverts. The `managed` flag is served by the backend so the editor disables
  exactly what PATCH refuses instead of keeping a second copy of the owned-file
  list in TypeScript.
* Validation runs before any mutation, so a rejected combined PATCH cannot leave
  a half-applied model change behind.

Every rejection carries a machine-readable `code`; the prose is advisory, since
the dashboard renders it into a localized UI.

UI: a New template button on the Installed Agents card and a Duplicate action on
the selected template, both opening one dialog (name, start-from, description,
system prompt). The detail panel's read-only prompt block becomes an editor with
explicit Save/Revert — a prompt is prose typed over many keystrokes, so
save-per-keystroke would rewrite a spec kiro-cli reads for every half-finished
sentence. A managed template shows the same content with the reason it is locked.

33 new strings across all 10 locales plus the pseudolocale.
@kyleseaman
kyleseaman requested a review from a team August 8, 2026 00:00
@kyleseaman
kyleseaman requested a review from a team as a code owner August 8, 2026 00:00
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Advisory design-level review of 6b5707454d95ec0e64f76c61a87f0b3c651ffbff — updated in place on each push; does not block merge.

Design-Verdict: PASS

Real gap, additive API, and the privilege surface (tools/allowedTools/toolsSettings) is correctly fenced out as a separate capability — sound, proportionate design.

Suggestions

  • docs/system-specs/modules/learn-cron-dashboard.md documents GET/DELETE /api/agents/detail/{name} but not the new POST /api/agents/detail or the extended PATCH fields — the same-commit spec-sync rule applies to this new public surface; update it in this PR.

[DESIGN-REVIEWED] 6b57074

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — 🔴 changes requested (blocking)

GPT 5.6 found at least one blocking issue that must be resolved before merging 6b5707454d95ec0e64f76c61a87f0b3c651ffbff.

This comment is updated in place on each push.

BLOCKING -- website/src/components/AgentTemplateEditor.tsx:51 -- Preserve edits made during an active save
setDesc(description) / setText(prompt)
Save, then continue typing before PATCH completes -> success updates props -> the effect erases newer unsaved text.
Fix: Disable both fields while saveMut.isPending.

BLOCKING -- website/src/components/AgentTemplateCreateDialog.tsx:73 -- Prevent stale create completions from discarding a new draft
onCreated(r.name || n)
Submit A, cancel, reopen and type B before A completes -> A's completion closes the dialog -> B's draft is lost.
Fix: Block dismissal while submitting, including Cancel and onOpenChange.

FINDING -- src/kiro_crew/dashboard/handlers/agents.py:1292 -- Duplicating without a replacement description executes "spec.pop(\"description\", None)" and strips the source description -> Fix: preserve it when cloning unless description was explicitly supplied.

FINDING -- src/kiro_crew/dashboard/handlers/agents.py:1066 -- Calling _managed_agent_filenames() resolves the added "from kiro_crew.agent_files import OWNED_KIRO_AGENT_FILES" inside the function, violating the matched top-level-imports rule -> Fix: move this leaf-module import to the module import block.

[BLOCK-MERGE] 6b57074
[GPT-REVIEWED] 6b57074
False positive or not applicable? A repository writer can comment:
/ai-review override gpt 6b5707454d95ec0e64f76c61a87f0b3c651ffbff: <one-sentence reason>

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — 🟡 CONCERNS

Advisory UX-level review of 6b5707454d95ec0e64f76c61a87f0b3c651ffbff — updated in place on each push; does not block merge.

I have what I need. Two real UX risks surfaced: silent loss of unsaved prompt text, and the fetch-failure fallback masquerading as the "managed template" lock. Here is the review.

UX-Verdict: CONCERNS

Long-form prompt edits can be silently destroyed, and a failed detail fetch shows a misleading "managed by Kiro Crew" lock explanation.

Watch

  • Unsaved prompt text is lost with no guard. AgentTemplateEditor reseeds on [agentName, description, prompt] and the create dialog resets on every open — so clicking another agent row mid-edit, or a stray overlay click/Escape on the dialog, discards a system prompt the user typed over minutes, no confirm, no undo. High impact (prose work destroyed) × plausible frequency (row list sits beside the editor) × every time. Smallest fix: block row-switch/dismiss when dirty with a confirm, or preserve the draft.
  • Fetch-failure fallback lies about why editing is locked. managed={selectedAgent.managed !== false || selectedAgent.skills === undefined} routes a failed detail fetch into the managed view, whose only visible copy is "Kiro Crew rewrites this template on every install… Duplicate it to get an editable copy" — wrong cause, wrong remedy for a user-owned template, and it contradicts the adjacent skills message ("Could not load this agent's configuration… Reselect to retry"). Low frequency × misdirects the user × persists until reselect. Smallest fix: a distinct load-failed state reusing the skills section's wording.

Suggestions

  • Promote the name-format rule ("Letters, digits, dot, dash and underscore only") from the InfoTip to inline pre-submit validation or visible helper text — today "My Agent" fails only after submit, with an untranslated server message.

[UX-REVIEWED] 6b5707454d95ec0e64f76c61a87f0b3c651fbfff

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Opus 5 Review — ✅ no blocking findings

Reviewed 6b5707454d95ec0e64f76c61a87f0b3c651ffbff — this comment is updated in place on each push.

Review details

No blocking findings.

FINDING — src/kiro_crew/dashboard/handlers/agents.py:1066 — from kiro_crew.agent_files import OWNED_KIRO_AGENT_FILES is an in-function import with no circular-import justification (agent_files.py is a documented leaf module with zero intra-package imports, so a top-level import cannot cycle) and no # circular import comment, violating top-level-imports → Fix: import OWNED_KIRO_AGENT_FILES in the module's top-level import block and drop the in-function import.

[OPUS-REVIEWED] 6b57074

Verdict parsed from the review's SHA-scoped output markers for commit 6b5707454d95ec0e64f76c61a87f0b3c651ffbff.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 6b5707454d95ec0e64f76c61a87f0b3c651ffbff: <one-sentence reason>

@github-actions github-actions Bot added the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Aug 8, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator

Overlap notice — three PRs implement Agent Template authoring from the dashboard:

Recommendation: consolidate on #2383 as the base — widest scope (create/edit/clone + all #2255 items), the only branch without merge conflicts, and it matches what issues #1829 and #2255 asked for. However, #2383 is not yet merge-ready: CI is failing (frontend i18n gates, backend test shard, coverage gate), it ships no tests, and only the en catalog. Before merge it should adopt:

@kyleseaman @RohanK6 — inviting you both to review #2383 so the best parts of all three land. If maintainers prefer the original sequencing (#2023 first, #2383 rebased back into the fast-follow it started as), that also works; the main thing is that the tests and i18n coverage from the other two PRs are not lost.

@kyleseaman

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by #2383, which covers strictly more of this surface and now carries this PR's verification.

Why this one loses

#2383 does everything here — POST to create a template — plus PUT to edit, a clone flow, and editing of tools / allowedTools / mcpServers / resources. It also closes #2255, where this PR closed nothing. And main's #1346 (two-pane inspector redesign) landed on 2026-08-09 and reworked the page this PR's AgentTemplateCreateDialog / AgentTemplateEditor were built against, so the frontend half would need rebuilding regardless.

What moved across rather than being thrown away

Rather than let the broader-but-untested implementation ship as-is, this PR's test suite went onto #2383 as commit d13016206, where it immediately caught three schema defects that made created templates non-functional:

  1. The prompt was written to customInstructions, which exists nowhere in this codebase. Since kiro-cli validates ~/.kiro/agents/*.json with serde deny_unknown_fields and rejects the entire spec on any unknown key — then silently falls back to the default agent — every template carrying a prompt was unloadable while the session looked like it was running the user's agent.
  2. deniedCommands was written at the top level (same whole-spec rejection). Relocating it under toolsSettings.execute_bash would have been worse: that mechanism is retired, and agent.py:_strip_legacy_denied_commands deletes the key on every refresh precisely so a stale spec rule cannot outrank Settings > Security. It is now refused outright. The dialog's deny-command editor was also already inert — the form sent the nested toolsSettings shape while the handler only read a top-level key — so it was removed with the field.
  3. PUT guarded only on -- (app-namespaced specs). No entry in OWNED_KIRO_AGENT_FILES contains a double dash, so PUT /api/agents/installed/kirocrew full-replaced Kiro Crew's own spec, dropping hooks (including the bash audit hook), includeMcpJson and the managed MCP block until the next install rebuild.

Also carried over: O_EXCL exclusive create in place of exists() + os.replace, rejection of a name an existing spec already answers to via its name field, reserved managed stems, and carry-forward of spec keys the authoring form does not model so editing a description no longer deletes hand-authored hooks.

87 tests now cover the two endpoints and the MCP env credential predicates on #2383, with every schema guard revert-verified. The one deliberate design difference: this PR refused tools / allowedTools / deniedCommands from the request body entirely, while #2383 accepts the first two behind credential screening. That is a defensible widening — the deny list is the part that stayed closed.

Nothing here is worth reviving. Branch feat/agent-template-create can be deleted.

@kyleseaman kyleseaman closed this Aug 12, 2026
@github-actions github-actions Bot removed the readiness: action required A blocking check or review needs attention label Aug 12, 2026
@bolichen97
bolichen97 deleted the feat/agent-template-create branch September 6, 2026 03:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge conflict Branch has merge conflicts with its base — author must resolve before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants