Skip to Content
DesignPrompt SuggestionPrompt Suggestion (NES) Design

Prompt Suggestion (NES) Design

Sagt voraus, was der Benutzer nach einer KI-Antwort natürlicherweise als Nächstes eingeben würde, und zeigt dies als Geistertext im Eingabeprompt an.

Implementierungsstatus: prompt-suggestion-implementation.md. Spekulationsengine: speculation-design.md.

Überblick

Eine Prompt Suggestion (Next-Step Suggestion / NES) ist eine kurze Vorhersage (2–12 Wörter) des nächsten Benutzereingabe, die durch einen LLM-Aufruf nach jeder KI-Antwort generiert wird. Sie erscheint als Geistertext im Eingabefeld. Der Benutzer kann sie mit Tab/Enter/rechtem Pfeil annehmen oder durch Tippen verwerfen.

Architektur

┌─────────────────────────────────────────────────────────────┐ │ AppContainer (CLI) │ │ │ │ Übergang Responding → Idle │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Guard-Bedingungen (11 Kategorien) │ │ │ │ settings, interactive, sdk, plan mode, dialogs, │ │ │ │ elicitation, API error │ │ │ └────────────────────┬────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ generatePromptSuggestion() │ │ │ │ │ │ │ │ ┌─── CacheSafeParams verfügbar? ────┐ │ │ │ │ │ │ │ │ │ │ ▼ JA NEIN ▼ │ │ │ │ runForkedQuery() BaseLlmClient.generateJson() │ │ │ │ (cache-bewusst) (eigenständiger Fallback) │ │ │ │ │ │ │ │ ──── SUGGESTION_PROMPT ──── │ │ │ │ ──── 12 Filterregeln ──────── │ │ │ │ ──── getFilterReason() ──── │ │ │ └────────────────────┬────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ FollowupController (framework-agnostisch) │ │ │ │ 300ms Verzögerung → als Geistertext anzeigen │ │ │ │ │ │ │ │ Tab → annehmen (Eingabe füllen) │ │ │ │ Enter → annehmen + absenden │ │ │ │ Rechts → annehmen (Eingabe füllen) │ │ │ │ Tippen → verwerfen + Spekulation abbrechen │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Telemetrie (PromptSuggestionEvent) │ │ │ │ outcome, accept_method, timing, similarity, │ │ │ │ keystroke, focus, suppression reason, prompt_id │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘

Suggestion-Generierung

LLM-Prompt

[SUGGESTION MODE: Suggest what the user might naturally type next.] FIRST: Read the LAST FEW LINES of the assistant's most recent message — that's where next-step hints, tips, and actionable suggestions usually appear. Then check the user's recent messages and original request. Your job is to predict what THEY would type - not what you think they should do. THE TEST: Would they think "I was just about to type that"? PRIORITY: If the assistant's last message contains a tip or hint like "Tip: type X to ..." or "type X to ...", extract X as the suggestion. These are explicit next-step hints. EXAMPLES: Assistant says "Tip: type post comments to publish findings" → "post comments" Assistant says "type /review to start" → "/review" User asked "fix the bug and run tests", bug is fixed → "run the tests" After code written → "try it out" Task complete, obvious follow-up → "commit this" or "push it" Format: 2-12 words, match the user's style. Or nothing. Reply with ONLY the suggestion, no quotes or explanation.

Filterregeln (12)

RegelBeispiel blockiert
done”done”
meta_text”nothing found”, “no suggestion”, “silence”
meta_wrapped”(silence)”, “[no suggestion]“
error_message”api error: 500”
prefixed_label”Suggestion: commit”
too_few_words”hmm” (erlaubt aber “yes”, “commit”, “push”)
too_many_words> 12 Wörter
too_long>= 100 Zeichen
multiple_sentences”Run tests. Then commit.”
has_formattingZeilenumbrüche, Markdown fett
evaluative”looks good”, “thanks” (mit \b Wortgrenzen)
ai_voice”Let me…”, “I’ll…”, “Here’s…”

Guard-Bedingungen

AppContainer useEffect (13 Prüfungen im Code):

GuardPrüfung
Settings-ToggleenableFollowupSuggestions
Nicht-interaktivconfig.isInteractive()
SDK-Modus!config.getSdkMode()
Streaming-ÜbergangResponding → Idle (2 Prüfungen)
API-Fehler (History)historyManager.history[last]?.type !== 'error'
API-Fehler (ausstehend)!pendingGeminiHistoryItems.some(type === 'error')
Bestätigungsdialogeshell + allgemein + Schleifenerkennung (3 Prüfungen)
BerechtigungsdialogisPermissionsDialogOpen
Abfrage (Elicitation)settingInputRequests.length === 0
Plan-ModusApprovalMode.PLAN

Innerhalb von generatePromptSuggestion():

GuardPrüfung
Frühes GesprächmodelTurns < 2

Separate Feature-Flags (nicht im Guard-Block):

FlagSteuerung
enableCacheSharingOb Forked-Query oder Fallback auf generateJson verwendet wird
enableSpeculationOb Spekulation beim Anzeigen der Suggestion gestartet wird

Zustandsverwaltung

FollowupState

interface FollowupState { suggestion: string | null; isVisible: boolean; shownAt: number; // timestamp for telemetry }

FollowupController

Framework-agnostischer Controller, der von CLI (Ink) und WebUI (React) gemeinsam genutzt wird:

  • setSuggestion(text) — 300ms verzögerte Anzeige, null löscht sofort
  • accept(method) — löscht den Zustand, löst onAccept via Microtask aus, 100ms Debounce-Sperre
  • dismiss() — löscht den Zustand, protokolliert ignored Telemetrie
  • clear() — hartes Zurücksetzen aller Zustände + Timer
  • Object.freeze(INITIAL_FOLLOWUP_STATE) verhindert versehentliche Mutationen

Tastaturinteraktion

TasteCLIWebUI
TabEingabe füllen (kein Absenden)Eingabe füllen (kein Absenden)
EnterFüllen + AbsendenFüllen + Absenden (explicitText-Parameter)
Rechter PfeilEingabe füllen (kein Absenden)Eingabe füllen (kein Absenden)
TippenVerwerfen + Spekulation abbrechenVerwerfen
EinfügenVerwerfen + Spekulation abbrechenVerwerfen

Tastenzuordnungshinweis

Der Tab-Handler verwendet explizit key.name === 'tab' (nicht den ACCEPT_SUGGESTION-Matching), da ACCEPT_SUGGESTION auch auf Enter passt, das aber zum SUBMIT-Handler durchgereicht werden muss.

Telemetrie

PromptSuggestionEvent

FeldTypBeschreibung
outcomeaccepted/ignored/suppressedEndgültiges Ergebnis
prompt_idstringStandard: ‘user_intent’
accept_methodtab/enter/rightWie der Benutzer angenommen hat
time_to_accept_msnumberZeit von Anzeige bis Annahme
time_to_ignore_msnumberZeit von Anzeige bis Verwerfen
time_to_first_keystroke_msnumberZeit bis zum ersten Tastendruck während Anzeige
suggestion_lengthnumberZeichenanzahl
similaritynumber1.0 bei Annahme, 0.0 bei Ignorieren
was_focused_when_shownbooleanTerminal hatte Fokus
reasonstringBei suppressed: Name der Filterregel

SpeculationEvent

FeldTypBeschreibung
outcomeaccepted/aborted/failedErgebnis der Spekulation
turns_usednumberAPI-Roundtrips
files_writtennumberDateien im Overlay
tool_use_countnumberAusgeführte Tools
duration_msnumberWanduhrzeit
boundary_typestringWas die Spekulation gestoppt hat
had_pipelined_suggestionbooleanNächste Suggestion generiert?

Feature-Flags und Einstellungen

EinstellungTypStandardBeschreibung
enableFollowupSuggestionsbooleantrueHauptschalter für Prompt Suggestions
enableCacheSharingbooleantrueCache-bewusste geforkte Abfragen verwenden
enableSpeculationbooleanfalsePredictive Execution Engine
fastModel (oberste Ebene)string""Modell für alle Hintergrundaufgaben (leer = Hauptmodell). Setzen via /model --fast

Interne Prompt-ID-Filterung

Hintergrundoperationen verwenden dedizierte Prompt-IDs (INTERNAL_PROMPT_IDS in utils/internalPromptIds.ts), um zu verhindern, dass ihr API-Traffic und ihre Tool-Aufrufe in der benutzersichtbaren UI erscheinen:

Prompt-IDVerwendet von
prompt_suggestionSuggestion-Generierung
forked_queryCache-bewusste Forked-Queries
speculationSpekulationsengine

Angewandte Filterung:

  • loggingContentGenerator — überspringt logApiRequest und OpenAI-Interaktionsprotokollierung für interne IDs
  • logApiResponse / logApiError — überspringt chatRecordingService.recordUiTelemetryEvent
  • logToolCall — überspringt chatRecordingService.recordUiTelemetryEvent
  • uiTelemetryService.addEventnicht gefiltert (stellt /stats Token-Tracking sicher)

Denkmodus

Denken/Reasoning ist explizit deaktiviert (thinkingConfig: { includeThoughts: false }) für alle Hintergrundaufgaben-Pfade:

  • Forked-Query-Pfad (createForkedChat) — überschreibt thinkingConfig im geklonten generationConfig, deckt sowohl Suggestion-Generierung als auch Spekulation ab
  • BaseLlm-Fallback-Pfad (generateViaBaseLlm) — anfragebezogene Konfiguration überschreibt die Denk-Einstellungen des Basis-Content-Generators

Dies ist sicher, weil:

  • Das Cache-Präfix durch systemInstruction + Tools + history bestimmt wird, nicht durch thinkingConfig — Cache-Treffer bleiben unbeeinflusst
  • Alle Backends (Gemini, OpenAI-kompatibel, Anthropic) behandeln includeThoughts: false durch Weglassen des Denk-Felds — keine API-Fehler bei Modellen ohne Denkunterstützung
  • Suggestion-Generierung und Spekulation profitieren nicht von Reasoning-Tokens
Last updated on