[object Object]

← back to Claude Email Responder

Reverse channel: directive parser bypasses Claude for 'act on X' / 'status' / 'pause loop' / 'resume loop'

a7dfc6e5e614d4856ce9d971c8e71636db6ddabd · 2026-05-08 00:35:15 -0700 · SteveStudio2

Steve can reply to any watchdog email with one of these verbs and the
directive lands in ~/Projects/_dw-batch/directives/<ts>-<verb>.json.
The next yolo tick reads the queue and acts.

Bypasses askClaude() — deterministic, free, fast. Falls through to
existing Claude-CLI conversational path for non-directive replies.

'status' verb returns ledger-tail directly in the email reply.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit a7dfc6e5e614d4856ce9d971c8e71636db6ddabd
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Fri May 8 00:35:15 2026 -0700

    Reverse channel: directive parser bypasses Claude for 'act on X' / 'status' / 'pause loop' / 'resume loop'
    
    Steve can reply to any watchdog email with one of these verbs and the
    directive lands in ~/Projects/_dw-batch/directives/<ts>-<verb>.json.
    The next yolo tick reads the queue and acts.
    
    Bypasses askClaude() — deterministic, free, fast. Falls through to
    existing Claude-CLI conversational path for non-directive replies.
    
    'status' verb returns ledger-tail directly in the email reply.
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 server.js | 42 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 40 insertions(+), 2 deletions(-)

diff --git a/server.js b/server.js
index 7b0c5b2..fdae68a 100644
--- a/server.js
+++ b/server.js
@@ -173,9 +173,47 @@ async function pollOnce() {
             continue;
           }
           console.log(`[responder] handling msg ${id} from ${sender}: ${bodyText.slice(0,80)}…`);
+
+          // ─── Directive shortcut (reverse channel into yolo loop) ───
+          // Steve can reply to any watchdog email with verbs like:
+          //   act on <target>     — queue an action for the next yolo tick
+          //   status              — reply with autonomy ledger tail
+          //   pause loop / resume loop
+          // This bypasses Claude (deterministic, free, fast).
+          const directiveMatch = bodyText.trim().match(/^(act on|pause loop|resume loop|status)\b\s*(.*)$/i);
           let answer;
-          try { answer = await askClaude(subject, bodyText, account); }
-          catch (e) { console.error('[responder] claude error', e.message); errors++; continue; }
+          if (directiveMatch) {
+            const verb = directiveMatch[1].toLowerCase().replace(/\s+/g, '_');
+            const target = (directiveMatch[2] || '').trim();
+            const directive = {
+              ts: new Date().toISOString(),
+              from: sender,
+              raw: bodyText.slice(0, 300),
+              parsed: { verb, target },
+              thread_id: full.threadId || id,
+            };
+            const fname = `${Date.now()}-${verb}.json`;
+            const dirQueue = '/Users/stevestudio2/Projects/_dw-batch/directives';
+            try {
+              fs.mkdirSync(dirQueue, { recursive: true });
+              fs.writeFileSync(`${dirQueue}/${fname}`, JSON.stringify(directive, null, 2));
+              console.log(`[responder] directive queued: ${fname}`);
+              if (verb === 'status') {
+                // Quick status reply — read ledger tail
+                let tail = '';
+                try { tail = require('child_process').execSync(`/Users/stevestudio2/.claude/data/ledger-tail.sh 15`, { encoding: 'utf8' }); } catch {}
+                answer = `<p>Directive queued: <code>${fname}</code></p><pre style="font-family:monospace;font-size:11px;background:#f5f5f5;padding:10px">${tail.replace(/[<>]/g, c => c === '<' ? '&lt;' : '&gt;')}</pre>`;
+              } else {
+                answer = `<p>Directive queued for next yolo tick: <code>${verb}</code> · target: <code>${target || '(none)'}</code></p><p style="color:#888;font-size:11px">file: ${fname}</p>`;
+              }
+            } catch (e) {
+              console.error('[responder] directive queue failed', e.message);
+              answer = `<p>Directive parse failed: ${e.message}</p>`;
+            }
+          } else {
+            try { answer = await askClaude(subject, bodyText, account); }
+            catch (e) { console.error('[responder] claude error', e.message); errors++; continue; }
+          }
           if (!answer) { state.replied[id] = { ts: Date.now(), reason: 'empty-claude-answer' }; continue; }
           // Wrap with light HTML so the reply is paragraphed even if Claude returned plain.
           const looksHtml = /<\w+[^>]*>/.test(answer);

← ec27116 initial snapshot — gitify all builds (CLAUDE.md rule 2026-05  ·  back to Claude Email Responder  ·  snapshot: 1 file(s) changed, ~1 modified 0e199d6 →