summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-09-03 07:41:51 -0600
committert <t@tjp.lol>2026-09-03 07:57:03 -0600
commit2f429aae8468f249dd40baaf815975d5e02b1030 (patch)
treebe097a440d02d515fd0059cc127a2a5a660212fb
parentb206f895d9d3e82fcd2dc028d993b66ab0734011 (diff)
Show full subagents.lua source when expanded; guide toward templated promptsHEADmain
The expanded progress board capped the executed Lua at 128 lines (and 32KB), so long workflow sources ended in an ellipsis even after Ctrl-O. Expanded now renders the whole source; collapsed still shows the first four lines. Add a "Program, don't transcribe" section to the system guidance so the primary writes shorter sources: one [[template]] with {name} placeholders filled by gsub with a table, loops over data instead of per-child copies, shared system prompts in `agents`, and file references instead of quoted contents. A single templating mechanism is named on purpose to remove choice. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ETqhv5FVD8Z14BEBsXeHHC
-rw-r--r--init.lua19
-rw-r--r--spec/test_progress.lua23
-rw-r--r--subagents/progress.lua7
3 files changed, 46 insertions, 3 deletions
diff --git a/init.lua b/init.lua
index 6058c9d..5597dd2 100644
--- a/init.lua
+++ b/init.lua
@@ -156,6 +156,25 @@ return subagents.workflow(function(ctx)
end)
```
+**Program, don't transcribe.** `source` is a program, not a transcript: write each piece of text once and let code produce the variations. Templates are the one mechanism: a `[[long string]]` with `{name}` placeholders, filled by `template:gsub("{([%w_]+)}", vars)` where `vars` is a table. Do not use `string.format`, `..` chains, or any other placeholder style. Near-identical prompts come from a loop over a data table, never copy-pasted per child; a shared system prompt goes in `agents` once, not in every `ctx:agent`. Children share your workspace, so a prompt can name a file to read instead of quoting its contents. If `source` is mostly quoted text, that is the signal to factor it.
+
+```lua
+local brief = [[
+Audit the {area} module under {root} for unchecked errors. Read-only.
+Report as a bullet list; end with RESULT={"issues":<count>}
+]]
+return subagents.workflow(function(ctx)
+ local workers = {}
+ for _, area in ipairs({"auth", "billing", "search"}) do
+ workers[#workers + 1] = ctx:agent{name=area, agent="auditor",
+ prompt=brief:gsub("{([%w_]+)}", {area=area, root="src/"})}
+ end
+ local out = {}
+ for _, r in ipairs(ctx:await(workers, "all")) do out[#out + 1] = r.output end
+ return table.concat(out, "\n\n")
+end)
+```
+
**Deterministic code drives; agents advise.** A workflow is code you control wrapped around judgment you cannot. Put every decision that can be nailed down into Lua — loops, counts, ordering, retries, pass/fail gates — and spend agents only on discovery and judgment. When Lua needs a fact an agent found (a list to fan out over, a verdict, a chosen option), tell that agent to end its report with one machine-parseable line, parse it, and let the parsed value shape the rest of the graph. The sandbox has `json.encode(value)` and `json.decode(text)`, which returns the value or `nil, message`.
```lua
diff --git a/spec/test_progress.lua b/spec/test_progress.lua
index 79570c1..a562789 100644
--- a/spec/test_progress.lua
+++ b/spec/test_progress.lua
@@ -349,6 +349,29 @@ return {
progress.reset()
end },
+ { "expanded subagents.lua source is never truncated", function()
+ if not pcall(require, "dkjson") then return "skip", "dkjson is not installed" end
+ progress.reset()
+ local lines = {}
+ for i = 1, 600 do lines[i] = "local v" .. i .. " = " .. i end
+ lines[#lines + 1] = "return 'last-line'"
+ local component
+ progress.claim({
+ id = "call-long-source",
+ tool_name = "subagents.lua",
+ collapsed = false,
+ input = require("dkjson").encode({ source = table.concat(lines, "\n") }),
+ set_component = function(_, value)
+ component = value
+ return { invalidate = function() end, alive = function() return true end }
+ end,
+ })
+ local rendered = component:render(100)
+ assert(rendered[#rendered] == " return 'last-line'", rendered[#rendered])
+ assert(#rendered == 2 + #lines, #rendered)
+ progress.reset()
+ end },
+
{ "expanded history includes streamed assistant text and available tool call fields", function()
progress.reset()
local component = board("call-tools", false)
diff --git a/subagents/progress.lua b/subagents/progress.lua
index fc86935..e079163 100644
--- a/subagents/progress.lua
+++ b/subagents/progress.lua
@@ -77,7 +77,7 @@ local function source_of(tool_name, input)
if type(source) ~= "string" or source == "" then return nil end
-- Tabs are stripped to a single space by sanitization, which would flatten
-- indented code; widen them first so the listing keeps its shape.
- return sanitize_multiline((source:gsub("\t", " "))):sub(1, MAX_PRESENTATION_BYTES)
+ return sanitize_multiline((source:gsub("\t", " ")))
end
local function wrap(text, width, emit)
@@ -279,9 +279,10 @@ local function body_lines(card, width, collapsed)
end
-- The executed source, verbatim: real newlines, one rendered row per wrapped
--- source line. Collapsed shows the head of it, expanded the whole thing.
+-- source line. Collapsed shows the head of it, expanded the whole thing,
+-- uncapped: the model wrote it and the user asked to see it.
local function source_lines(board, width, collapsed)
- local limit = collapsed and COLLAPSED_LINES or MAX_PRESENTATION_LINES
+ local limit = collapsed and COLLAPSED_LINES or math.huge
local out, truncated = {}, false
local function add(row)
if #out < limit then out[#out + 1] = BODY_INDENT .. row else truncated = true end