Skip to content

feat: authorize keyword triggering instead of forcing it (addresses #88, #89) - #93

Merged
QuintinShaw merged 3 commits into
mainfrom
feat/authorize-keyword-trigger
Jul 18, 2026
Merged

feat: authorize keyword triggering instead of forcing it (addresses #88, #89)#93
QuintinShaw merged 3 commits into
mainfrom
feat/authorize-keyword-trigger

Conversation

@QuintinShaw

@QuintinShaw QuintinShaw commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Reframes keyword/effort triggering from force to authorize — the trigger arms the workflow tool and lets the model decide, instead of rewriting the message into "You MUST / the ONLY acceptable action / Do NOT answer". Addresses #88 (over-triggering) and #89 (triggered runs going idle), and cuts self-priming (contributes to #65).

What it does

  • Authorize, not force. buildArmedWorkflowPrompt leads with the decision boundary (a question / trivial task / just talk → answer directly; a real decomposable request → call workflow) and states the true opt-in reason per path (keyword vs standing-effort — no longer falsely claiming "you typed the trigger word" on the effort path). No MUST/ONLY/Do-NOT-answer.
  • Pi behavior is weird when workflow execution is triggered via the editor component keyword. #89 fixed at the root. A shared BACKGROUND_DELIVERY_REASSURANCE clause on all arm paths tells the model a background run legitimately ends the turn and its result auto-delivers back — "expected, not a stall" — and to use background:false only when the user is waiting inline. The idle was the model being told to emit one bare background call with no signal that turn-end is normal; this closes it.
  • How-to moved to the tool description (a static, cacheable part of the tool definition), removed from the armed message — so the mechanics are available on every path that calls the tool, including natural-language opt-ins ("fan this out") that don't trip the literal keyword, instead of only on keyword-armed turns.
  • Always-on prompt = one gate line (task-shape positives + explicit-opt-in gate + "even a beneficial task, don't call it — offer it with a rough cost").
  • /workflows run stays a force (explicit command → distinct buildForcedWorkflowPrompt, no question-escape) but without the old hostile language.
  • Kept: keyword triggering default-ON (under authorize semantics its worst case is a single armed-turn directive), the fix: bound workflow keyword matching #79 boundary regex, rainbow highlight, Backspace-to-disarm.

Honest sizing (a move, not a saving)

  • Always-on prompt: ~6500 → ~766 B (now just the gate line).
  • Tool definition: ~2529 → ~8770 B (how-to moved in — a cacheable, once-per-definition cost, replacing ~6.1 KB that was re-injected on every armed turn).
  • Per-armed-turn message: ~6765 → ~905 B.

Trimming the how-to text itself is separate work (#65 / contract concision), not this change — no token saving on the definition is claimed.

Tests

Trigger regression corpus (negatives: URLs, camelCase, hyphens, filenames, /workflows list; positives: run a workflow to…, workflow:, a CJK phrasing) at the lexical + directive layers; how-to-lives-in-description assertions; #89 reassurance present on all paths; decision-first ordering; per-path truthful reason; /workflows run forces without hostile language. 929 tests pass; tsc + biome clean.

Addresses #88 and #89; reduces self-priming toward #65.

Reframes keyword/effort triggering from 'force a workflow' to 'arm the tool and
let the model decide', addressing #88 (over-triggering), #89 (triggered runs
going idle instead of staying interactive), and cutting the always-on prompt
(part of #65).

- buildForcedWorkflowPrompt -> buildArmedWorkflowPrompt: the trigger now injects
  an authorize directive ('typing the word is explicit opt-in; if it's a request
  call workflow, if it's a question just answer -- the opt-in does not force a
  workflow') instead of 'You MUST / the ONLY acceptable action / Do NOT answer'.
  A lexical false-positive is no longer amplified into a forced wrong action.
- #89 falls out of de-forcing: the idle was a downstream effect of the model
  being told to emit nothing but one bare background workflow call (which ends
  the turn). Handled in a normal turn, Pi stays interactive and the model can
  fold results inline. No change to the tool's background machinery.
- Always-on prompt shrunk ~91% (6500 -> ~600 bytes): the ~20 'For workflow,'
  how-to lines moved off the always-on promptGuidelines into
  workflowHowToGuidelines(), injected into the message only on an armed turn; the
  always-on guideline is now a single gate line. Cuts self-priming and the
  always-on token cost, and stops churning the prompt-prefix cache.
- byEffort path de-forced too, with an explicit conversational-turn escape.
- Kept: keywordTriggerEnabled default ON (under authorize semantics the worst
  case is just an armed-turn directive), the #79 boundary regex, rainbow
  highlight, Backspace-to-disarm. README updated (force -> arm).

914 tests; always-on prompt budget ratcheted 6500 -> 650.
- Move the how-to mechanics from the per-armed-turn message into the
  workflow tool's static description, so the model has the manual on every
  arming path AND on off-keyword natural-language opt-ins, cacheably, not as
  per-turn priming (P2). Always-on promptGuidelines stays the single gate line.
- buildArmedWorkflowPrompt now leads with the decision boundary, states the
  truthful opt-in reason per path (keyword vs standing-effort), and carries the
  #89 background/deliver-back reassurance so an ending turn reads as expected,
  not a stall (P1, P3).
- /workflows run uses a distinct forcing directive (no question-escape) while
  avoiding the old MUST/ONLY language (P5).
- Gate line gains task-shape positives, balancing the strong negative (P4).
- Tests: trigger regression corpus (negatives/positives), gate-line R3, how-to
  now in tool description, armed-directive content, /workflows run forcing;
  honest prompt-budget ratchets (always-on ~766B, tool def grows to ~8.8KB as a
  MOVE, armed message drops ~6.8KB -> ~0.9KB; how-to trimming is #65, separate).
…ool default-active

The how-to now lives in the tool description, so a bare natural-language opt-in
('fan this out') sees the mechanics only if the host keeps the workflow tool in
its default active set. The arming paths add it on arm; document the dependency.
@QuintinShaw
QuintinShaw merged commit db23a3c into main Jul 18, 2026
1 check passed
@QuintinShaw
QuintinShaw deleted the feat/authorize-keyword-trigger branch July 18, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant