Skip to content

[GUI] Give the agent's own questions (ask_user_question) an answerer surface #4244

Open

Description

@devplex69

Problem

When the agent runs, the Harness can emit a user-questions/request host event — the agent asking the user a question (the ask_user_question capability that Qoder / OpenAI Codex desktop / Claude desktop all surface). In the current host bridge that event is auto-dismissed with outcome: 'next', so the agent's question is never shown to the user and never answered. The agent effectively cannot ask the user anything mid-run.

The approval path right next to it already does exactly the round-trip a question needs:

approval/request → pending map → GET /api/agent/pending → POST /api/chat/approval → approve() → gateway.answerEvent() → $events/result

The question path is simply not wired to it.

The above is from static reading of apps/host/server/agent/. I have not run a live Harness to observe the event fire — unconfirmed static analysis.

Proposed behavior (opt-in)

Add an opt-in "ask user questions" answerer that reuses the existing approval round-trip:

  • A new tools-settings toggle askUserQuestions (default false). When off, behavior is byte-identical to today — questions are still auto-dismissed with outcome:'next'.
  • When on, user-questions/request is parked in a pendingQuestions map and exposed through the existing GET /api/agent/pending (as a questions array, present only when non-empty so the default response is unchanged).
  • A new POST /api/chat/answer endpoint sends the user's answers back through gateway.answerEvent() as a structured $events/result.
  • The tool-ask-user plugin is added to the preset only when the toggle is on.

The default path stays byte-identical and the feature is entirely opt-in, matching CONTRIBUTING's "new features opt-in" rule.

Open question for maintainers

Two details are unconfirmed because they depend on the pinned DeepSeek Harness wire contract, which is not in this repo:

  1. The exact $events/result payload the Harness expects for a user-questions/request answer (I currently send { answers: [{ question, answer }] }).
  2. The exact plugin id for the ask-user tool (I use @deepseek-ai/dsh-tool-ask-user).

I have isolated both in a single adapter file (apps/host/server/agent/user-questions.mjs) so each is a one-line change once confirmed. Is there a canonical shape you would prefer, or a Harness reference I should match?

Validation

  • Passed: npm test in apps/host — 5 new harness-free pure tests (parse shapes, answer wire, answerEvent backward-compat, preset opt-in / default-drift, settings toggle persistence) plus the existing preset drift test.
  • Not run: end-to-end against a live Harness (no STRATA_HARNESS_DIR available here), because the wire contract above is unconfirmed.

I plan to open this as a draft PR so it is not treated as review-ready until the wire shape is confirmed. Scope agreement first, per CONTRIBUTING — happy to reshape or drop this if it overlaps something already in flight.

Environment

  • Strata version: 0.2.7 (apps/host package version at main commit 5e8a4cc; analysed from source — no installed build on this machine).
  • Graphics card: NVIDIA GeForce RTX 4090, 24 GB VRAM.
  • RAM: 128 GB.
  • Operating system: Windows 11 x64 (build 26200).

Activity

  1. github-actions commented on Oct 26, 2026

    @github-actions

    Thanks for the issue. To keep the tracker readable, every item needs an area prefix in the title and the parts of the issue form filled in. This one is missing:

    • Add the Strata version (Settings > About, or strata --version).
    • Add your graphics card with its VRAM, your RAM and your operating system.

    Edit the issue (title and description) and this note goes away on its own. If it is still missing parts in 5 days it will be closed, which is not a rejection: fix it and comment /reopen.

    Details: CONTRIBUTING.md

  2. github-actions commented on Oct 28, 2026

    @github-actions

    Thanks, this follows the format now.

  3. xvcezrqpbxtpzblrt commented on Oct 31, 2026

    @xvcezrqpbxtpzblrt
    Contributor

    Second reader, reading tag v0.2.8 plus the harness that ships with 0.2.8. Three facts that change how much of this is already paid for, and one gap the plan above does not cover.

    1. In 0.2.8 the model is never offered the tool at all. Besides the auto-decline you found (apps/host/server/agent/agent-bridge.mjs:277-280, comment: "The app has no surface for the agent's own questions yet. Declaring that nobody can answer makes the tool return an error the model reads and carries on from"), the tool is also absent from the list a chat turn offers — the repo pins that in its own test: apps/host/tests/agent-tools.test.mjs:140-145, title "…no scheduler, no unanswerable question", asserting !offered.includes('ask_user_question'). So a bridge surface plus an opt-in toggle is not enough on its own: the preset has to put the tool back into the offered set. The gate is not the blocker — apps/host/server/agent/plugins/strata-gate.mjs:40-47 already classifies ask_user_question as read-only, so it would never raise an approval card for it.

    2. The runtime shipped with 0.2.8 already carries single-select, multi-select, free text, and a question that stays open. From harness\node_modules\@deepseek-ai\dsh-api-session-controller\lib\typert.host.js:167-196 (the userQuestions projection):

    • a question is { id, question, detail?, header? }, where detail is "supporting detail rendered with the question but kept out of option labels";
    • choices are options: [{ label, description? }], description being "Optional extra context rendered by capable UIs";
    • multiSelect?: boolean — "Whether more than one option may be selected. Defaults to single-select.";
    • an answer is { id, selected: string[], custom?: string }, custom being "Optional free-text 'Other' answer", and selected may accompany custom for a multi-select question;
    • active are the "calls that can still take an answer, in ask order" and settled the answered batches, so the question survives and can be answered late.

    That means the multi-select, type-your-own-answer, and "wait until you answer" halves need nothing upstream — they only need a panel. The repo already documents the runtime's own panel: apps/host/docs/dsh-recon.md:84 (dsh-client-ui-user-questions — "Agent question panel beside the input: options, text, skip, batch submit") and :142 (its copy: "Ask question", "Waiting until you answer", "Type your answer", "Take time", "Skip", "Dismiss all questions"). What the desktop app can answer today is boolean only: the approval card at apps/host/src/screens/Chat.tsx:722-727 posts resolveTool(messageId, t.id, true|false).

    3. The gap your plan does not cover: a recommended choice and the reason for it. There is no recommended flag and no per-option reason field anywhere in that contract. question.detail and options[].description can carry the words, but nothing marks which option is the suggested one, and the only presentation intent is intent: { kind: 'plan-review', approve: <label> }. Two ways to close it:

    • no runtime change: the app renders detail and each description (today the desktop card renders neither), and the ask-tool prompt asks the model to write the recommendation into detail in one line ("Recommended: — because …"), which the panel then shows as its own row;
    • the durable version: add recommended: <option label> next to multiSelect, for the reason the contract already states about intent.approve — "Named rather than positional so no UI infers the verdict from option order."

    4. What this costs a user today, with the tool unavailable. The model asks for its decision inside the prose answer, so the person reads the options and types one out by hand — and if the answer is still streaming the desktop composer cannot even send a message (#4783: Enter bounces back with "The assistant is still answering"). A one-click option panel removes the typing entirely; for the small local models many people run, the typed reply is also where the answer goes wrong, because it re-states the option in the user's own words.

    My reading is static (tag v0.2.8 and the harness installed with it); I have not run a live Harness to capture a user-questions/request frame, so the wire description above comes from those type definitions, not from an observed payload.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions