feat(tags): derive skill + agent tags from conversation analysis #2

Merged
gabrielvidal merged 1 commits from conv-tags into main 2026-08-09 23:01:19 +02:00
Owner

Summary

The tag registry only knew where work happened (project / service /
stack). This adds the other axis — what it was done with — as two new
derived kinds, skill:<name> and agent:<name>, mined from the conversations
themselves: the Skill(…) calls a session made and the agent types it spawned
(or was launched as). Nothing new is authored: like every other tag, these exist
because something in the homelab already refers to them, and the only editable
part stays colour / glyph / description.

They're linked to conversations in both directions — a conversation's detail
header wears the skills and agents it used, and each tag's card counts the
conversations that used it, with a last-used timestamp. Skills and agents also
become composer chips, so a new or resumed session can be told to reach for
one.

Key changes

  • backend/main.py — conversation cards, /api/conversation-summary and
    /api/conversation now carry skillsUsed and agentsUsed.
    _skills_by_path() joins the indexer's per-transcript skill column back onto
    the summaries (a stored summary has no skills key — the indexer pops it into
    its own column); _agents_by_conversation() folds the existing _agent_runs()
    list into a conversation → agent-types map. Both memoized on the store version.
  • backend/schemas.py + frontend/openapi.json + frontend/src/generated/
    the two new optional string lists on ConversationSummary.
  • frontend/src/lib/tags.tsTagKind gains skill/agent; useTags() folds
    in the skills and agents catalogs (name, description, sourceKind, path) and
    counts one conversation per session that used them; TagUsage gains agents
    (agent definitions a project ships, mirroring the existing skills);
    new tagHref() resolves a tag's own page.
  • frontend/src/business/conversations/components/ConvMetaBits.tsx — new
    SkillTags/AgentTags pills (amber wrench / emerald bot), capped at 5 with an
    expanding +N, rendered on the conversation detail header only.
  • frontend/src/business/skills/pages/Skills.tsx — accepts ?skill=<name>,
    narrowing the catalog to one card with a "show all skills" chip, so a skill
    pill has somewhere specific to land.
  • frontend/src/lib/richComposer.tsx + lib/shortcuts.tsxtoolMentions() /
    toolTags() add a chip per skill and per agent to both composers
    (recently-used first, reachable via #); selecting one appends a
    Use the following in this task: block to the prompt.
  • frontend/src/business/tags/pages/Tags.tsx, CLAUDE.md, mock/seed.ts
    kind filters, usage stat, docs and mock data for the new kinds.

Key decisions

  • Agents come from _agent_runs(), not from a second pass over Task calls.
    That list already resolves both origins correctly — a subagent run keys to its
    parent conversation via the child's agent-*.meta.json, and a cron/manual
    agent session to the conversation it is — so a card can never disagree with
    the agents page. It also meant no PARSER_VERSION bump and no re-parse.
  • A subagent's skills count as its parent's. A child transcript is never
    listed on its own, so attributing its Skill(…) calls only to itself would
    lose them entirely.
  • One count per conversation, not per call. A tag list answers "how many
    sessions used this"; the Skills/Agents pages already own the per-invocation
    arithmetic.
  • Population = everything on disk + the built-ins that actually ran, straight
    off the two catalogs (asked and confirmed). A built-in like Explore is a tag
    with exists: false and no file to open — the same treatment a dangling
    project tag gets.
  • Detail only, not the list cards (asked and confirmed). These are long tails
    — a busy session touches a dozen skills — and the list cards stay about
    location.
  • Skill/agent tag descriptions stay out of the Work in the following homelab location(s): block (asked and confirmed). A skill isn't a location; picking
    one writes its own bullet instead, which is the difference between a session
    invoking open-pr and one hand-rolling a PR body.
  • A skill pill links to /skills?skill=<name> rather than a new route: a skill's
    real "detail" is its SKILL.md in the editor, and the catalog page already
    links there.

Changelog

  • Added: conversations are now tagged with the skills they invoked and the agents they spawned, shown as pills on the conversation detail
  • Added: Settings → Tags lists Skill and Agent tags alongside projects, services and stacks — each with how many conversations used it and when it last ran, and the same colour/glyph/description editing
  • Added: skills and agents are pickable in the prompt composer (type #), adding a line that tells the session to use them
  • Added: the Skills page can be deep-linked to a single skill (/skills?skill=<name>)

Test notes

  • Ran the branch's backend in a throwaway container against a copy of the live
    DB and the real transcripts: /api/conversations, /api/conversation-summary
    and /api/conversation all return the new fields; spot-checked a conversation
    with 7 skills + 1 agent against its transcript, and confirmed conversations
    whose skills are invoked through shell aliases (not the Skill tool) correctly
    report none.
  • Frontend: npx tsc --noEmit clean, production build clean, then served the
    built dist behind a proxy to that backend and captured the conversation
    detail, both new Tags filters, and the composer's # menu (below).

Screenshots

conv-tool-tags_20260809-225714.jpeg
tags-page-tools-skills_20260809-225736.jpeg
tags-page-tools-agents_20260809-225736.jpeg
composer-skill-mention-filtered_20260809-225850.jpeg

## Summary The tag registry only knew **where** work happened (`project` / `service` / `stack`). This adds the other axis — **what it was done with** — as two new derived kinds, `skill:<name>` and `agent:<name>`, mined from the conversations themselves: the `Skill(…)` calls a session made and the agent types it spawned (or was launched as). Nothing new is authored: like every other tag, these exist because something in the homelab already refers to them, and the only editable part stays colour / glyph / description. They're linked to conversations in both directions — a conversation's detail header wears the skills and agents it used, and each tag's card counts the conversations that used it, with a last-used timestamp. Skills and agents also become composer chips, so a new or resumed session can be *told* to reach for one. ## Key changes - `backend/main.py` — conversation cards, `/api/conversation-summary` and `/api/conversation` now carry `skillsUsed` and `agentsUsed`. `_skills_by_path()` joins the indexer's per-transcript skill column back onto the summaries (a stored summary has no `skills` key — the indexer pops it into its own column); `_agents_by_conversation()` folds the existing `_agent_runs()` list into a conversation → agent-types map. Both memoized on the store version. - `backend/schemas.py` + `frontend/openapi.json` + `frontend/src/generated/` — the two new optional string lists on `ConversationSummary`. - `frontend/src/lib/tags.ts` — `TagKind` gains `skill`/`agent`; `useTags()` folds in the skills and agents catalogs (name, description, `sourceKind`, path) and counts one conversation per session that used them; `TagUsage` gains `agents` (agent definitions a project ships, mirroring the existing `skills`); new `tagHref()` resolves a tag's own page. - `frontend/src/business/conversations/components/ConvMetaBits.tsx` — new `SkillTags`/`AgentTags` pills (amber wrench / emerald bot), capped at 5 with an expanding `+N`, rendered on the conversation **detail** header only. - `frontend/src/business/skills/pages/Skills.tsx` — accepts `?skill=<name>`, narrowing the catalog to one card with a "show all skills" chip, so a skill pill has somewhere specific to land. - `frontend/src/lib/richComposer.tsx` + `lib/shortcuts.tsx` — `toolMentions()` / `toolTags()` add a chip per skill and per agent to both composers (recently-used first, reachable via `#`); selecting one appends a `Use the following in this task:` block to the prompt. - `frontend/src/business/tags/pages/Tags.tsx`, `CLAUDE.md`, `mock/seed.ts` — kind filters, usage stat, docs and mock data for the new kinds. ## Key decisions - **Agents come from `_agent_runs()`, not from a second pass over `Task` calls.** That list already resolves both origins correctly — a subagent run keys to its *parent* conversation via the child's `agent-*.meta.json`, and a cron/manual agent session to the conversation it *is* — so a card can never disagree with the agents page. It also meant no `PARSER_VERSION` bump and no re-parse. - **A subagent's skills count as its parent's.** A child transcript is never listed on its own, so attributing its `Skill(…)` calls only to itself would lose them entirely. - **One count per conversation, not per call.** A tag list answers "how many sessions used this"; the Skills/Agents pages already own the per-invocation arithmetic. - **Population = everything on disk + the built-ins that actually ran**, straight off the two catalogs (asked and confirmed). A built-in like `Explore` is a tag with `exists: false` and no file to open — the same treatment a dangling project tag gets. - **Detail only, not the list cards** (asked and confirmed). These are long tails — a busy session touches a dozen skills — and the list cards stay about location. - **Skill/agent tag descriptions stay out of the `Work in the following homelab location(s):` block** (asked and confirmed). A skill isn't a location; picking one writes its own bullet instead, which is the difference between a session invoking `open-pr` and one hand-rolling a PR body. - A skill pill links to `/skills?skill=<name>` rather than a new route: a skill's real "detail" is its `SKILL.md` in the editor, and the catalog page already links there. ## Changelog - Added: conversations are now tagged with the skills they invoked and the agents they spawned, shown as pills on the conversation detail - Added: Settings → Tags lists `Skill` and `Agent` tags alongside projects, services and stacks — each with how many conversations used it and when it last ran, and the same colour/glyph/description editing - Added: skills and agents are pickable in the prompt composer (type `#`), adding a line that tells the session to use them - Added: the Skills page can be deep-linked to a single skill (`/skills?skill=<name>`) ## Test notes - Ran the branch's backend in a throwaway container against a copy of the live DB and the real transcripts: `/api/conversations`, `/api/conversation-summary` and `/api/conversation` all return the new fields; spot-checked a conversation with 7 skills + 1 agent against its transcript, and confirmed conversations whose skills are invoked through shell aliases (not the `Skill` tool) correctly report none. - Frontend: `npx tsc --noEmit` clean, production build clean, then served the built `dist` behind a proxy to that backend and captured the conversation detail, both new Tags filters, and the composer's `#` menu (below). ## Screenshots ![conv-tool-tags_20260809-225714.jpeg](https://git.gabvdl.xyz/attachments/f5d1e056-bdcc-46c5-8862-46c3961cadc0) ![tags-page-tools-skills_20260809-225736.jpeg](https://git.gabvdl.xyz/attachments/7e3795b3-ea04-4b4b-98d6-a780c7168e7c) ![tags-page-tools-agents_20260809-225736.jpeg](https://git.gabvdl.xyz/attachments/fb6ae173-6501-473f-8371-54888004cfb9) ![composer-skill-mention-filtered_20260809-225850.jpeg](https://git.gabvdl.xyz/attachments/8df3495d-84e0-4786-a80b-4c73f98811ef)
gabrielvidal self-assigned this 2026-08-09 23:00:53 +02:00
gabrielvidal added 1 commit 2026-08-09 23:00:53 +02:00
Two new tag kinds on the derived registry — skill:<name> and agent:<name> —
mined from what conversations actually did, next to the project/service/stack
axis that says where they worked.

Backend: conversation cards and the detail now carry skillsUsed (the Skill()
calls, a conversation's subagents' included — the indexer keeps that map in the
transcripts table's own column, so main._skills_by_path joins it back) and
agentsUsed (built from the same run list the agents catalog counts, so a card
can never disagree with the agent page).

Frontend: useTags() folds both catalogs in — everything on disk plus the
built-ins that actually ran — with per-tag conversation counts and last-used;
SkillTags/AgentTags render on the conversation detail (capped at 5, expanding
+N); the Skills page takes ?skill=<name> so a skill pill lands on one card.

Skills and agents are composer chips too (toolMentions/toolTags): picking one
adds a 'Use the following in this task:' bullet naming the skill/subagent —
deliberately not part of the Work-in-location block.
gabrielvidal merged commit 7e38501a4a into main 2026-08-09 23:01:19 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gabrielvidal/ai-agent#2