Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .beads/issues.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,12 @@
{"id":"responsible-vibe-31.4.1","title":"Code cleanup: check for debug/temp artifacts","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-03T09:54:03.20756+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-03T09:54:12.432805+02:00","closed_at":"2026-04-03T09:54:12.432805+02:00","close_reason":"Closed","dependencies":[{"issue_id":"responsible-vibe-31.4.1","depends_on_id":"responsible-vibe-31.4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
{"id":"responsible-vibe-31.4.2","title":"Final test run","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-03T09:54:03.404412+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-03T10:00:00.880469+02:00","closed_at":"2026-04-03T10:00:00.880469+02:00","close_reason":"Closed","dependencies":[{"issue_id":"responsible-vibe-31.4.2","depends_on_id":"responsible-vibe-31.4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
{"id":"responsible-vibe-31.4.3","title":"Commit changes","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-03T09:54:03.597606+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-03T10:00:00.995887+02:00","closed_at":"2026-04-03T10:00:00.995887+02:00","close_reason":"Closed","dependencies":[{"issue_id":"responsible-vibe-31.4.3","depends_on_id":"responsible-vibe-31.4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
{"id":"responsible-vibe-32","title":"responsible-vibe: minor (development-plan-continue-after-compact-2.md)","description":"Responsible vibe engineering session using minor workflow for responsible-vibe","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-21T11:12:19.686403+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-21T11:12:19.686403+02:00"}
{"id":"responsible-vibe-32.1","title":"Explore","description":"Understand the problem, analyze existing patterns, and design your approach. Consider the scope and impact of the change. **STEP 1: Analyze Requirements** - If exists: Use it to understand the required changes - Otherwise: Document requirements in your task management system **STEP 2: Review Design Approach** - If exists: Respect the design approach documented in - Otherwise: Design your approach based on the problem analysis **STEP 3: Document Decisions** - Document your analysis and design decisions - Create tasks to guide implementation - Focus on analysis and design only - do not write any code yet","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-21T11:12:19.962361+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-21T11:12:19.962361+02:00","dependencies":[{"issue_id":"responsible-vibe-32.1","depends_on_id":"responsible-vibe-32","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
{"id":"responsible-vibe-32.1.1","title":"Research: confirm promptAsync vs prompt behavior","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-21T12:56:13.271134+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-21T12:58:58.330246+02:00","closed_at":"2026-04-21T12:58:58.330246+02:00","close_reason":"Closed","dependencies":[{"issue_id":"responsible-vibe-32.1.1","depends_on_id":"responsible-vibe-32.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
{"id":"responsible-vibe-32.1.2","title":"Design: inject full phase instructions into compacting hook","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-21T12:56:13.49865+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-21T12:58:58.513881+02:00","closed_at":"2026-04-21T12:58:58.513881+02:00","close_reason":"Closed","dependencies":[{"issue_id":"responsible-vibe-32.1.2","depends_on_id":"responsible-vibe-32.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
{"id":"responsible-vibe-32.2","title":"Implement","description":"Write clean, focused code for the minor enhancement, test your changes, and prepare for commit. **STEP 1: Review Design and Requirements** - If exists: Follow your design from - Otherwise: Elaborate design options and present them to the user - If exists: Ensure the relevant requirements from are met - Otherwise: Ensure existing requirements are met based on your task context **STEP 2: Implement Changes** - Write clean, focused code for the minor enhancement - Test your changes to ensure they work correctly and don't break existing functionality **STEP 3: Prepare for Finalization** - Update task progress as needed - Prepare documentation and commit when ready","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-21T11:12:20.251568+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-21T11:12:20.251568+02:00","dependencies":[{"issue_id":"responsible-vibe-32.2","depends_on_id":"responsible-vibe-32","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"responsible-vibe-32.2","depends_on_id":"responsible-vibe-32.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
{"id":"responsible-vibe-32.3","title":"Finalize","description":"Ensure code quality and documentation accuracy through systematic cleanup and review. **STEP 1: Code Cleanup** Systematically clean up development artifacts: - **Remove Debug Output**: Search for and remove all temporary debug output statements used during development. Look for language-specific debug output methods (console logging, print statements, debug output functions). Remove any debugging statements that were added for development purposes. - **Review TODO/FIXME Comments**: - Address each TODO/FIXME comment by either implementing the solution or documenting why it's deferred - Remove completed TODOs - Convert remaining TODOs to proper issue tracking if needed - **Remove Debugging Code Blocks**: - Remove temporary debugging code, test code blocks, and commented-out code - Clean up any experimental code that's no longer needed - Ensure proper error handling replaces temporary debug logging **STEP 2: Documentation Review** Review and update documentation to reflect final implementation: - **Update Long-Term Memory Documents**: Based on what was actually implemented: - If exists: Update if requirements changed during development - If exists: Update if design details were refined or changed - **Compare Against Implementation**: Review documentation against actual implemented functionality - **Update Changed Sections**: Only modify documentation sections that have functional changes - **Remove Development Progress**: Remove references to development iterations, progress notes, and temporary decisions - **Focus on Final State**: Ensure documentation describes the final implemented state, not the development process - **Ask User to Review Document Updates** **STEP 3: Final Validation** - Run existing tests to ensure cleanup didn't break functionality - Verify documentation accuracy with a final review - Ensure minor enhancement is ready for delivery - Update task progress and mark completed work as you finalize the minor enhancement","status":"open","priority":3,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-04-21T11:12:20.567032+02:00","created_by":"Oliver Jägle","updated_at":"2026-04-21T11:12:20.567032+02:00","dependencies":[{"issue_id":"responsible-vibe-32.3","depends_on_id":"responsible-vibe-32","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"responsible-vibe-32.3","depends_on_id":"responsible-vibe-32.2","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
{"id":"responsible-vibe-4","title":"Responsible-Vibe Development: responsible-vibe","description":"Development session using minor workflow for responsible-vibe","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-01-20T08:45:26.033247+01:00","created_by":"Oliver Jägle","updated_at":"2026-01-20T08:45:26.033247+01:00"}
{"id":"responsible-vibe-4.1","title":"Explore","description":"minor workflow explore phase tasks","status":"open","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-01-20T08:45:26.241377+01:00","created_by":"Oliver Jägle","updated_at":"2026-01-20T08:45:26.241377+01:00","dependencies":[{"issue_id":"responsible-vibe-4.1","depends_on_id":"responsible-vibe-4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
{"id":"responsible-vibe-4.1.1","title":"Analyze current verbose task management sections in plan file templates","status":"closed","priority":2,"issue_type":"task","owner":"github@beimir.net","created_at":"2026-01-20T08:46:16.960883+01:00","created_by":"Oliver Jägle","updated_at":"2026-01-20T08:46:58.154323+01:00","closed_at":"2026-01-20T08:46:58.154323+01:00","close_reason":"Closed","dependencies":[{"issue_id":"responsible-vibe-4.1.1","depends_on_id":"responsible-vibe-4.1","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
Expand Down
2 changes: 1 addition & 1 deletion .beads/last-touched
Original file line number Diff line number Diff line change
@@ -1 +1 @@
responsible-vibe-31.4.3
responsible-vibe-32.1.2
75 changes: 75 additions & 0 deletions .vibe/development-plan-continue-after-compact-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Development Plan: responsible-vibe (continue-after-compact-2 branch)

*Generated on 2026-04-21 by Vibe Feature MCP*
*Workflow: [minor](https://mrsimpson.github.io/responsible-vibe-mcp/workflows/minor)*

## Goal
After OpenCode context compaction, the AI should automatically continue with full workflow phase context. Two scenarios:
1. **Auto compaction** (`auto: true`): OpenCode creates a synthetic "Continue if you have next steps..." message, but it bypasses `chat.message` hook → phase instructions not injected.
2. **Manual compaction** (`auto: false`, triggered via UI): No synthetic continue message is created at all. Our plugin fires `client.session.prompt()` but the loop exits immediately without making an LLM call.

## Key Decisions

### Root cause findings (2026-04-21 deep dive)

**Manual compaction (`auto: false`):**
- The compaction task part in the DB has `auto=0` — all observed compactions were triggered via the OpenCode TUI with `auto: false`
- Since `auto: false`, the synthetic "Continue if you have next steps..." message is NEVER created
- `session.compacted` is still published (it fires regardless of auto)
- Our plugin calls `client.session.prompt()` → POST `/session/{id}/message` → returns with `duration=0`
- The loop exits IMMEDIATELY without calling the LLM — confirmed by absence of `session.prompt step=0 loop` logs and no LLM activity in the ~1m46s window before user's manual message
- The exact cause of immediate loop exit is unclear but likely related to race conditions between the runner state machine and our prompt call timing

**Auto compaction (`auto: true`):**
- Synthetic "Continue..." message IS created (with `synthetic: true` on the text part)
- The `chat.message` hook is NOT triggered for synthetic messages — this is the original stated problem
- AI responds but without phase context

**Session.idle timing issue:**
- `session.idle` fires SIMULTANEOUSLY with `session.compacted` (same millisecond in logs)
- This is because after auto compaction, `session.idle` fires at the compaction step boundary
- For manual compaction: the loop at step=2 exits in 2ms after the compaction summary is created, then `session.idle` fires

### Proposed fix approach

**Two-pronged strategy:**

1. **Enhance `experimental.session.compacting` hook** to inject FULL phase instructions into the compaction context/prompt. This makes the summary self-sufficient — it embeds the phase context so the AI knows what to continue even without `chat.message` firing.

2. **Fix the post-compaction prompt injection:**
- Switch from `client.session.prompt()` (synchronous, blocks) to `client.session.promptAsync()` (fires-and-forgets, non-blocking)
- Add a small delay (e.g., 500ms) before calling to avoid race conditions with runner state transitions
- Use `client.session.promptAsync()` to inject "Continue with the current phase." so the loop runs properly without our code blocking on the HTTP response

### Why `experimental.compaction.autocontinue` is not the solution
Setting `enabled: false` would prevent the synthetic message entirely and force reliance on our prompt — but this doesn't solve the immediate loop exit issue.

## Notes
- All 3 compaction tasks in the DB have `auto=0` — this project uses manual compaction (via TUI)
- Session ID `ses_2516d00b9ffesNfmx4bVKPm4Hh` is a long-running session with messages from multiple days
- The `duration=0` for the HTTP `/session/{id}/message` response is NORMAL for streaming endpoints (measures time to first byte, not full response)
- The `chat.message` hook DOES fire for our `client.session.prompt()` messages — confirmed by synthetic phase instructions appearing in DB parts
- After the loop exits at step=2 (2ms), there's 1m46s of silence before user's manual message — our `promptAsync` call must have had no effect

## Explore
<!-- beads-phase-id: responsible-vibe-32.1 -->
### Tasks
<!-- beads-synced: 2026-04-21 -->
*Auto-synced — do not edit here, use `bd` CLI instead.*

- [x] `responsible-vibe-32.1.1` Research: confirm promptAsync vs prompt behavior
- [x] `responsible-vibe-32.1.2` Design: inject full phase instructions into compacting hook

## Implement
<!-- beads-phase-id: responsible-vibe-32.2 -->
### Tasks
<!-- beads-synced: 2026-04-21 -->
*Auto-synced — do not edit here, use `bd` CLI instead.*


## Finalize
<!-- beads-phase-id: responsible-vibe-32.3 -->
### Tasks
<!-- beads-synced: 2026-04-21 -->
*Auto-synced — do not edit here, use `bd` CLI instead.*

11 changes: 8 additions & 3 deletions packages/core/src/instruction-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,15 @@ export class InstructionGenerator implements IInstructionGenerator {

const phaseName = capitalizePhase(phase);

// IMPORTANT: Directive markers to make instructions stand out from context
let workflowSection = `---
**Read \`${conversationContext.planFilePath}\`** for context.
- Focus on "${phaseName}" tasks, log decisions in "Key Decisions"
- Do NOT use other task/todo tools - use only the plan file for task tracking`;
### YOU MUST FOLLOW THESE INSTRUCTIONS:

**IMPORTANT: Read \`${conversationContext.planFilePath}\`** for context.

**ACTION REQUIRED: Focus on "${phaseName}" tasks** and log decisions in "Key Decisions"

**CRITICAL: Do NOT use other task/todo tools** - use only the plan file for task tracking`;

// Add file restriction guidance if patterns are restricted
if (
Expand Down
109 changes: 103 additions & 6 deletions packages/opencode-plugin/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ export const WorkflowsPlugin: Plugin = async (
// Consumed and cleared by the next chat.message hook call.
let bufferedInstructions: BufferedInstructions | null = null;

// Tracks sessions that just completed compaction and need a phase-aware
// continue message once the session becomes idle.
let postCompactionSession: string | null = null;

// Last-known model from chat.message hook. Cached so proceed_to_phase can
// pass providerID + modelID to the summarize API (which requires them).
let lastKnownModel: { providerID: string; modelID: string } | null = null;
Expand Down Expand Up @@ -473,8 +477,10 @@ ACTION REQUIRED: Use transition_phase tool to move to a phase that allows editin

/**
* Hook 3: experimental.session.compacting
* Fires when session is being compacted. We provide minimal guidance on what
* to preserve and instruct the summary to end with phase continuation.
* Fires when session is being compacted. We provide full phase instructions
* so the compaction summary is self-sufficient — the AI knows exactly what
* to continue even if the chat.message hook doesn't fire for the synthetic
* auto-compaction "continue" message.
*/
'experimental.session.compacting': async (hookInput, output) => {
logger.debug('experimental.session.compacting hook fired', {
Expand All @@ -487,14 +493,105 @@ ACTION REQUIRED: Use transition_phase tool to move to a phase that allows editin
return;
}

// Get full phase instructions to embed in compaction context
let phaseInstructions: string | null = null;
try {
const serverContext = await getServerContext();
const handler = new WhatsNextHandler();
const handlerResult = await handler.handle({}, serverContext);
if (handlerResult.success && handlerResult.data) {
phaseInstructions = stripWhatsNextReferences(
handlerResult.data.instructions
);
}
} catch (_err) {
// Fall back to minimal guidance if instructions can't be fetched
}

output.context.push(
'Preserve: user intents, key decisions, significant changes and the reasoning why they were made. Remove tool calls, intermediate thoughts, and minor details.'
);
output.context.push(
`End summary with: "Continue ${state.phase} phase. ${state.phaseDescription || ''}"`
);

logger.info('Injected compaction guidance', { phase: state.phase });
if (phaseInstructions) {
output.context.push(
`Current workflow phase: ${state.phase}. After compaction, resume with full phase context:\n\n${phaseInstructions}`
);
} else {
output.context.push(
`End summary with: "Continue ${state.phase} phase. ${state.phaseDescription || ''}"`
);
}

logger.info('Injected compaction guidance', {
phase: state.phase,
fullInstructions: phaseInstructions !== null,
});
},

/**
* Hook 4: event
* Listens for bus events. When a session compaction completes we record it,
* then when the session becomes idle we send a real user message so the
* normal chat.message hook fires and injects phase instructions — giving the
* AI full workflow context to continue after the compaction.
*
* We intentionally do NOT suppress the default synthetic "continue" message
* (experimental.compaction.autocontinue). It may produce a first generic AI
* response, but the idle trigger below ensures a proper phase-aware follow-up.
*/
event: async ({ event }) => {
logger.debug('event hook fired', { type: event.type });

if (event.type === 'session.compacted') {
postCompactionSession = event.properties.sessionID as string;
logger.info('session.compacted: pending phase-aware continue', {
sessionID: postCompactionSession,
});
return;
}

if (
event.type === 'session.idle' &&
postCompactionSession === (event.properties.sessionID as string)
) {
const sessionID = postCompactionSession;
postCompactionSession = null;
logger.info(
'session.idle after compaction: sending phase-aware continue',
{ sessionID }
);

// Wait a short time to allow the OpenCode runner state machine to
// fully settle after compaction before we fire the follow-up prompt.
await new Promise(resolve => setTimeout(resolve, 500));

try {
const client = input.client as {
session: {
promptAsync(params: {
path: { id: string };
body: { parts: Array<{ type: string; text: string }> };
}): Promise<unknown>;
};
};
await client.session.promptAsync({
path: { id: sessionID },
body: {
parts: [
{ type: 'text', text: 'Continue with the current phase.' },
],
},
});
logger.info('session.idle: phase-aware continue sent (async)', {
sessionID,
});
} catch (err) {
logger.error('session.idle: failed to send phase-aware continue', {
sessionID,
error: err instanceof Error ? err.message : String(err),
});
}
}
},

/**
Expand Down
Loading
Loading