Agent Prompt Blocks
Meshfox has no dedicated "AI prompt" block type — and doesn't need one. A
runnable fence just resolves to program args... code (see SPEC.md's
"Runnable code fences"): the implicit bash -c code when there's no
interpreter=, or interpreter's own program with code written to a temp
file otherwise. An agent CLI is just another program to point that at — and
interpreter="@agent" is meshfox's own built-in interpreter for exactly
that (see crates/core/src/builtins/agent.sh, shipped in the binary
itself): the fence body becomes the prompt, with env=-declared variables
interpolated right into it. This canvas runs it for real (cached output
below each fence, not typed by hand — same convention as
interpreters.canvas.md), plus the one thing @agent deliberately doesn't
try to cover — real shell logic beyond variable substitution, where a raw
bash fence is still the answer.
One-shot via @agent
The common case: interpreter="@agent", fence body is the prompt, done.
Captured (non-tty, this fence's own shape) means agent.sh's own [ -t 1 ] check picks its one-shot claude -p/codex exec branch —
see Interactive session via tty for the
other one. Which provider it actually calls comes from
interpreters.agent.provider in .meshfox/config.toml (unset here, so it
defaults to claude) — see meshfox_core::config.
Ответь одной короткой фразой на русском: что быстрее — заяц или черепаха?
exit code: 0 · 3.5s
Заяц.
Meshfox adds no access restrictions in either mode. Like a bash block, the agent runs as the current user, with its own tool, sandbox, approval, and directory trust settings. Capturing output does not disable tools.
Interpolation via env=
@agent's own script (crates/core/src/builtins/agent.sh) interpolates
$NAME/${NAME} references in the prompt itself — three rules decide
exactly when:
- Only names this fence's own
env=declares are ever candidates.agent.shusesMESHFOX_ENV_NAMES— the comma-separated local names fromenv=— to choose which names to substitute (here, justTOPIC). A$-lookingtoken whose name isn't in that list —$PATH,$HOME, a typo, anything not declared — is left completely alone, exactly as typed. - Only as a whole token. Same "not a prefix of a longer identifier"
rule
interpreter="$PYTHON -u"already follows (crate::exec::interpreter_var_refs):$TOPICmatches,$TOPICSdoesn't — the trailingSblocks it, so a name that happens to prefix a real English word in the prompt is safe. $$NAMEalways escapes to a literal$NAME, doubling the$— same convention Make/CI systems already use for this — regardless of whetherNAMEis even declared. This is the actual answer to "how do I stop a$-looking piece of my own prompt from being touched": for a declared name you don't want substituted this once, escape it; for anything not declared, rule 1 already leaves it alone with nothing extra to write.
Substitution reads the original prompt once. Inserted values are opaque:
if A contains $B, substituting $A produces the literal $B, regardless
of the order of names in env=.
All three at once, in one prompt, one real run:
Explain $TOPIC in one sentence. Also, literally repeat the words "dollar
sign dollar sign TOPIC" — no wait, repeat this literally instead: $$TOPIC.
And don't touch this at all, it isn't declared: $UNRELATED.
exit code: 0 · 3.9s
Git rebase replays your branch's commits one by one onto a new base commit, rewriting history to produce a linear sequence instead of a merge.
Literal repeat: $TOPIC
I won't touch $UNRELATED since it isn't declared.
Need more than variable substitution — real shell logic, calling another
program first, building the prompt piece by piece? That's what a raw
bash fence is still for: write the claude -p invocation (and its
quoting) out by hand, same as before @agent existed.
claude -p \
--model haiku \
--max-budget-usd 0.05 \
--no-session-persistence \
-- "$(cat <<EOF
Объясни в одном коротком предложении на русском, что такое $TOPIC.
EOF
)"
exit code: 0 · 3.6s
Git rebase — это команда для перемещения ваших коммитов на новую основу, переписывая историю истории коммитов вместо создания merge-коммита.
Interactive session via tty
@agent's own script tells the two run shapes apart with the same plain
[ -t 1 ] check mentioned above — no separate meshfox mechanism needed. A
plain (non-tty) block always has its stdout piped for capture, so it gets
the one-shot -p/exec answer One-shot via
@agent already demonstrates. A tty block hands the
process a real inherited terminal instead — agent.sh sees that and
drops into a genuine interactive claude/codex session
(using the agent's own access policy), seeded with this fence's own body as the opening
message, exactly as if you'd typed claude "..." by hand in this shell.
Not run here — same as interpreters.canvas.md's own tty example, this
needs a real interactive terminal to mean anything — but it's a valid,
runnable block: meshfox run --canvas examples/agent-prompt.canvas.md interactive-session-via-tty ask, from an interactive shell, hands you a
real Claude session.
Let's look at this repository together — what does it do?