Skip to content

feat(skills): add a create-skill button to capture a session during chat - #5973

Open
RolianTan wants to merge 1 commit into
kirodotdev:mainfrom
RolianTan:feat/manual-create-skill-button
Open

feat(skills): add a create-skill button to capture a session during chat#5973
RolianTan wants to merge 1 commit into
kirodotdev:mainfrom
RolianTan:feat/manual-create-skill-button

Conversation

@RolianTan

@RolianTan RolianTan commented Aug 26, 2026

Copy link
Copy Markdown

Problem / Motivation

Reusable procedures emerge inside chat sessions, but there is no in-chat way to capture one as a skill. To turn "what I just did" into a reusable Kiro Crew skill today, a user has to break out of the conversation and hand-author the skill later from memory — so most of that value is simply lost when the chat moves on.

Why it matters

Letting the owner bank a skill the moment they recognize one — without interrupting the running session — is where the payoff is. It also keeps user-captured skills in their own manual/ namespace, cleanly separated from auto-generated auto/ skills, and routes them through the existing pending-review flow so nothing goes live unreviewed.

What changed (motivation → approach → change)

Goal: let the owner capture the current session as a reusable skill from the chat UI, without interrupting the session.

Approach: a session-level menu action (it captures the whole session, so it belongs in the session menu, not on one message) that fires a background authoring subagent and returns immediately, staging a candidate for review rather than writing a live skill. Because that subagent operates on an untrusted transcript, it must gain no elevated tool authority.

What was built:

  • Frontend — a Create skill item in the chat header's session menu, rendered right after "Switch to Autopilot" via the SessionActionsMenu modeSlots seam (above Move to folder / Tags). It opens a small modal (CreateSkillDialog) that collects a mandatory one-line purpose; password-manager autofill affordances are suppressed, and a failed submit preserves the typed purpose and keeps the modal open.
  • Backend — owner-only POST /api/skills/-/from-session shapes the session transcript (bounded/truncated), requires a non-empty purpose (≤ 500 chars), and spawns a fire-and-forget authoring subagent, returning 202 immediately. The subagent follows the crystallize skill in candidate mode: it stages under auto/.pending/<slug>/ tagged namespace: manual, which is promoted to a live manual/<slug> skill on approval.
  • Security — the subagent spawns with approval_mode="spawn": authorized to start (owner-triggered), but its tool calls stay approval-gated and it does not inherit any auto-approve policy (parent / YOLO / global config / auto_approve_subagent_tools hook), because the transcript is untrusted; a suppressed hook auto-approve is SEL-audited. It runs under the target session's own agent/app governance identities (validated off the event loop; the default sentinel maps to the host default; an unresolvable named agent is refused, never silently downgraded). A private/incognito session — including a linked Slack thread set to !incognito — is refused, and that check is repeated as the last step before the synchronous spawn to close the TOCTOU window. Every denial emits a SEL audit event.

Tests

  • test/test_dashboard_create_skill.py (new) — endpoint auth (app-token / internal-auth / non-owner denials), purpose + session_key validation, incognito and linked-!incognito refusal, the TOCTOU pre-spawn re-check, the default-agent sentinel path, unresolvable-agent rejection, and that the session's agent/app reach spawn.
  • test/test_subagent.pyapproval_mode="spawn" does not inherit an auto-approve policy (parent / YOLO / global config / hook) and audits the suppression.
  • test/test_skill_pending.pytest_manual_candidate_promotes_to_manual_namespace: a manual-tagged candidate promotes under manual/<slug>/.
  • website/src/test/CreateSkillDialog.test.tsx (new) — the modal: submit disabled until a purpose is entered, clears/closes only on success, and preserves the draft on a failed submit.

Catalog parity across the 12 locale catalogs + the en-XA pseudolocale is enforced by the i18n:check gate (not a per-PR test).

Manual verification

Full gate run locally on the rebased commit, all green: black --check (new files), flake8, mypy src/kiro_crew (1105 files, clean), 146 pytest passed, npm run i18n:check, and tsc -b (after npm ci). UI smoke-tested against a local gateway: the Create skill row appears in the session menu after "Switch to Autopilot", the modal enforces a non-empty purpose, and submitting stages a candidate in Skills → Pending review without blocking the chat.

Screenshots / video

skil-v2-1 skill-v2-2 skill-v2-3 skill-v2-4

Related Issues

Closes #3181

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable) — docs/system-specs/modules/subagent.md, memory-skills-hooks.md
  • No secrets, credentials, or internal references in the diff

Contribution License Agreement

N/A — the repo's template carries a pre-launch CLA placeholder (OSPO supplies the wording); nothing to sign yet.

@RolianTan
RolianTan requested a review from a team August 26, 2026 01:57
@RolianTan
RolianTan requested a review from a team as a code owner August 26, 2026 01:57
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention labels Aug 26, 2026
@RolianTan RolianTan changed the title feat(skills): create skill button during chat. feat(skills): create skill button during chat Aug 26, 2026
@dwu96

dwu96 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

👋 Hi! This PR's description is missing some required sections from our PR template. Workflow runs won't be auto-approved until the description is updated.

Missing sections:

  • ## Problem / Motivation
  • ## Why it matters
  • ## What changed

Please update your PR description to include these sections, then push or re-save the description. The workflows will be approved on the next cycle.

@RolianTan RolianTan changed the title feat(skills): create skill button during chat feat(skills): add a create-skill button to capture a session during chat Aug 26, 2026
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Aug 26, 2026
@RolianTan
RolianTan force-pushed the feat/manual-create-skill-button branch from e08099d to e31f29c Compare August 26, 2026 03:26
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 26, 2026
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — 🟡 CONCERNS

Design-level review of c997aacd20ec021c0ea441c96b370263b09f0471 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

I've verified the base-code cascade: with approval_mode="spawn" a gated tool call falls to the interactive dashboard/Slack prompt or deny-by-default, and silent=True suppresses the subagent's completion notification. That combination is the one design risk worth surfacing.

Design-Verdict: CONCERNS

Sound capture design, but the "fire-and-forget" pitch and the approval-gated security posture collide in a silently-failing background path.

Watch

  • The 202 promises "banked without interrupting the session", yet approval_mode="spawn" routes every gated tool call to the interactive callback (2h timeout) or deny-by-default, and silent=True suppresses the completion notification — a missed/timed-out approval or authoring error means the capture dies with zero feedback and no candidate ever appears in Pending review ("Completion surfaces through the existing skills.pending_changed notification" — success-only). A fallible background path needs a failure-surfacing story (e.g. a non-silent failure event or a status the returned id can be polled for; the frontend currently discards it).
  • The manual namespace is enforced only by prompt instruction: approve_pending_skill reads namespace from the LLM-authored .meta.json and silently defaults to auto when it's missing or misspelled — filing the deliberate capture into the archive-if-unused lifecycle this namespace exists to avoid.

Suggestions

  • Record the manual-capture intent server-side (e.g. keyed by subagent id, or a namespace choice at approval time) instead of laundering it through the subagent's output.

[DESIGN-REVIEWED] c997aac

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5, fork) — 🟡 CONCERNS

Premise-level review of c997aacd20ec021c0ea441c96b370263b09f0471 via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

I've verified everything against the trusted base. Key facts established: the base already ships the crystallize builtin skill whose triggers are literally "create a skill", "create a skill from this", "save this as a skill" (staging a pending candidate) — contradicting the PR's opening claim; the auto-aging lifecycle walks list_auto_skills() (auto/-prefixed only) so the manual/ namespace genuinely escapes it; infoSlots/modeSlots each have exactly one real consumer; all reused symbols (extract_turns, render_input, _validate_agent, _agent_prevalidated, _read_pending_meta) exist in the base. Final review:

First-Principles-Verdict: CONCERNS

"There is no in-chat way to capture one as a skill" is false — crystallize already triggers on "create a skill"; the honest delta is non-blocking capture plus a manual/ namespace the sibling path never gets.

What this change ships

Intent: let the owner capture the current chat session as a reviewed, reusable skill without interrupting it — an ADDITION.

  1. "Create skill" row in the chat-header session menu — overlaps crystallize, see Watch
  2. Purpose modal, mandatory one-liner, draft preserved on failure — justified
  3. Owner-only POST /api/skills/-/from-session — justified (1 consumer: client.ts)
  4. Authoring runs as a background subagent; chat never blocks — justified (the real delta)
  5. approval_mode="spawn": tools stay gated, suppressed auto grants SEL-audited — justified (untrusted-transcript boundary)
  6. New manual/ namespace exempt from auto aging — named harm, but symptom-level (see Watch)
  7. Incognito / linked-!incognito sessions refused, re-checked pre-spawn — justified (privacy boundary)
  8. Capacity-queued capture refused (503, cancelled) instead of queued — undeclared; privacy-derived, justified
  9. modeSlots generic slot prop on the shared menu — one consumer, generalized
  10. Purpose input opts out of password-manager autofill — justified

Watch

  • Framing: "there is no in-chat way to capture one as a skill" is contradicted by the base — builtin_skills/crystallize/SKILL.md:4 triggers on "create a skill, create a skill from this, save this as a skill" and stages the same pending candidate. The feature survives on its real deltas (background, non-blocking, structured purpose), but two capture spellings now coexist and have already diverged (source: crystallizeauto/, source: make-it-skillmanual/).
  • manual/ fixes "a deliberate capture ages out like an auto skill" for the button only. Grepped name: auto/<slug>: 1 unfixed sibling — crystallize's own user-requested candidate mode (crystallize/SKILL.md:87) still stages into auto/ and still ages. The general fix is the same one-line namespace: manual this PR's prompt already dictates, applied to crystallize's candidate template — in scope and smaller than the divergence it prevents.

Subtractions

  • Replace modeSlots?: React.ReactNode[] (SessionActionsMenu.tsx) with a single onCreateSkill?: () => void prop — one consumer ever passes one element (grep: ChatPage.tsx only); the description calls it a "seam" but this PR is what adds it.

[FIRST-PRINCIPLES-REVIEWED] c997aac

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed c997aacd20ec021c0ea441c96b370263b09f0471 via the fork AI-review pipeline; updated in place on each push.

Review details

I've validated the single candidate against the code.

Candidate 1 (memory/lessons/project injected into the authoring subagent): the claimed wrong outcome — private memory content ending up embedded in the staged skill — dies under falsification. The subagent runs approval_mode="spawn" with deny_auto_inherit, so every tool call (including the SKILL.md/.meta.json writes) is approval-gated and never auto-executes; the candidate then lands in Skills → Pending review and loads nothing until the owner explicitly approves it. The memory is the owner's own data handed to an authoring subagent running under the owner's authority, whose output the owner reviews before it takes effect. Reaching the harm requires an injection to succeed and the owner to approve a candidate without noticing embedded private data — outcome (c) resolves to "might," not an observable wrong outcome. The candidate's own confidence is "low." Dropped.

I also examined the deny_auto_inherit cascade in subagent.py (spawn-gate branch, the four suppressed auto sources, the TOOL_AUTO_APPROVE hook suppression and its control flow), the _session_key_is_restricted extraction, and the TOCTOU re-check / queued-capture refusal in create_skill.py. The auto-suppression branches are all guarded by not deny_auto_inherit, the hook path falls through to interactive/deny-by-default (parent_policy forced to ""), and the pre-spawn restriction re-check has no intervening await. No grounded defect in the changed lines.

No findings.

[OPUS-REVIEWED] c997aac

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — 🔴 changes requested (blocking)

Reviewed c997aacd20ec021c0ea441c96b370263b09f0471 via the fork AI-review pipeline; updated in place on each push.

BLOCKING -- src/kiro_crew/dashboard/handlers/create_skill.py:212 -- Spawn mode leaves static tool grants unattended
info = state.subagents.spawn(... approval_mode="spawn", ...)
Injected transcript -> default @kirocrew-core allowlist -> learn_add bypasses permission events -> durable lesson persists without approval.
Anchor: residual/security
Fix: Revert this spawn hunk until static allowedTools and autoApprove grants are removed.

FINDING -- src/kiro_crew/dashboard/handlers/create_skill.py:189 -- "_validate_agent, target_agent" omits slot.project, rejecting valid project-local session agents -> Fix: validate with slot.project and pass it as the spawn cwd.

FINDING -- website/src/pages/ChatPage.tsx:5386 -- direct "await api.createSkillFromSession" performs a server mutation outside the required React Query mutation path -> Fix: wrap submission in useMutation. (origin: validation)

[BLOCK-MERGE] c997aac
[GPT-REVIEWED] c997aac

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention merge conflict Branch has merge conflicts with its base — author must resolve before merge and removed readiness: checking Automated validation is still running labels Aug 26, 2026
@RolianTan
RolianTan force-pushed the feat/manual-create-skill-button branch from e31f29c to c997aac Compare August 28, 2026 02:24
@github-actions github-actions Bot removed merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: action required A blocking check or review needs attention labels Aug 28, 2026
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

UX Review (Fable 5, fork) — 🟡 CONCERNS

UX-level review of c997aacd20ec021c0ea441c96b370263b09f0471 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All evidence gathered. The success path is fully silent: the subagent spawns with silent=True (completion notification suppressed), and skills.pending_changed only invalidates queries on an already-open Skills tab — no toast, no pointer anywhere. The repo ships its own useImeGuard hook that the new dialog's bare Enter handler bypasses, and five locale catalogs received English values for the new keys.

UX-Verdict: CONCERNS

Success is completely silent: the modal just closes, the subagent is silent=True, and nothing ever tells the user the skill landed in Skills → Pending review.

Watch

  • No closure on the happy path. handleCreateSkill resolves the 202 with no success UI, the spawn is silent: True, and skills.pending_changed only does queryClient.invalidateQueries — so after "Create" the user gets zero acknowledgment that authoring started, finished, or where to approve it. High: every use, every user; likely retries (duplicate subagents) or "it's broken." Fix: on success, show a confirmation linking to Skills → Pending review.
  • Mixed-language dialog in de/es/fr/it/pt. Those catalogs got English values ("create_skill": "Create skill", "create_skill_placeholder": "Skill purpose", "create_skill_submit": "Create", create_skill_failed_error) while create_skill_hint is translated — a half-English modal for 5 of 12 locales, every open. Fix: translate the four keys.
  • Bare Enter submits mid-IME composition. onKeyDown checks e.key === 'Enter' without the repo's useImeGuard/isComposing check, so a ja/ko/zh user confirming composition fires the request with a half-typed purpose and the dialog closes — the subagent is already running with the wrong intent, unrecoverable. Fix: guard with useImeGuard like sibling composers.

Suggestions

  • Hide or disable the "Create skill" menu item when the slot is incognito — today the user types a purpose only to get the incognito_session rejection the frontend could have known upfront.
  • Label: "Create skill from chat" — in the session menu, bare "Create skill" doesn't say it captures this session rather than authoring a blank one.

[UX-REVIEWED] c997aac

@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 28, 2026
@bolichen97
bolichen97 enabled auto-merge August 30, 2026 00:00
@github-actions github-actions Bot added the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Aug 30, 2026
@bolichen97 bolichen97 added needs-pr-triage PR scanner: awaiting automated triage needs-human-security-review Security scan flagged — needs manual review and removed needs-pr-triage PR scanner: awaiting automated triage labels Sep 2, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator

🤖 Kiro Crew [operator: bolichen97#66809557]: Security scan flagged this PR for manual review.

Findings:

  • code / destructive-op patterntest/test_subagent.py:241: yield LLMEvent(kind=EVENT_PERMISSION_REQUEST, title="rm -rf /", request_id=7) matched the rm -rf / denylist rule. This appears to be a synthetic test fixture (a permission-request title string, not an executed command), but the automated content scan cannot distinguish that from a real destructive op, so it is routed to a human.

A maintainer will review before any automated action is taken. (Separately, the GPT 5.6 blocking finding on create_skill.py:212 proposes reverting the approval_mode="spawn" hunk — a design decision the author/maintainer should make.)

@bolichen97

Copy link
Copy Markdown
Collaborator

Open PR relationship audit

This is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion.

Relationship findings

  • This PR is PARTIALLY_COVERED with PR #392. Coverage is explicitly incomplete; this finding is not a completion or closure claim. Recommended action for PR #5973: REBASE. The goal is partly served on main by crystallize, so the PR's framing is wrong, but its real deltas (button + endpoint, non-blocking background authoring, manual/ namespace, approval-gated untrusted-transcript spawn) are not implemented anywhere in current code. Not grounds for closure. Files: src/kiro_crew/builtin_skills/crystallize/SKILL.md.

No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit.

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.
@bolichen97
bolichen97 force-pushed the feat/manual-create-skill-button branch from c997aac to 25a1193 Compare September 8, 2026 21:29
@bolichen97

Copy link
Copy Markdown
Collaborator

Rebased onto main 6fa5519c by a maintainer as part of the 2026-09-08 open-PR audit. The branch was 1633 commits behind.

Conflicts, all mechanical relocations with no behaviour change:

Gates run locally: black/isort/flake8 on changed files, pytest on the touched test files (150 passed), tsc --noEmit, vitest on the touched components (45 passed).

Please review the resolution. A maintainer push makes the maintainer the last pusher, so a second approver is needed. Reply if anything looks wrong.

@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running merge conflict Branch has merge conflicts with its base — author must resolve before merge labels Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor) needs-human-security-review Security scan flagged — needs manual review readiness: action required A blocking check or review needs attention

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(skills) Create Skill Button: one-click footer button to manually create skill without interrupt session.

3 participants