Use Ask Me as a display and persistence adapter. Question generation and reasoning stay in the calling workflow.
Overview: three paths, take only one
| Path | When to take | How the answer comes back |
|---|---|---|
Standard path ask |
Default. Runs in the background, blocks until the user submits | Process exits, harness pushes a completion notification, read the stdout file |
Failure recovery resume |
Only for the cases listed in the "Failure Quick Reference" table | status: "submitted" contains the complete answer |
Manual fallback create |
ask last resort when it truly cannot be used |
After the user replies "submitted", run resume |
The cost of choosing the wrong path is marked with 🔴 later in the text. Read the overview first before going further; do not jump into the details of one path and get stuck there.
🔴 There is only one red-line list, in the "Anti-patterns" table at the end—each row has three columns: what not to do, why, and what to do instead. Check it every time before sending a command or replying. (The 🔴 in the middle sections are in-place reminders, not another list.)
Deciding whether to use the UI
🔴 CHECKPOINT: when the current batch of questions includes at least two independent questions the user can answer right now, you must use the UI. Questions whose answers depend on a previous question are written as conditional questions in the same ask (showWhen, see step 3); only questions that require the Agent to reason again after getting the answer may be left to the next ask. When there is only one question, ask directly in the conversation; the only exception is correcting or supplementing an already-submitted answer—even if there is only one question, start another ask, because verbal confirmation in the conversation is not persisted and later steps cannot read it.
For grill-me、grill-with-docs, brainstorming, or other confirmation and question-collection workflows, whenever there are more than two questions at once, always use the UI.
Fallback order
Step down in order until you reach the first level that works:
- Ask Me(this skill)—default.
AskUserQuestion—use this instead when this skill cannot be used. There are two such cases: the harness has no tool for executing commands (Bash or equivalent), or the service/browser truly cannot start.- Numbered text questions in the conversation—only allowed when
AskUserQuestionis also unavailable.
When stepping down, state the real reason; do not write "no Bash tool" as "the service won't start"—the former cannot be fixed by rewriting in any language, while the latter is an environment issue. Use ToolSearch to confirm the tool truly does not exist before drawing a conclusion.
Standard path:ask eight steps
-
Resolve the directory containing this
SKILL.mdtoASK_ME_SKILL_DIR。 -
Before creating the JSON, read two files:references/questionset.schema.json is the field list itself (JSON Schema 2020-12, each field has a Chinese description),references/schema.md covers what the schema cannot express—cross-field hard rules, actual page behavior, and why it is written this way.references/example-question-set.json is a complete starter template you can copy directly and change the fields (one single-choice, one multiple-choice, and one free-text question each, with recommended answers and context fields). The structure of the answer is shown in references/answerset.schema.json。
-
Create a QuestionSet JSON file. Each
askis an independent question, and the id is generated automatically by the CLI. Legacy fields (sessionId/roundNumber/basedOnRound/sessionTitle/sessionSummary/sessionBackground) have all been removed; if you write them, you will get an error pointing the way. Go through the following seven items one by one:- Required fields: Two fields are required for every question:
type(single/multiple/text,No default value; omitting it raises an error) andtext(the question body; put both the question itself and the description here). - Optional questions: Questions that may be left blank must explicitly set
"required": false——requireddefaults totrue; if omitted, the question is required, the page shows a "Required" badge, and blank submissions are blocked.Don't write "(optional)" intextinstead of using this field: the text and the badge won't match, and users will be forced to make something up. Questions like "Anything else to add?", "Other notes", or "Optional parameters" should always usetype: "text"+"required": false。 - Context fields: Include them as well so users can tell what is being asked without looking at the conversation:
projectName/title/summary/background("Current context" in the left column, with a separate button in the upper right to enlarge it) /purpose; for questions that need background explained separately, write a question-levelbackground。 - Options: Always JSON objects
{"text":"…","description":"…","recommended":true,"reason":"…"},Strings are not accepted;reasoncan only be written on the options ofrecommended: true. If an option'sidis omitted, it is generated automatically in the order within that questionoption-1/option-2…;Options that need to be referenced byshowWhenmust have"id"written explicitly; don't guess the auto-generated numbers. Choice questions have no "Other" option. Answers outside the preset options are captured by each question's supplementary note, so list only the genuinely mutually exclusive options and don't add "Other" just to fill space. Choice questions require at least 2 options; the script will error out directly with第 X 题是选择题,至少要有两个选项and exit — turn a confirmation question with only one candidate intotype: "text", or just ask it in the conversation. - Conditional questions (branching): Write questions with dependencies as conditional questions within the same request — write the options of
q1as{"id":"yes","text":"要,分阶段放量"}, write"showWhen": {"questionId":"q1","optionIds":["yes"]}for this question, so it appears only whenq1has that option selected; it appears or disappears immediately after the user selects.optionIdsis filled with theoption'sid, not the option text — filling in text raises第 q2 题的 showWhen 引用了 q1 里不存在的选项:…。showWhencan only point to earlier questions; build the branch tree with chained dependencies; when a text question is the trigger source, useanswered/contains/matches. Hidden questions do not validate required fields and are not included inanswers.json(ids are listed inhiddenQuestionIds). See the full rules in references/schema.md 's "Conditional questions (branching)" —showWhen's three cross-field hard rules (point to earlier questions, matching method fits the question type, option ids actually exist) cannot be caught by the schema; only runtime reports errors. - Rich text and file links:
background, the question'stextandbackgroundsupport Markdown (GFM: headings, bold, inline code, code blocks, lists, links, quotes, tables) + Mermaid; options'descriptiononly supports Markdown.When you mention a file, write it as a link:[审计报告](.scratch/model-audit.html)(the path is relative to the workspace; absolute paths andfile:///…are also accepted), and clicking it opens the file in a new tab in the default browser, with a realfile://. Only documents and images are allowed (.html/.md/.txt/.log/.json/.yaml/.csv/.pdf/ images); things like.sh、.appwill be blocked. Just writing a path is not clickable; the user has to open it themselves. For details, see "Local file links" in references/schema.md . - Diagrams and nested fences: for things that are hard to explain in words, such as flows, sequences, and architectures, write
```mermaidcode blocks, which render as diagrams that follow the theme. Tables, diagrams, and code blocks can all be clicked to enlarge and zoom/drag on the page. If you mark the language on a code block's fence (```ts、```sql), it will be highlighted according to the language.Nesting rules: when you need to show a piece that itself contains围栏的 markdown(或代码里含), the outer fence must use four backticks````—three backticks will be terminated by the first提前闭合,后面的内容漏成正文,页面上出现裸character of the inner layer.
- Required fields: Two fields are required for every question:
-
Run the command in the background, redirect only stdout to a file, and leave stderr on the console:
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs ask --input <questions.json> > <run>.stdout.jsonStart it using the harness's background mechanism (in Claude Code, the Bash tool's
run_in_background: true). stdout is the result JSON and must go to a file; stderr is the human-readable progress lines (URL,ask-me-id), and leaving them on the console lets the user see them right away. -
**Repeat the page URL on the first line of the reply.** stderr prints
Ask Me ready at <url>andask-me-id: <id>immediately at startup; useTaskOutputto read the background task output once and put these two lines into the reply. The browser is opened automatically by the script, but it may not pop up (no GUI, default browser not configured, window blocked); showing the URL lets the user open it themselves. Reading once is enough; if you can't read it, end this round as usual—do notsleepretry. -
🛑 STOP: after outputting the URL, end this round immediately; there is nothing to wait for.
askhas no timeout and will block until the user submits; after the user submits, the process exits and the harness proactively pushes the task-completion notification to you, which is the wake-up signal. -
After receiving the completion notification, read the full question and answer in
<run>.stdout.json, then continue the original workflow. The standardaskautomatically cleans up the question directory after writing to stdout; after reading, delete only this time's created<questions.json>and<run>.stdout.json(do not clean up other request files). When usingcreate, immediately runnode <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs complete --id <askId>(custom data directories with--data-dir)。completefirst read the answer, then delete only the specified question directory and its index entry, and then output the answer that was read; if reading the answer fails, keep the files and recover according to the troubleshooting guide. -
If you need more independent questions, create another QuestionSet JSON and call
askagain—eachaskis naturally independent and does not interfere with the others.
Service and browser lifecycle
-
Run logs are in
$TEMP/ask-me.log: one JSON per line (time / pid / event / askId), rotated at 10MB, keeping at most 3 backups (.1.2.3). Check it first when troubleshooting service start/stop, submission, or wake-up failures;<dataRoot>/server.logOnly the stderr crash stack of the persistent serve process. -
The URL format is
http://127.0.0.1:<port>/<askId>, without/ask/;askIdit is a random 12-character hexadecimal string, not a standard full UUID. Eachaskopens the browser: after submission, first saveanswers.json, then write the complete question, answer and URL to stdout, and finally exit the command; the page closes 3 seconds after submission. The standardaskimmediately callscompleteto clean up after stdout is written; if usingcreate, the answer is kept until the Agent finishes reading and executescomplete。 -
The user's half-filled answer is written to
<ask 目录>/draft.jsonon every change (saved 400 milliseconds after stopping, and sent once more when the tab is closed). Refreshing, closing and reopening the page, or restarting the service all reconnect; after successful submission this draft is deleted.Don't treatdraft.jsonas the answer, it is a work in progress and does not pass validation; the official answer is only inanswers.json。 -
The persistent service does not need manual cleanup; it manages its own start and stop—for exit rules see the previous item and the "form stuck for a long time with no answer" row in the "Troubleshooting" table.
-
Use
--no-openonly when the browser opening is separately managed externally. Use--port <number>。
Troubleshooting: what to do when something happens
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs resume --id <askId>
🔴 Only the situations listed in the table below require resume. The normal path is always to read <run>.stdout.json, do not treat resume as a routine action.
askId from the ask-me-id: <id> marker in the task output (stderr); there is also a line ask-me-submitted: <id>, which is a backup signal that submission is complete.resume returns status: "submitted" , it contains the complete answer.
| Trigger condition | First-line fix | Fallback if still failing |
|---|---|---|
The task exits immediately, and there is not even a ask-me-id marker in the task output |
The QuestionSet JSON is invalid, and the question was never created at all: read the error in the task output (all problems listed at once), fix the JSON and rerun ask |
🔴 Do not run resume—there is no data to recover; --id without resume will only fetch old questions from other tasks |
The background task was killed, crashed, or exited with a non-zero code (the task output has ask-me-id) |
After taking askId , run resume |
Run --id without resume, filter by title / summary to find candidates about the current task, and take the latest one of submittedAt the latest one |
<run>.stdout.json is empty or not valid JSON |
Same as above, use resume to fetch the result again |
When the data really does not exist, truthfully state that the answer has been lost, and use the same batch of questions to re- ask |
| Switched to a new Agent session and cannot get the original background task | Take the id from the most recent ask-me-id marker in the conversation, then resume |
If the marker is also lost, run --id without resume to list candidates |
resume returns {"status":"waiting"} |
The user has not submitted yet: do nothing, end this round immediately, and continue waiting for the harness's completion notification | 🔴 Do not reopen the form, do not resend the question, do not urge the user, do not sleep |
| The form has been up for a long time with no answer, worried that the service is occupying resources | Do nothing: as long as questions are waiting to be answered, the service should keep running; after all questions are answered, exit automatically if there is no access for 24 hours, and exit immediately if the data directory is deleted.complete / cancel stop it on the spot when the last question is finished |
🔴 Do not manually kill the process; to change the idle duration, use ASK_ME_IDLE_TIMEOUT_MINUTES |
| Local browser cannot connect to the temporary service | First check the <dataRoot>/server.log last line to confirm why the service exited (received SIGTERM / idle for N minutes / data directory is gone / crash stack), then go through the create detached flow (see "Manual fallback and recovery") |
If it still cannot connect, fall back to AskUserQuestion |
| The harness has no Bash or equivalent execution tool | Use ToolSearch to confirm the tool really does not exist, fall back to AskUserQuestion |
AskUserQuestion Only when that also fails, use the numbered text questions in the conversation |
| Failed to wake up the adapter | Preserve the answer and return to the manual "submitted" flow | The answer has been written to disk; use resume to retrieve it again |
Manual fallback and recovery
🔴 CHECKPOINT: This is the last resort; only use it when ask really cannot be used—it is the only path that requires the user to reply "submitted".ask Run in the backgroundnot countedcannot be used; that is the standard path, just wait for the notification as described above.
Use the detached flow when the following occurs: the foreground tool call cannot stay active, the local browser cannot connect to the temporary service, or a direct-connection question that was interrupted needs to be recovered:
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs create --input <questions.json>
Parse the returned JSON. In the conversation, provide both its URL and a visible marker:
ask-me-id: <askId>
Tell the user to reply only "submitted" after submitting the form.create The command will start or reuse a detached localhost service and return immediately.
When the user says "submitted", "done submitting", or "finished answering":
-
From the most recent
ask-me-idmarker in the conversation, recoveraskId。 -
Run:
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs resume --id <askId> -
If the result is
submitted, first read the full question and answer; immediately run thecomplete --id <askId>cleanup from step 4, then delete the temporary input/output files created this time, and then continue the original workflow. -
Clean up immediately after reading the answer each time, even if the next step is to ask another question:
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs complete --id <askId> -
If more independent questions are needed, prefer returning to the foreground
askagain when direct-connection waiting is still impossiblecreate。
If that marker cannot be obtained from the conversation, run without --id the resume. When there are multiple candidates, it returns status: "ambiguous" and a candidates list (including askId / title / summary / workspace / submittedAt). The data directory defaults to .ask-meunder the current working directory, so all candidates come from this workspace. Filter in this order:
- First filter by
titleandsummary, keeping only candidates that are about the current task. - If only one remains, use it; if multiple remain, take the
submittedAtlatest one. - When none of them match the current task, list the
askId、title、submittedAtof each candidate for the user to choose; do not pick a recent one and make do.
🔴 Repeated "submitted" messages must not create duplicate questions. Only after successfully reading a submitted before another question can be asked.
Optional proactive wake-up
Ask Me supports optional wake-up metadata for Claude Code and Codex App Server. Treat it as an enhancement, not a requirement.
- Enable automatic wake-up only after the user agrees.
- Claude Code requires a recorded session id.
- Codex requires a host-provided thread id. Never guess the Codex thread id.
- If the adapter fails, preserve the answer and fall back to the manual "submitted" flow.
- Direct
askmode never triggers the wake-up adapter, because the waiting process itself is the return channel.
Anti-patterns: never do these
Check against this list every time before sending a command or replying.
| 🔴 Don't | Why | Instead |
|---|---|---|
Hand-roll backgrounding with something like nohup ... & handwritten backend like |
The harness never receives the exit event, and the whole chain falls back to manual follow-up | Use the harness's own background mechanism |
| Don't redirect stdout and parse the result directly from the task output | The two output streams get mixed together, andJSON.parse will inevitably fail |
> <run>.stdout.json, keep stderr on the console |
| Redirect stderr into the file as well | The URL and ask-me-id get buried in the file, the user can't see them, and if the page doesn't pop up they can't open it themselves |
Redirect only stdout |
sleep poll, nag the user, ask the user to reply "submitted" |
The background task's completion notification is the wake-up signal; just wait for it | End the current turn immediately after starting it |
Look for the answer in the task output, or hand-assemble the .ask-me/ path |
The task output only contains stderr progress lines; the answer isn't there | Read the answer from <run>.stdout.json or run resume; use the task output only to get the URL,ask-me-id , and errors |
| Write bare file paths in the body | They can't be clicked on the page; the user has to go find them in a file manager | Write them as Markdown links [报告](.scratch/report.html), which open in a new tab when clicked |
| Add an "Other" option to a multiple-choice question | Answers outside the presets are carried by each question's supplementary note | List only the truly mutually exclusive options |
| Write a multiple-choice question with only one option | The script hard-rejects it, and the whole batch of questions can't even establish a session | Add a second genuinely mutually exclusive option, or change it to type: "text" |
Write "(optional)" in text without writing "required": false |
required defaults to true, so the page still shows a "required" badge and blocks submission when left empty; the copy and the validation don't match |
For optional questions, explicitly write "required": false |
Omit type, or write options as strings |
Both are hard-rejected, and the whole batch of questions can't even establish a session | type One of the three must be written; options must always be written as JSON objects with text JSON object |
Use question-level recommendedOptionIds to mark a recommendation |
This field is no longer recognized, and the script will error | Put the recommendation in the option:"recommended": true with "reason" |
Let showWhen point to a question that comes later |
Order is dependency order; backward references will be hard-rejected | put the triggering question earlier |
| splitting into multiple asks just because "the question has dependencies" | each time you have to reopen the browser, and the Agent has to wake up again | within the same ask, use showWhen to branch |
| overwriting a submitted question or answer | answers.json is immutable after submission |
for corrections and additions, start another ask |
| describing "no Bash tool" as "the service won't start" | misattributed; the user will go fix a nonexistent environment problem | state clearly whether it's a missing tool or a service failure |
| guessing the Codex thread id | a wrong guess sends the wake-up to a different session | the thread id can only be provided by the host; if unavailable, use the manual flow |
Common commands
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs ask --input <questions.json> # 标准路径
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs create --input <questions.json> # 手动回退
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs resume --id <askId> # 故障恢复
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs status --id <askId> # 查提问状态
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs serve # 常驻服务(ask/create 自动管理,一般不单跑)
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs complete --id <askId> # 正常结束提问
node <ASK_ME_SKILL_DIR>/scripts/ask-me.mjs cancel --id <askId> # 作废提问(问题问错了、任务取消)
node <ASK_ME_SKILL_DIR>/scripts/self-test.mjs # 自检,改完 skill 或排查环境时跑
Development: TS source and compiled output
- To change code, only edit
scripts/**/*.mts. The same-named.mjsis the tsc build output, committed to the repo together (~/.agents's installed copy runs.mjsdirectly, with no build step); hand-editing.mjswill be overwritten on the next compile. - Compiling is type checking (strict):
npm --prefix <ASK_ME_SKILL_DIR> run build(first time runnpm --prefix <ASK_ME_SKILL_DIR> install); to check without modifying, userun typecheck. The pre-commit hook automatically compiles, stages the output, and runs the self-tests. - Runtime requires Node ≥ 18 (
fetch/AbortSignal.timeout); package.json'senginesalready declares this. - Browser assets
assets/app/*.jsstay as .js (shared by Node and the browser, no build), with the type contract in the same directory.d.ts—if you change the export signature of.js, update.d.ts。