Skip to content

Commit 9d47f8c

Browse files
committed
merge: resolve orchestrator conflicts with main
Keep <subagent> block architecture, incorporate main's new "User questions (MANDATORY)" instructions into subagent prompts for both start_unit/continue_unit and start_units cases. https://claude.ai/code/session_01WtQrEmTYmoUheaP49HAei7
2 parents f731948 + 0161557 commit 9d47f8c

File tree

9 files changed

+24765
-2904
lines changed

9 files changed

+24765
-2904
lines changed

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
},
77
"metadata": {
88
"description": "H·AI·K·U — universal lifecycle orchestration with hat-based workflows, completion criteria, and automatic context preservation.",
9-
"version": "1.101.7"
9+
"version": "1.102.2"
1010
},
1111
"plugins": [
1212
{

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.102.2] - 2026-04-15
9+
10+
### Changed
11+
- Agents now receive improved context when formulating questions during orchestration, resulting in clearer agent-to-agent interactions.
12+
13+
## [1.102.1] - 2026-04-15
14+
15+
### Fixed
16+
17+
- Stages with `review: auto` gates now automatically advance without waiting for user input.
18+
19+
## [1.102.0] - 2026-04-15
20+
21+
### Added
22+
- Automatic repair of migrated intents with incomplete prior stages, allowing safe resumption of work without data loss
23+
824
## [1.101.7] - 2026-04-15
925

1026
### Fixed

packages/haiku/src/hooks/subagent-context.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,28 @@ export async function generateSubagentContext(
461461
out("- `\ud83d\uded1 Blocked:` When genuinely stuck after rescue attempts")
462462
out("- `\u2753 Decision needed:` Use `AskUserQuestion` for user input")
463463
out("")
464+
out("**MANDATORY question tool rules:**")
465+
out("")
466+
out(
467+
"- **Always use tools for questions** \u2014 NEVER output option lists, numbered choices, or " +
468+
'"A) ... B) ... C) ..." as plain conversation text.',
469+
)
470+
out(
471+
"- **Always provide `options[]`** \u2014 when using `AskUserQuestion`, populate the `options` array " +
472+
"with concrete choices derived from your domain knowledge. Do not force the user to type freeform " +
473+
'answers when you already know the alternatives. Include "Other (let me specify)" when the ' +
474+
"list may not be exhaustive.",
475+
)
476+
out(
477+
"- **One question per tool call** \u2014 if you have multiple independent decisions, make separate " +
478+
"`AskUserQuestion` calls for each. Do NOT combine unrelated questions into one message.",
479+
)
480+
out(
481+
"- **Use `ask_user_visual_question`** for visual artifacts (wireframes, designs, mockups), " +
482+
"rich markdown context, or when presenting multiple related questions together " +
483+
"(use its `questions[]` array with per-question `options`).",
484+
)
485+
out("")
464486
out("Output status messages directly - users see them in real-time.")
465487
out(
466488
`Document blockers in \`.haiku/intents/${intentSlug}/state/blockers.md\` for persistence (unit-scoped).`,

packages/haiku/src/orchestrator.ts

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,10 +1406,11 @@ export function runNext(slug: string): OrchestratorAction {
14061406
}
14071407
}
14081408

1409-
// All units valid — open review gate before advancing to execute.
1410-
// The review UI blocks until the user approves the specs.
1411-
// This is handled by the handleOrchestratorTool wrapper which
1412-
// detects gate_review and calls _openReviewAndWait.
1409+
// All units valid — either auto-advance or open review gate before execution.
1410+
//
1411+
// For stages with review: auto (and non-discrete mode), skip the gate
1412+
// entirely and advance directly to execution. This is critical for
1413+
// autonomous workflows where the user should not be interrupted.
14131414
//
14141415
// For the first stage of a fresh intent (not yet reviewed), this gate
14151416
// doubles as the intent review — CC review agents have already run
@@ -1418,6 +1419,35 @@ export function runNext(slug: string): OrchestratorAction {
14181419
// with intent_review context until intent_reviewed is set to true.
14191420
const intentReviewed = (intent.intent_reviewed as boolean) || false
14201421
const isIntentReview = currentStage === studioStages[0] && !intentReviewed
1422+
const intentMode = (intent.mode as string) || "continuous"
1423+
const stageReviewType = resolveStageReview(studio, currentStage)
1424+
1425+
// Auto gates: skip review UI and advance directly to execution
1426+
if (stageReviewType === "auto" && intentMode !== "discrete") {
1427+
if (isIntentReview) {
1428+
setFrontmatterField(intentFile, "intent_reviewed", true)
1429+
gitCommitState(`haiku: intent ${slug} auto-approved`)
1430+
}
1431+
fsmAdvancePhase(slug, currentStage, "execute")
1432+
emitTelemetry("haiku.gate.auto_advanced", {
1433+
intent: slug,
1434+
stage: currentStage,
1435+
gate_context: isIntentReview ? "intent_review" : "elaborate_to_execute",
1436+
})
1437+
return {
1438+
action: isIntentReview ? "intent_approved" : "advance_phase",
1439+
intent: slug,
1440+
studio,
1441+
stage: currentStage,
1442+
from_phase: "elaborate",
1443+
to_phase: "execute",
1444+
message: isIntentReview
1445+
? `Auto-gate: intent approved — advancing to execution. Call haiku_run_next { intent: "${slug}" } immediately.`
1446+
: `Auto-gate: specs validated — advancing to execution. Call haiku_run_next { intent: "${slug}" } immediately.`,
1447+
}
1448+
}
1449+
1450+
// Non-auto gates: open review UI
14211451
return {
14221452
action: "gate_review",
14231453
intent: slug,
@@ -1611,11 +1641,12 @@ export function runNext(slug: string): OrchestratorAction {
16111641
}
16121642
}
16131643

1614-
// Stage in gate phase — always open local review UI as a preliminary gate.
1615-
// The gate type determines what options are shown:
1644+
// Stage in gate phase — determine whether to auto-advance or open review UI.
1645+
// Gate behavior:
16161646
// - Discrete intent mode: always "external" (Submit for External Review + Request Changes)
16171647
// - Continuous/hybrid intent mode: based on the stage's review field
1618-
// - auto/ask → "ask" (Approve + Request Changes)
1648+
// - auto → auto-advance without user interaction (autonomous gate)
1649+
// - ask → "ask" (Approve + Request Changes)
16191650
// - external → "external" (Submit for External Review + Request Changes)
16201651
// - [external, ask] → as-is (Approve + Submit for External Review + Request Changes)
16211652
// - await → "external" (awaits external event after submission)
@@ -1638,7 +1669,39 @@ export function runNext(slug: string): OrchestratorAction {
16381669
const intentMode = (intent.mode as string) || "continuous"
16391670
const gitAvailable = isGitRepo()
16401671

1641-
// Determine gate type for the review UI
1672+
// Auto gates: advance without user interaction.
1673+
// "auto" review type means the studio author trusts the FSM to advance
1674+
// without human approval. In continuous/hybrid mode, skip the gate UI
1675+
// entirely. Discrete mode always uses external review (PR per stage).
1676+
if (reviewType === "auto" && intentMode !== "discrete") {
1677+
emitTelemetry("haiku.gate.auto_advanced", {
1678+
intent: slug,
1679+
stage: currentStage,
1680+
gate_context: "stage_gate",
1681+
})
1682+
if (nextStage) {
1683+
fsmAdvanceStage(slug, currentStage, nextStage)
1684+
return {
1685+
action: "advance_stage",
1686+
intent: slug,
1687+
studio,
1688+
stage: currentStage,
1689+
next_stage: nextStage,
1690+
gate_outcome: "advanced",
1691+
message: `Auto-gate passed — advancing to '${nextStage}'. Call haiku_run_next { intent: "${slug}" } immediately.`,
1692+
}
1693+
}
1694+
fsmCompleteStage(slug, currentStage, "advanced")
1695+
fsmIntentComplete(slug)
1696+
return {
1697+
action: "intent_complete",
1698+
intent: slug,
1699+
studio,
1700+
message: `Auto-gate passed — all stages complete for intent '${slug}'`,
1701+
}
1702+
}
1703+
1704+
// Non-auto gates: open review UI
16421705
let effectiveGateType: string
16431706
if (!gitAvailable && reviewType.includes("external")) {
16441707
// Non-git environment: external gates have no structural signal (no branch
@@ -1652,7 +1715,7 @@ export function runNext(slug: string): OrchestratorAction {
16521715
} else if (intentMode === "discrete") {
16531716
// Pure discrete intent: always submit for external review (PR per stage)
16541717
effectiveGateType = "external"
1655-
} else if (reviewType === "auto" || reviewType === "ask") {
1718+
} else if (reviewType === "ask") {
16561719
effectiveGateType = "ask"
16571720
} else if (reviewType === "await") {
16581721
effectiveGateType = "external"
@@ -2608,13 +2671,27 @@ function buildRunInstructions(
26082671
"When you have questions for the user, you MUST use the correct tool:\n\n" +
26092672
"| Question type | Tool | Example |\n" +
26102673
"|---|---|---|\n" +
2611-
`| Scope decisions, tradeoffs, A/B/C choices | \`AskUserQuestion\` | "Should we support X or Y?" |\n` +
2674+
`| Scope decisions, tradeoffs, A/B/C choices | \`AskUserQuestion\` with options[] | "Should we support X or Y?" |\n` +
26122675
"| Specs, comparisons, detailed options (markdown) | `ask_user_visual_question` MCP tool | Domain model review, architecture options |\n" +
2676+
"| Visual artifacts, wireframes, designs | `ask_user_visual_question` with image_paths | Side-by-side design comparison |\n" +
26132677
"| Design direction with previews | `pick_design_direction` MCP tool | Wireframe variants |\n" +
2614-
`| Simple open-ended clarification | Conversation text | "Tell me more about the use case" |\n\n` +
2678+
`| Simple open-ended clarification (no known options) | Conversation text | "Tell me more about the use case" |\n\n` +
2679+
"### ALWAYS provide pre-selected options\n\n" +
2680+
"When using `AskUserQuestion`, you MUST provide an `options` array with concrete choices the user can pick from. " +
2681+
"You already know the domain — translate your knowledge into selectable options instead of forcing the user to type freeform answers. " +
2682+
'Include an "Other (let me specify)" option when the list may not be exhaustive.\n\n' +
2683+
'**Good:** `AskUserQuestion({ question: "Which auth strategy?", options: ["OAuth 2.0 + PKCE", "Magic link (passwordless)", "SSO via SAML", "Other (let me specify)"] })`\n' +
2684+
'**Bad:** Typing "Which auth strategy should we use? We could do OAuth, magic links, or SSO..." as plain text.\n\n' +
2685+
"### One question per tool call — break up compound questions\n\n" +
2686+
"If you have multiple independent questions (e.g., auth strategy AND database choice AND caching layer), " +
2687+
"do NOT combine them into a single long message. Instead:\n" +
2688+
"- Use **separate `AskUserQuestion` calls** for each independent decision, OR\n" +
2689+
"- Use **one `ask_user_visual_question` call** with multiple entries in the `questions[]` array (each with its own options) when the decisions are related and benefit from being seen together\n\n" +
2690+
"Never dump multiple questions as numbered plain-text paragraphs.\n\n" +
26152691
`**Violation:** Outputting numbered questions, option lists, or "A) ... B) ... C) ..." as conversation text. ` +
2616-
"If you catch yourself typing options inline, STOP and use `AskUserQuestion` instead.\n\n"
2617-
: "Mode: **autonomous** — elaborate independently.\n\n"
2692+
"If you catch yourself typing options inline, STOP and use `AskUserQuestion` with an `options` array instead.\n\n"
2693+
: "Mode: **autonomous** — elaborate independently. When you DO need user input (blockers, ambiguity), " +
2694+
"use `AskUserQuestion` with pre-selected `options[]` — never plain-text option lists.\n\n"
26182695
}**Elaboration produces the PLAN, not the deliverables:**\n1. Research the problem space and write discovery artifacts to \`knowledge/\`\n2. Define units with scope, completion criteria, and dependencies — NOT the actual work product\n - A unit spec says WHAT will be produced and HOW to verify it\n - The execution phase produces the actual deliverables\n - Do NOT write full specs, schemas, or implementations during elaboration\n3. Write unit files to \`.haiku/intents/${slug}/stages/${stage}/units/\`\n4. Call \`haiku_run_next { intent: "${slug}" }\` — the orchestrator validates and opens the review gate\n\n**Unit file naming convention (REQUIRED):**\nFiles MUST be named \`unit-NN-slug.md\` where:\n- \`NN\` is a zero-padded sequence number (01, 02, 03...)\n- \`slug\` is a kebab-case descriptor (e.g., \`user-auth\`, \`data-model\`)\n- Example: \`unit-01-data-model.md\`, \`unit-02-api-endpoints.md\`\n\nFiles that don't match this pattern will not appear in the review UI and will block advancement.`,
26192696
)
26202697

@@ -2805,6 +2882,12 @@ function buildRunInstructions(
28052882
"6. Track outputs in unit frontmatter `outputs:` field",
28062883
"7. Use `ask_user_visual_question` for visual artifacts — do NOT open files in a browser",
28072884
`8. If outputs from a previous stage are missing: call \`haiku_revisit { intent: "${slug}" }\``,
2885+
"",
2886+
"**User questions (MANDATORY):** When you need user input:",
2887+
"- Use `AskUserQuestion` with an `options[]` array for every decision that has known alternatives — NEVER output option lists as plain text",
2888+
"- Use `ask_user_visual_question` when questions involve visual artifacts, rich markdown context, or multiple related decisions (use the `questions[]` array)",
2889+
"- Break independent questions into separate tool calls — do NOT bundle unrelated decisions into one message",
2890+
'- Always pre-populate options from your domain knowledge; include "Other (let me specify)" when the list may not be exhaustive',
28082891
)
28092892
subagentParts.push(instrLines.join("\n"))
28102893

@@ -3005,6 +3088,12 @@ function buildRunInstructions(
30053088
"6. Track outputs in unit frontmatter `outputs:` field",
30063089
"7. Use `ask_user_visual_question` for visual artifacts — do NOT open files in a browser",
30073090
`8. If outputs from a previous stage are missing: call \`haiku_revisit { intent: "${slug}" }\``,
3091+
"",
3092+
"**User questions (MANDATORY):** When you need user input:",
3093+
"- Use `AskUserQuestion` with an `options[]` array for every decision that has known alternatives — NEVER output option lists as plain text",
3094+
"- Use `ask_user_visual_question` when questions involve visual artifacts, rich markdown context, or multiple related decisions (use the `questions[]` array)",
3095+
"- Break independent questions into separate tool calls — do NOT bundle unrelated decisions into one message",
3096+
'- Always pre-populate options from your domain knowledge; include "Other (let me specify)" when the list may not be exhaustive',
30083097
)
30093098

30103099
sections.push(

packages/haiku/src/server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
200200
description:
201201
"Ask the user one or more questions via a rich HTML page in the browser. " +
202202
"Renders questions with selectable options (radio or checkbox) and an optional 'Other' field. " +
203-
"The user's answers are pushed back as a channel event.",
203+
"ALWAYS provide concrete options[] for each question — never leave the user to type freeform when you know the alternatives. " +
204+
"Use this instead of AskUserQuestion when: (1) questions involve visual artifacts or image_paths, " +
205+
"(2) you need rich markdown context above the questions, or (3) you have multiple related questions " +
206+
"that benefit from being presented together (each as a separate entry in the questions[] array). " +
207+
"For unrelated questions, make separate tool calls instead of bundling them.",
204208
inputSchema: {
205209
type: "object" as const,
206210
properties: {

packages/haiku/src/state-tools.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,13 +1567,16 @@ export function setFrontmatterField(
15671567
): void {
15681568
const raw = readFileSync(filePath, "utf8")
15691569
const parsed = matter(raw)
1570-
parsed.data[field] = value
1570+
// Spread to avoid mutating gray-matter's returned data object in place —
1571+
// in-place mutation can corrupt gray-matter's internal cache and cause
1572+
// subsequent parseFrontmatter calls to return stale values.
1573+
const updated = { ...parsed.data, [field]: value }
15711574
// gray-matter stringify: matter.stringify(content, data)
15721575
writeFileSync(
15731576
filePath,
15741577
matter.stringify(
15751578
parsed.content,
1576-
normalizeDates(parsed.data as Record<string, unknown>),
1579+
normalizeDates(updated as Record<string, unknown>),
15771580
),
15781581
)
15791582
}

0 commit comments

Comments
 (0)