feat(skills): add a create-skill button to capture a session - #4193
feat(skills): add a create-skill button to capture a session#4193RolianTan wants to merge 1 commit into
Conversation
UX Review (Fable 5, fork) — 🟡 CONCERNSUX-level review of Evidence gathered. Composing the review. UX-Verdict: CONCERNS Kicking off a background skill capture gives zero success feedback or destination — the dialog just closes and the result surfaces nowhere the user was told about. Watch
[UX-REVIEWED] 49d0e5b |
GPT 5.6 Review (fork) — 🔴 changes requested (blocking)Reviewed BLOCKING -- src/kiro_crew/skills.py:3301 -- LLM metadata controls lifecycle namespace FINDING -- src/kiro_crew/dashboard/handlers/create_skill.py:183 -- FINDING -- website/src/pages/ChatPage.tsx:4947 -- |
Design Review (Fable 5, fork) — 🟡 CONCERNSDesign-level review of Design-Verdict: CONCERNS Sound reuse of crystallize + pending-review, with a careful trust story — but the fire-and-forget path has no failure feedback, and the manual namespace rides on prompt compliance. Watch
[DESIGN-REVIEWED] 49d0e5b |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of Reviewed the contract, the patch, and the base tree (crystallize skill, the skills lifecycle sweep, First-Principles-Verdict: CONCERNS The feature and its guards are derived and earn their place; a one-consumer What this change shipsIntent: let a user capture the current chat as a reusable skill without interrupting it — an ADDITION (closes #3181).
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] 49d0e5b |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsThe single candidate rests on whether an agent-resolution refusal ( Step 2: I traced the No findings. [OPUS-REVIEWED] 49d0e5b |
ba4e7cc to
6c4d02c
Compare
6c4d02c to
7579287
Compare
7579287 to
1178971
Compare
cf046b6 to
3a4d367
Compare
6521406 to
23b1731
Compare
23b1731 to
f33510d
Compare
f33510d to
3a81a5f
Compare
Add a "Create skill" action to the chat header's session actions menu. It opens a small modal for a required one-line purpose; submitting hands the current session to a fire-and-forget background subagent that follows the crystallize skill and stages a candidate for review, so authoring never blocks the chat. Backend: owner-only POST /api/skills/-/from-session shapes the session transcript, injects the required purpose (empty is rejected), and spawns the subagent. The subagent is spawned with approval_mode="spawn": the spawn is authorized because the owner triggered it, but its tool calls stay approval-gated and it does NOT inherit a trusted parent's auto policy, because it acts on an untrusted transcript. Permission denials emit a SEL audit event. Private sessions are refused -- incognito/temporary, plus a linked channel session (e.g. a Slack thread set to !incognito) via the session's canonical restriction state rather than the dashboard slot's memory_mode. A "manual" skill namespace keeps user-initiated captures separate from auto-generated ones: approve_pending_skill reads the candidate's namespace and promotes it to manual/<slug> (default stays auto/<slug>). Frontend: the session-menu item plus the purpose modal, wired through ChatPage to api.createSkillFromSession, with the input opted out of password-manager autofill decorations. Strings added to all twelve catalogs and the en-XA pseudolocale. Docs: the subagent and memory-skills-hooks specs are updated in the same commit.
3a81a5f to
49d0e5b
Compare
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: Security scan flagged this PR for manual review. Findings:
A maintainer will review before any automated action is taken. If cleared, remove |
|
Superseded by #5973 — same feature, rebased cleanly onto current main as a single commit with all gates green (black / flake8 / 146 pytest / i18n:check / tsc -b). Continuing there. |
Pull request was closed
Problem / Motivation
When a chat works out something reusable — a deploy runbook, a debugging path, a fixed command sequence — there is no low-friction way to keep it at that moment. The on-demand
crystallizeskill authors a skill from the session but runs inline, which derails the conversation you are in; automatic post-session generation only fires on the idle path. So a user who knows the current chat is worth keeping must either interrupt their session or wait and hope the idle pass catches it.Why it matters
Hard-won context is captured while it is fresh instead of being lost or left to a passive pass. The capture is non-blocking, so the user hands off and keeps working — removing the one real drawback of inline
crystallize. It reuses proven machinery (crystallize authoring + the pending-review/approve pipeline), so the added surface is small and low-risk.What changed (motivation → approach → change)
crystallizeskill, and reuse the existing staging → pending-review → approve pipeline. Keep user-initiated captures separate from auto-generated ones via a newmanual/namespace — auto skills have an archive-if-unused lifecycle, and a deliberate capture should not be aged out or confused with a suggestion.POST /api/skills/-/from-session: validates the caller (rejects app / internal-secret callers and incognito sessions), shapes the session transcript, injects the user's required purpose into the authoring instructions, and spawns the subagent (silent=True,approval_mode="spawn"— the spawn is authorized but the subagent's tool calls stay approval-gated); returns202immediately.MANUAL_SKILL_NAMESPACE;approve_pending_skillreads a candidate's.meta.jsonnamespace and promotes toauto/ormanual/(default staysauto/) — the rest of the pipeline is unchanged.ChatPagetoapi.createSkillFromSession; strings added to all catalogs.docs/system-specs/modules/memory-skills-hooks.mdandsubagent.mdupdated in the same change.Tests
test/test_dashboard_create_skill.py,test/test_skill_pending.py,test/test_subagent.py): the endpoint's auth/validation guards (app / internal / owner-only, missing / non-string / over-length / empty purpose, unknown session, incognito refusal, empty session), the successful background spawn (202,approval_mode="spawn") and at-capacity (429); that a candidate markednamespace: "manual"is promoted tomanual/<slug>on approval whileauto/stays the default; and that a"spawn"subagent does not inherit a trusted parent's auto policy.CreateSkillDialog.test.tsx,AssistantMessage.test.tsx): the dialog submits the trimmed purpose, disables/no-ops on empty or whitespace, submits on Enter once non-empty, and carries the autofill opt-out attributes; catalog parity holds. 147 tests (incl.catalogParity).main:flake8/isort/mypy,tsc -b,npm run i18n:check,check_brand_name,docs-lint.Manual verification
Ran the dashboard against an isolated dev home: opened the chat header's session menu → Create skill, entered a purpose, confirmed the request returns immediately (chat stays interactive), the candidate appears in Skills → Pending review, and approving it promotes the skill under
manual/<slug>. Confirmed the Create button stays disabled until a non-empty purpose is entered.Screenshots / video
User-visible surface is the Create skill item in the chat header's session menu and its purpose modal.
Related Issues
Closes #3181
Checklist
memory-skills-hooks+subagentspecs, same commit)PR revision fixes
Successive fork review rounds (Opus 4.8 / GPT 5.6 / SAST / First Principles) and a rebase onto the latest
mainwere all addressed. Complete list of fixes applied, in their final state:backend-security-controls) — every denial branch ofPOST /api/skills/-/from-sessionemits a SELoutcome="denied"event.approval_mode="spawn": the spawn is authorized, but its tool calls stay approval-gated and it does not inherit any auto policy (trusted parent, YOLO, global config, or theauto_approve_subagent_toolshook). Suppressing that auto grant emits a singlesubagent.deny_auto_inheritSEL denial event naming the overridden source (backend-security-controls)._session_key_is_restricted(effective_session_key(slot)), so a channel-born session whose linked Slack thread is set to!incognito(restriction on theSessionMap, not the slot'smemory_mode) is refused.400 purpose_required); the unreachable "infer from transcript" branch was removed.raw-html-format— false positive on the subagent prompt string (never rendered as HTML); suppressed with a justified# nosemgrep.max-two-buttons-per-row— "Create skill" left the assistant-message footer entirely and is now an item in the chat header's session actions menu, positioned directly under Switch to Autopilot (via a genericmodeSlotsslot on the sharedSessionActionsMenu;CreateSkillPopoverdeleted).subagent.py) — spawn mode also suppresses a hookTOOL_AUTO_APPROVE, not just the inherited policy, so an untrusted-transcript authoring subagent cannot auto-execute a tool matchingauto_approve_tools; its calls route to interactive approval or deny-by-default.CreateSkillDialog.tsx) — the purpose modal awaits submission and clears/closes only on success; a capacity/network failure keeps the dialog open with the typed purpose intact.create_skill.py) — the subagent now runs under the target session'sagent/appidentities (not the default agent), so an app- or named-agent session's scoped governance profile still bounds it.create_skill.py) — the incognito/restriction check is re-run immediately before spawn (the transcript read is awaited), so a session flipped to!incognitomid-read is refused rather than authored.create_skill.py) — the restriction re-check now runs as the last step before the synchronous spawn, after the agent-cache warmawait, so no awaited step can invalidate it.subagent.py) — when a spawn-mode subagent suppresses a hookTOOL_AUTO_APPROVE, it now emits asubagent.hook_auto_approve_suppressedSEL denial event (backend-security-controls).create_skill.py) — the target agent is validated off the event loop (asyncio.to_thread(_validate_agent, …)), thenspawn()skips its own on-looplist_agents()scan via_agent_prevalidated; an unresolvable agent is refused (400 agent_unavailable) rather than downgraded to the default.CreateSkillDialog.tsx) — Escape / outside-click is ignored while a submission is in flight, so the typed purpose can't be discarded before the request resolves.main(i18n catalogs union-merged,en-XAregenerated).