Browse Source

Move phase reminder into static prompt

Alvin Unreal 3 months ago
parent
commit
d43a2b3445

+ 5 - 0
src/agents/orchestrator.ts

@@ -1,4 +1,5 @@
 import type { AgentConfig } from '@opencode-ai/sdk/v2';
+import { PHASE_REMINDER_TEXT } from '../config/constants';
 
 export interface AgentDefinition {
   name: string;
@@ -212,6 +213,10 @@ ${enabledValidationRouting}
 
 </Workflow>
 
+<WorkflowReminder>
+${PHASE_REMINDER_TEXT}
+</WorkflowReminder>
+
 <Communication>
 
 ## Clarity Over Assumptions

+ 8 - 15
src/hooks/phase-reminder/index.test.ts

@@ -1,9 +1,8 @@
 import { describe, expect, test } from 'bun:test';
-import { SLIM_INTERNAL_INITIATOR_MARKER } from '../../utils';
 import { createPhaseReminderHook, PHASE_REMINDER } from './index';
 
 describe('createPhaseReminderHook', () => {
-  test('prepends reminder for orchestrator sessions', async () => {
+  test('does not mutate orchestrator messages', async () => {
     const hook = createPhaseReminderHook();
     const output = {
       messages: [
@@ -16,9 +15,8 @@ describe('createPhaseReminderHook', () => {
 
     await hook['experimental.chat.messages.transform']({}, output);
 
-    expect(output.messages[0].parts[0].text).toBe(
-      `${PHASE_REMINDER}\n\n---\n\nhello`,
-    );
+    expect(output.messages[0].parts[0].text).toBe('hello');
+    expect(output.messages[0].parts[0].text).not.toContain(PHASE_REMINDER);
   });
 
   test('skips non-orchestrator sessions', async () => {
@@ -37,27 +35,22 @@ describe('createPhaseReminderHook', () => {
     expect(output.messages[0].parts[0].text).toBe('hello');
   });
 
-  test('skips internal notification turns', async () => {
+  test('does not mutate internal notification turns', async () => {
     const hook = createPhaseReminderHook();
+    const text =
+      '[Background task "x" completed]\n<!-- slim-internal-initiator -->';
     const output = {
       messages: [
         {
           info: { role: 'user' },
-          parts: [
-            {
-              type: 'text',
-              text: `[Background task "x" completed]\n${SLIM_INTERNAL_INITIATOR_MARKER}`,
-            },
-          ],
+          parts: [{ type: 'text', text }],
         },
       ],
     };
 
     await hook['experimental.chat.messages.transform']({}, output);
 
-    expect(output.messages[0].parts[0].text).toContain(
-      SLIM_INTERNAL_INITIATOR_MARKER,
-    );
+    expect(output.messages[0].parts[0].text).toBe(text);
     expect(output.messages[0].parts[0].text).not.toContain(PHASE_REMINDER);
   });
 });

+ 6 - 52
src/hooks/phase-reminder/index.ts

@@ -1,15 +1,12 @@
 /**
- * Phase reminder to inject before each user message.
- * Keeps workflow instructions in the immediate attention window
- * to combat instruction-following degradation over long contexts.
+ * Phase reminder hook retained for backwards-compatible hook wiring.
  *
- * Research: "LLMs Get Lost In Multi-Turn Conversation" (arXiv:2505.06120)
- * shows ~40% compliance drop after 2-3 turns without reminders.
- *
- * Uses experimental.chat.messages.transform so it doesn't show in UI.
+ * The reminder now lives in the static orchestrator prompt. Injecting it into
+ * every latest user message changed request content unnecessarily and could
+ * interfere with provider prompt-cache reuse. Keeping this transform as a
+ * no-op preserves the hook shape without mutating chat messages.
  */
 import { PHASE_REMINDER_TEXT } from '../../config/constants';
-import { SLIM_INTERNAL_INITIATOR_MARKER } from '../../utils';
 
 export const PHASE_REMINDER = `<reminder>${PHASE_REMINDER_TEXT}</reminder>`;
 
@@ -41,50 +38,7 @@ export function createPhaseReminderHook() {
       _input: Record<string, never>,
       output: { messages: MessageWithParts[] },
     ): Promise<void> => {
-      const { messages } = output;
-
-      if (messages.length === 0) {
-        return;
-      }
-
-      // Find the last user message
-      let lastUserMessageIndex = -1;
-      for (let i = messages.length - 1; i >= 0; i--) {
-        if (messages[i].info.role === 'user') {
-          lastUserMessageIndex = i;
-          break;
-        }
-      }
-
-      if (lastUserMessageIndex === -1) {
-        return;
-      }
-
-      const lastUserMessage = messages[lastUserMessageIndex];
-
-      // Only inject for orchestrator (or if no agent specified = main session)
-      const agent = lastUserMessage.info.agent;
-      if (agent && agent !== 'orchestrator') {
-        return;
-      }
-
-      // Find the first text part
-      const textPartIndex = lastUserMessage.parts.findIndex(
-        (p) => p.type === 'text' && p.text !== undefined,
-      );
-
-      if (textPartIndex === -1) {
-        return;
-      }
-
-      const originalText = lastUserMessage.parts[textPartIndex].text ?? '';
-      if (originalText.includes(SLIM_INTERNAL_INITIATOR_MARKER)) {
-        return;
-      }
-
-      // Prepend the reminder to the existing text
-      lastUserMessage.parts[textPartIndex].text =
-        `${PHASE_REMINDER}\n\n---\n\n${originalText}`;
+      void output;
     },
   };
 }