Automation: Macros
Lectic supports a simple but powerful macro system that allows you to define and reuse snippets of text. This is useful for saving frequently used prompts, automating repetitive workflows, and composing complex, multi-step commands.
Macros are defined in your YAML configuration (either in a .lec file’s header or in an included configuration file).
Defining Macros
Macros are defined under the macros key. Each macro must have a name and an expansion. You can optionally provide an env map to set default environment variables for the expansion.
macros:
- name: summarize
expansion: >
Please provide a concise, single-paragraph summary of our
conversation so far, focusing on the key decisions made and
conclusions reached.
- name: build
env:
BUILD_DIR: ./dist
expansion: exec:echo "Building in $BUILD_DIR"Expansion Sources
The expansion field can be a simple string, or it can load its content from a file or from the output of a command, just like the prompt field. For full semantics of file: and exec:, see External Prompts.
- File Source:
expansion: file:./prompts/summarize.txt - Command/Script Source:
Single line:
expansion: exec:get-prompt-from-db --name summarize(executed directly, not via a shell)Multi‑line script: start with a shebang, e.g.
expansion: | exec:#!/usr/bin/env bash echo "Hello, ${TARGET}!"Multi‑line scripts are written to a temp file and executed with the interpreter given by the shebang.
Using Macros
To use a macro, you invoke it by writing the macro name as the directive name:
:name[]expands the macro.:name[args]expands the macro and also passesargsto the expansion as theARGenvironment variable.
When Lectic processes the file, it replaces the macro directive with the full text from its expansion field before processing any other directives (like :cmd).
This was a long and productive discussion. Could you wrap it up?
:summarize[]The Macro Expansion Environment
When a macro expands via exec, the script being executed can be pased information via environment variables.
Passing arguments to expansions via ARG
The text inside the directive brackets is passed to the macro expansion as the ARG environment variable.
This works for both single-line exec: commands and multi-line exec: scripts.
:name[hello]setsARG=hello.- If you explicitly set an
ARGattribute, it overrides the bracket content::name[hello]{ARG="override"}.
Passing other environment variables via attributes
You can pass environment variables to a macro’s expansion by adding attributes to the macro directive. These attributes are injected into the environment of exec: expansions when they run.
:name[]{FOO="bar"}sets the variableFOOtobar.:name[]{EMPTY}sets the variableEMPTYto be undefined. If you need an empty string value, write:name[]{EMPTY=""}.
Notes: - Single‑line exec: commands are not run through a shell. If you need shell features, invoke a shell explicitly, e.g., exec: bash -c 'echo "Hello, $TARGET"'. - In single‑line commands, variables in the command string are expanded before execution. For multi‑line scripts, variables are available to the script via the environment.
Example
Configuration:
macros:
- name: greet
expansion: exec: bash -c 'echo "Hello, $TARGET!"'Conversation:
:greet[]{TARGET="World"}When Lectic processes this, the directive will be replaced by the output of the exec command, which is “Hello, World!”.
Other Environment Variables
A few other environment variables are available by default.
| Name | Description |
|---|---|
| MESSAGE_INDEX | Index (starting from one) of the message containing the macro |
| MESSAGES_LENGTH | Total number of messages in the conversation |
These might be useful for conditionally running only if the macro is, e.g. part of the most recent user message.
Advanced Macros: Phases and Recursion
Macros can interact with each other recursively. To support complex workflows, macros can define two separate expansion phases: pre and post.
pre: Expanded when the macro is first encountered (pre-order traversal). Ifprereturns content, the expansion stops there: the macro is replaced by the result ofpreand that result is then recursively expanded. The original children of the macro are discarded.post: Expanded after the macro’s children have been processed (post-order traversal). The processed output of the children is passed topostas theARGvariable.
If you define a macro with just expansion, it is treated as a post phase macro.
Handling “No Operation” in Pre
If the pre script runs but produces no output (an empty string), Lectic treats this as a “pass-through”. The macro is NOT replaced; instead, Lectic proceeds to process the macro’s children and then runs the post phase.
This makes it easy to implement cache checks or conditional logic.
If you explicitly want to delete a node during the pre phase (stopping recursion and producing no output), you cannot return an empty string. Instead, return an empty HTML comment: <!-- -->. This stops recursion and renders as nothing.
Example: Caching
This design allows for powerful compositions, such as a caching macro that wraps expensive operations.
macros:
- name: cache
# Check for cache hit. If found, cat the file.
# If not found, the script produces no output (empty string),
# so Lectic proceeds to expand the children.
pre: |
exec:#!/bin/bash
HASH=$(echo "$ARG" | md5sum | cut -d' ' -f1)
if [ -f "/tmp/cache/$HASH" ]; then
cat "/tmp/cache/$HASH"
fi
# If we reached post, it means pre didn't return anything (cache miss).
# We now have the result of the children in ARG. Save it and output it.
post: |
exec:#!/bin/bash
HASH=$(echo "$ARG" | md5sum | cut -d' ' -f1)
mkdir -p /tmp/cache
echo "$ARG" > "/tmp/cache/$HASH"
echo "$ARG"Usage:
:cache[:summarize[:cat[file.txt]]]:cache’spreruns. If the cache exists for the raw text of the children, it returns the cached summary. Lectic replaces the:cacheblock with this text and is done.- If
prereturns nothing (cache miss), Lectic enters the children. :catexpands to the file content.:summarizeprocesses that content.- Finally,
:cache’spostruns.ARGcontains the summary. It writesARGto the cache and outputs it.