From 72d65f467225f4bec85ba610056301334e5946f4 Mon Sep 17 00:00:00 2001 From: vinodhalaharvi-claude Date: Mon, 25 May 2026 14:08:21 +0000 Subject: [PATCH] runtime+prompt: content verbs read stdin OR arg; content travels in the program MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend portability principle: on temporal a program runs on an arbitrary remote worker, so it cannot read local files — only the data passed in (stdin) and out (stdout) crosses that boundary. A pipeline must therefore carry its content as values, not reach into ambient local state. The same program must run unchanged on memory and temporal. This makes the content verbs echo-like (stdin OR arg) so pasted content travels in the program: runtime (internal/agentscript/runtime.go): - summarize: was input-ONLY — it ignored its arg, so summarize "text" summarized empty input (the bug seen live in Slack). Now: use piped input if present, else the arg; error clearly if neither. - analyze: if there's no piped input, treat the arg as the content (not only the focus), so analyze "text" works standalone. - (ask already did arg + optional stdin context — left as the model.) prompt (pkg/script/translate.go): - New PASSING CONTENT section: put pasted text directly in the command's quoted argument; do NOT use a file path or read for pasted content — content travels in the argument so the program is portable across backends. Effect: '@loom in memory: summarize ' now translates to memory static ( summarize "" ) and runs in-process on Claude Code, returning a real summary — and the identical program is valid on temporal later (content is a value, not a local file). Verified: summarize "text" and (stdin >=> summarize) both reach the LLM with content (sandbox lacks the claude binary, which is why they stop there — proving the route). No file/local-state dependency. CI: vet, gofmt, staticcheck, go test ./pkg/..., build pass. --- internal/agentscript/runtime.go | 22 +++++++++++++++++++--- pkg/script/translate.go | 6 ++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/internal/agentscript/runtime.go b/internal/agentscript/runtime.go index 22831c8..eb287a5 100644 --- a/internal/agentscript/runtime.go +++ b/internal/agentscript/runtime.go @@ -431,7 +431,18 @@ func (r *Runtime) executeCommand(ctx context.Context, cmd *Command, input string case "search": result, err = r.search(ctx, cmd.Arg) case "summarize": - result, err = r.geminiCall(ctx, "Summarize the following content concisely:\n\n"+input) + // Content comes from piped input (stdin) or, if none, the arg — + // like echo. Keeps the pipeline stdin->stdout portable while also + // allowing `summarize "text"` directly. + content := input + if content == "" { + content = cmd.Arg + } + if content == "" { + err = fmt.Errorf("summarize: no content (pipe text in or pass it as an argument)") + } else { + result, err = r.geminiCall(ctx, "Summarize the following content concisely:\n\n"+content) + } case "ask": prompt := cmd.Arg if input != "" { @@ -445,11 +456,16 @@ func (r *Runtime) executeCommand(ctx context.Context, cmd *Command, input string } result, err = r.claudeCall(ctx, prompt) case "analyze": + // Content from piped input (stdin); if none, treat the arg as the + // content rather than only the focus — echo-like dual source. prompt := "Analyze the following" - if cmd.Arg != "" { + content := input + if content == "" && cmd.Arg != "" { + content = cmd.Arg + } else if cmd.Arg != "" { prompt += " focusing on " + cmd.Arg } - prompt += ":\n\n" + input + prompt += ":\n\n" + content result, err = r.geminiCall(ctx, prompt) case "save": result, err = r.save(cmd.Arg, input) diff --git a/pkg/script/translate.go b/pkg/script/translate.go index 5d3376f..6d74f6f 100644 --- a/pkg/script/translate.go +++ b/pkg/script/translate.go @@ -81,6 +81,12 @@ CHOOSING THE BACKEND - 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`" + `. +PASSING CONTENT +When the user pastes text to act on (e.g. "summarize ", "analyze "), put that pasted text directly into the command's quoted argument: + Request: summarize The quick brown fox jumped over the lazy dog. + Output: memory static ( summarize "The quick brown fox jumped over the lazy dog." ) +Do NOT use a file path or ` + "`read`" + ` for pasted content — content travels in the argument so the same program is portable across backends. Verbs take their content from the pipeline input or, when there is none, from their argument. + AVAILABLE COMMANDS (you may use ONLY these — never invent a command): ` + available + `