From 7390cad8d62276222e246e5fec2984fa18b2e538 Mon Sep 17 00:00:00 2001 From: vinodhalaharvi-claude Date: Mon, 25 May 2026 13:51:53 +0000 Subject: [PATCH] translate: teach both backends in the grammar prompt, default to memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prompt hardcoded 'temporal static ( ... )' as the only block form and instructed 'Output exactly one block: temporal static'. So the LLM ALWAYS emitted temporal — even when the user said 'in memory'. A memory-only verb (summarize, search, ...) then landed on temporal and was rejected with 'known action but not available on this backend yet'. The user asked for memory; the prompt made it impossible. Fix (BuildPrompt): - Teach BOTH block forms: 'memory static ( ... )' and 'temporal static ( ... )', with one-line semantics (memory = runs in-process immediately; temporal = durable workflow). - Add a CHOOSING THE BACKEND section: default to memory for research / summarize / read / ask / analyze / almost everything; use temporal ONLY when durability/scheduling/background is explicitly requested (and note only echo runs on temporal today); 'in memory' / 'locally' / 'quickly' → memory. - Examples now show memory blocks (summarize, parallel research+merge+ ask) plus one explicit temporal example. Effect: 'run in memory: summarize ...' now translates to 'memory static ( summarize ... )' → loom routes it to in-process execution (Claude Code) → result posts back, instead of being rejected. The full in-memory verb set becomes reachable from prose. Test (prompt_backend_test.go): the prompt must contain both block forms, the memory DEFAULT, and the 'in memory' cue — regression guard against reverting to temporal-only. CI: vet, gofmt, staticcheck, go test -race ./pkg/script/..., build pass. --- pkg/script/prompt_backend_test.go | 27 ++++++++++++++++++++++++ pkg/script/translate.go | 34 +++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 pkg/script/prompt_backend_test.go diff --git a/pkg/script/prompt_backend_test.go b/pkg/script/prompt_backend_test.go new file mode 100644 index 0000000..5c9e4c2 --- /dev/null +++ b/pkg/script/prompt_backend_test.go @@ -0,0 +1,27 @@ +package script_test + +import ( + "strings" + "testing" + + "github.com/vinodhalaharvi/agentscript/pkg/script" +) + +// The grammar prompt must teach BOTH backends and default to memory. +// Regression guard for the bug where the prompt only ever showed +// "temporal static ( ... )", so the LLM emitted temporal even when the +// user said "in memory" — and memory-only verbs were then rejected as +// "not available on this backend". +func TestBuildPrompt_TeachesBothBackendsDefaultMemory(t *testing.T) { + p := script.BuildPrompt(script.DefaultRegistry()) + for _, want := range []string{ + "memory static", // memory block form taught + "temporal static", // temporal block form taught + "DEFAULT", // memory is the default + "in memory", // explicit "in memory" → memory + } { + if !strings.Contains(p, want) { + t.Errorf("prompt should contain %q so the LLM can pick the memory backend", want) + } + } +} diff --git a/pkg/script/translate.go b/pkg/script/translate.go index 8fbc16f..5d3376f 100644 --- a/pkg/script/translate.go +++ b/pkg/script/translate.go @@ -69,25 +69,41 @@ func BuildPrompt(reg *registry.Registry) string { return `You translate a user's request into a small pipeline language called AgentScript. Output ONLY the AgentScript program — no prose, no explanation, no code fences. GRAMMAR -A program is a single block: - temporal static ( ) +A program is a single block that names an execution backend: + memory static ( ) ← runs in-process, immediately + temporal static ( ) ← runs as a durable workflow A pipeline is one or more commands joined by >=> (sequential, left output feeds right): command "arg" >=> command "arg" >=> command Parallel fan-out exists as <*> inside parentheses, but use it ONLY when the request is an explicit, unambiguous flat list of independent things to do at once. When in doubt, use sequential >=> . +CHOOSING THE BACKEND +- Use ` + "`memory`" + ` by DEFAULT — for research, summarizing, reading files, asking questions, analysis, and almost everything. It runs the full command set immediately. +- Use ` + "`temporal`" + ` ONLY when the user explicitly asks for a durable, long-running, scheduled, or background workflow. Currently only the ` + "`echo`" + ` command runs on temporal. +- If the user says "in memory", "locally", "quickly", or doesn't mention durability, use ` + "`memory`" + `. + AVAILABLE COMMANDS (you may use ONLY these — never invent a command): ` + available + ` RULES -1. Output exactly one block: temporal static ( ... ). Nothing else. -2. Use only commands from the AVAILABLE COMMANDS list. If the request needs a command that does not exist, choose the closest available command; do not invent names. -3. Prefer sequential >=> . Use <*> only for a clear list of independent parallel actions. -4. String arguments are double-quoted. Pass the user's intent as the argument text. -5. Keep it minimal — the smallest pipeline that satisfies the request. +1. Output exactly one block: ` + "`memory static ( ... )`" + ` or ` + "`temporal static ( ... )`" + `. Nothing else. +2. Default to the memory backend unless durability is explicitly requested. +3. Use only commands from the AVAILABLE COMMANDS list. If the request needs a command that does not exist, choose the closest available command; do not invent names. +4. Prefer sequential >=> . Use <*> only for a clear list of independent parallel actions. +5. String arguments are double-quoted. Pass the user's intent as the argument text. +6. Keep it minimal — the smallest pipeline that satisfies the request. + +EXAMPLES +Request: summarize this article about climate policy +Output: memory static ( summarize "this article about climate policy" ) + +Request: research Google and Microsoft strengths, then tell me who is winning +Output: memory static ( ( search "Google strengths" >=> analyze "strengths" <*> search "Microsoft strengths" >=> analyze "strengths" ) >=> merge >=> ask "who is winning?" ) -EXAMPLE Request: say hello to the team -Output: temporal static ( echo "hello to the team" )` +Output: memory static ( echo "hello to the team" ) + +Request: run a durable workflow that echoes hello +Output: temporal static ( echo "hello" )` } // cleanDSL strips common LLM wrapping (code fences, leading/trailing