From ff4760bfbd75c2657a41046eea70be64b2368782 Mon Sep 17 00:00:00 2001 From: Siddharth Balyan <52913345+alt-glitch@users.noreply.github.com> Date: Thu, 1 Oct 2026 02:26:02 +0530 Subject: [PATCH] The first task after setup connects the picks and finishes something small within minutes (#129646) * feat(onboarding): the first task chat delivers a small result in minutes Live runs on an RTX Spark showed the task chat surveying the whole machine, asking a plan question, and installing the picked plugins 11.5 minutes after setup. Its suggestions ignored the picks and the scan, and the handoff text said "Call me Sid." and "Start from this; look at my computer again...". - New optional skill first-task. start_chat appends it to the task chat's first message, after the ask and the "What setup learned" block, with a JSON block of the connector and plugin ids picked in setup. No system prompt changes. The history projection cuts the message at its marker, so no surface shows the skill. Its rules: connect and install the picks first, work visible in 1 minute and a finished result in 5, big asks cut to a first slice, three options only for a vague ask, machine setup as a short card interview. - setup_choose records the picked ids and the fork pick. The fork card is filled when shown: two first tasks from the picks and the scan (a daily brief from the picked apps, "Install a few apps for this Spark", a small page), then "I have something in mind", "Help me set up this ", "Let's figure it out together". The machine-first "Something else" step, "automate" and "skip" rows are gone; "figure" hands off at once. - The handoff template is the ask only; no "Call me", no install or plan paragraphs. start_chat drops the "Start from this" line. - initiate-setup SKILL.md keeps only the setup beats (20.5 KB -> 10 KB). - Desktop shows the "What setup learned" block folded under the first message; the model still gets the full text. * fix(setup): one unexpected shutdown reads as singular --- agent/first_task_prompt.py | 29 +++ agent/initiate_setup_prompt.py | 5 + .../assistant-ui/thread/setup-learned.tsx | 23 +++ .../assistant-ui/thread/user-message.tsx | 19 +- .../productivity/first-task/SKILL.md | 114 +++++++++++ .../productivity/initiate-setup/MAPPING.md | 18 +- .../productivity/initiate-setup/SKILL.md | 177 +++++------------- .../initiate-setup/scripts/host_facts.py | 107 ++++++----- .../initiate-setup/templates/handoff.md | 57 +----- tools/setup_choose_tool.py | 47 +++-- tui_gateway/session_history.py | 4 +- tui_gateway/start_chat.py | 21 ++- .../onboarding-recommendations.md | 2 +- .../docs/reference/optional-skills-catalog.md | 1 + website/docs/reference/tools-reference.md | 2 +- .../productivity/productivity-first-task.md | 131 +++++++++++++ .../productivity-initiate-setup.md | 177 +++++------------- website/sidebars.ts | 1 + 18 files changed, 549 insertions(+), 386 deletions(-) create mode 100644 agent/first_task_prompt.py create mode 100644 apps/desktop/src/components/assistant-ui/thread/setup-learned.tsx create mode 100644 optional-skills/productivity/first-task/SKILL.md create mode 100644 website/docs/user-guide/skills/optional/productivity/productivity-first-task.md diff --git a/agent/first_task_prompt.py b/agent/first_task_prompt.py new file mode 100644 index 0000000000..d0086d52cd --- /dev/null +++ b/agent/first_task_prompt.py @@ -0,0 +1,29 @@ +"""The ``first-task`` skill for the task chat a setup handoff opens. + +``start_chat`` sends it in that chat's first user message, the way ``/initiate-setup`` carries its skill, so +no system prompt changes. It rides at the tail, after the visible ask and the "What setup learned" block: +session previews read the ask, and the history projection cuts the message at ``MARKER`` so no surface +shows the skill. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +from hermes_constants import get_optional_skills_dir + +MARKER = "\n\n[/first-task]\n\n" + + +def first_task_tail(connect: list, install: list) -> str: + """``connect``: connector ids to show on one connect card; ``install``: catalog plugin ids to install.""" + skill_dir = get_optional_skills_dir(Path(__file__).resolve().parent.parent / "optional-skills") + skill = (skill_dir / "productivity" / "first-task" / "SKILL.md").read_text(encoding="utf-8-sig").strip() + facts = json.dumps({"connect": connect, "install": install}, ensure_ascii=False) + return f"{MARKER}{skill}\n\n```json\n{facts}\n```" + + +def visible_text(text: str) -> str: + """The part of a user message a surface shows: everything before the first-task skill.""" + return text.partition(MARKER)[0] diff --git a/agent/initiate_setup_prompt.py b/agent/initiate_setup_prompt.py index aee8fc205e..d07ffdae3a 100644 --- a/agent/initiate_setup_prompt.py +++ b/agent/initiate_setup_prompt.py @@ -72,6 +72,11 @@ def collect_setup_cards() -> dict: return host_facts.setup_cards(_collect(host_facts)) +def fork_card(cards: dict) -> dict: + """The fork card as it is shown, its first rows built from the picks this setup conversation recorded.""" + return _host_facts_module(_skill_dir()).fork_card(cards) + + def build_initiate_setup_prompt(surface: str, tools, primary_profile: str, session_id: str | None = None) -> str: """``session_id``: the desktop session the turn runs in; its ``setup_choose`` cards read these same facts.""" from hermes_cli.anon_auth import free_tier_route diff --git a/apps/desktop/src/components/assistant-ui/thread/setup-learned.tsx b/apps/desktop/src/components/assistant-ui/thread/setup-learned.tsx new file mode 100644 index 0000000000..0c1ef69e02 --- /dev/null +++ b/apps/desktop/src/components/assistant-ui/thread/setup-learned.tsx @@ -0,0 +1,23 @@ +import type { FC } from 'react' + +// tui_gateway/start_chat.py::_setup_learned: the block a setup handoff appends +// under the user's ask. The model reads all of it; the bubble shows only the ask. +const SETUP_LEARNED_MARKER = '\n\nWhat setup learned about me:\n' + +/** `[ask, block]`: the block is null for every message that has none. */ +export function splitSetupLearned(text: string): [string, null | string] { + const index = text.indexOf(SETUP_LEARNED_MARKER) + + return index < 0 ? [text, null] : [text.slice(0, index), text.slice(index + 2)] +} + +export const SetupLearnedNote: FC<{ text: string }> = ({ text }) => { + const [title = '', ...lines] = text.split('\n') + + return ( +
+ {title.replace(/:$/, '')} +
{lines.join('\n')}
+
+ ) +} diff --git a/apps/desktop/src/components/assistant-ui/thread/user-message.tsx b/apps/desktop/src/components/assistant-ui/thread/user-message.tsx index 231b1739e7..f86e873f60 100644 --- a/apps/desktop/src/components/assistant-ui/thread/user-message.tsx +++ b/apps/desktop/src/components/assistant-ui/thread/user-message.tsx @@ -9,6 +9,7 @@ import { PROCESS_NOTIFICATION_RE } from '@/components/assistant-ui/thread/content' import { ReactionBadge, ReactionPicker } from '@/components/assistant-ui/thread/message-reactions' +import { SetupLearnedNote, splitSetupLearned } from '@/components/assistant-ui/thread/setup-learned' import { BackgroundResult } from '@/components/assistant-ui/thread/system-message' import { threadUserOrdinal } from '@/components/assistant-ui/thread/thread-message-index' import { MessageTimelineTimestamp } from '@/components/assistant-ui/thread/timeline-timestamp' @@ -249,7 +250,8 @@ export const UserMessage: FC<{ const copy = t.assistant.thread const messageId = useAuiState(s => s.message.id) const content = useAuiState(s => s.message.content) - const messageText = messageContentText(content) + const fullText = messageContentText(content) + const [messageText, setupLearned] = splitSetupLearned(fullText) const threadRunning = useAuiState(s => s.thread.isRunning) const latestUserId = useAuiState(s => { @@ -411,11 +413,14 @@ export const UserMessage: FC<{ // it. No negative margin: -mt-* pulls the row up into the sticky box, // where the sticky-prompt clip hides its top even at rest. Image refs // render as thumbnails, file refs as chips; no border. - attachmentRefs.length > 0 && !chipOnlyTurn ? ( -
- -
- ) : null + <> + {attachmentRefs.length > 0 && !chipOnlyTurn && ( +
+ +
+ )} + {setupLearned && } + } messageId={messageId} > @@ -527,7 +532,7 @@ export const UserMessage: FC<{ event.stopPropagation() triggerHaptic('selection') onRequestRestoreConfirm?.(messageId, { - text: messageText, + text: fullText, userOrdinal: runtimeUserOrdinal }) }} diff --git a/optional-skills/productivity/first-task/SKILL.md b/optional-skills/productivity/first-task/SKILL.md new file mode 100644 index 0000000000..1c9afc5a7e --- /dev/null +++ b/optional-skills/productivity/first-task/SKILL.md @@ -0,0 +1,114 @@ +--- +name: first-task +description: "Run the first task chat that setup hands off." +version: 0.1.0 +author: "Siddharth Balyan (alt-glitch) + Hermes Agent" +license: MIT +platforms: [linux, macos, windows] +metadata: + hermes: + tags: [onboarding, first-run, desktop, handoff] + category: productivity + related_skills: [initiate-setup] +--- + +# First Task Skill + +Runs the first chat after setup, the one `start_chat` opened. The top of this message is the user's ask, then "What setup learned about me" (their picks and the scan), then these rules and a JSON block. The goal: they see work within one minute and hold a finished, useful result within five. + +## When to Use + +- This message carries the skill under a handoff from setup. +- Until the first result lands. After that it is a normal chat. + +## Prerequisites + +The task chat's own tools: `manage_connections`, `manage_catalog`, `clarify`, `terminal`, the file tools, the browser, `tool_search`, `skill_view`, and `desktop_preview` in the desktop app. Use only tools that are in your tool list. + +## How to Run + +The JSON block at the end holds `connect`, the connector ids of the apps they picked in setup, and `install`, the catalog plugin ids they picked or their first task brings. The ids are exact: skip search and status checks. + +The "What setup learned" lines are your recon. Do not survey the machine first: no system scan, no update search, no inventory of installed tools. Look at one thing only when the next step needs it. + +## Quick Reference + +| Step | What | Call | +|---|---|---| +| 1 | One line on what you start with | none | +| 2 | Connect the picked apps | `manage_connections` connect, every id in `connect`, once | +| 3 | Install the picked plugins | `manage_catalog` install, every id in `install`, once | +| 4 | Specific ask: start it. Vague ask: three options | `clarify`, three choices | +| 5 | The first slice, finished in five minutes | the task's own tools | +| 6 | Show the result, offer the next step | `clarify`: Looks right, Change something, Take it further | + +``` +manage_connections {"action":"connect","connectors":["", ...]} +manage_catalog {"action":"install","items":[{"kind":"plugin","id":""}, ...],"reason":""} +clarify {"questions":[{"question":"","choices":["