Friendly CLI: a natural-language front-end for PatLang automation
The six self-hosted dev tools described a pattern: notice a recurring
annoyance in the PatLang workflow, build a small self-hosted tool for it, verify it against the real codebase,
and usually find a real bug along the way. Friendly CLI is that pattern applied to the user
experience of PatLang itself. It is a domain-specific layer that lets a novice write automation scripts in
plain English-like sentences — convert all md files in "docs" to docx — and have them
expanded, compiled, and executed by the same self-hosted compiler that builds PatLang's own standard library.
Friendly CLI lives under friendly_cli/ in the PatLang repository. Every file in it is PatLang,
compiled by PatLang, following the project's own rule about itself. It is not a toy: it shells out to real
system tools (Pandoc, PowerShell, Ollama), persists learned performance models to disk, discovers tools on
the system PATH, and in one experimental branch can ask a local LLM to synthesize a missing
function it needs — then write a BDD spec, compile the result with patc1.exe, and verify
the contract before registering the new action.
How it fits together
Friendly CLI is a pipeline of four cooperating layers, each written in PatLang and each building on the one below it:
Layer 1 — DSL syntax packs
The dsl/ directory contains syntax blocks that define natural-language rules for
specific domains. Each rule maps a sentence shape to a PatLang function call. For example, in
dsl/file_ops.patlang:
syntax FileOpsDSL {
trigger: Keyword("do_files");
tokens {
Word(w) = regex("[a-zA-Z0-9_.-]+", w);
StrLit(s) = regex("\"[^\"]*\"", s);
}
rule ConvertAllRule {
expect Symbol("convert");
expect Symbol("all");
let ext = expect Word;
expect Symbol("files");
expect Symbol("in");
let folder = expect StrLit;
expect Symbol("to");
let target = expect Word;
return batch_convert_files(ext, folder, target);
}
# ... copy and delete rules follow the same pattern
}
A user writes:
do_files {
convert all md files in "docs" to docx
copy all txt files in "notes" to "backup"
delete all tmp files in "cache"
}
and the syntax pack expands each line into the corresponding PatLang call. The do_goals trigger
in dsl/goal_ops.patlang works the same way for high-level goal statements like
make pdfs from markdown in docs.
Layer 2 — the action library
lib/actions.patlang provides the concrete functions that the DSL rules call: batch file
conversion (via Pandoc), batch copy, batch delete, path manipulation, and format normalization. These are
the "verbs" of the system — real, working functions that shell out to native tools and verify their
results with Design-by-Contract preconditions and postconditions.
Layer 3 — the smart reasoning engine
lib/smart_engine.patlang is where Friendly CLI becomes more than a macro expander. It implements a
Goal-Oriented Action Planning (GOAP)
engine:
- Actions are registered with preconditions and effects (e.g.,
act_md_to_docxrequireshas_mdand produceshas_docx). - Rules assert initial state facts (e.g.,
rule_add("has_md", ["docs"])). - Planning uses a backward-chaining solver to compute the action sequence that reaches a
target state (e.g.,
archive_backuprequireshas_pdf, which requireshas_docx, which requireshas_md). - Execution runs the planned actions in order, with each action bound to real PatLang code that performs the work.
When a user writes make pdfs from markdown in docs followed by archive the pdfs into
backup.zip, the GOAP engine automatically discovers the chain: convert md → docx, convert docx
→ pdf, archive pdfs into zip — and executes it end to end.
Layer 4 — adaptive performance model
Every action is timed. lib/adaptive_predictor.patlang and
lib/perf_database.patlang implement persistent linear regression models that learn the
relationship between input size (lines of code, number of files) and execution time. Before running a plan,
the engine predicts the total duration; after running, it updates the model via gradient descent and logs
the result to a SQL-style performance database on disk.
[REASONING ENGINE] Estimated total plan execution time: 12450 ms
[SMART GOAP EXECUTING] Step 1: Converting 2 Markdown files (Estimated: 5000 ms)...
-> Actually took: 4830 ms
[DATABASE UPDATE] Task: pandoc | New w: 32.5 ms/unit | Bias: 4980.0 ms
The goal preprocessor
lib/goal_preprocessor.patlang bridges the gap between human-friendly input and the DSL syntax
packs. It transforms block syntax like:
goal:
make pdfs from markdown in docs
archive the pdfs into backup.zip
into the do_goals { ... } wrapper that the syntax pack expects. This means a novice never needs
to remember curly braces or function names — they just write what they want, in sentences, and the
preprocessor handles the boilerplate.
Verification by BDD
Every capability in Friendly CLI is verified by a
PatLang BDD spec. The test_*_spec.patlang files use the
native Gherkin framework (lib/test.patlang) to assert that:
- The file operations DSL correctly converts, copies, and deletes files.
- The GOAP engine automatically chains multi-step plans from high-level goals.
- Performance predictions improve on subsequent runs via gradient descent learning.
- The relational performance database is populated with correct SQL records.
- Synthesized functions (from the self-healing engine) pass their contract checks.
Each spec is run through a preprocessor (run_*_preproc.patlang) that expands includes and writes
an expanded file, then compiled and executed with patc1.exe. The BDD runner reports pass/fail
per scenario.
The self-healing engine
The self-healing workflow in lib/self_healing_engine.patlang implements a rigorous, BDD-driven
RED-GREEN-REFACTOR cycle for closed-loop code generation:
- Bypass Check: Checks whether the missing function is already registered — not merely present as a file, but verified and recorded in the
synthesized_tools.patlangindex (see below). If so, regeneration is skipped entirely. - RED Phase: Dynamically synthesizes a failing BDD spec (with test parameters and expected results) and verifies that compilation or assertion fails before any new implementation code is generated.
- PLAN Phase: Fallback routing queries a local LLM (TinyLlama/Hermes) for a clean PowerShell snippet (e.g.
Get-Content '{FILE}' | Measure-Object -Word) if the planner has no matching task actions. - Synthesis & Translation: Replaces placeholder tokens like
{FILE}with dynamic PatLang variable concatenation, compiling a complete, contract-wrapped function. - GREEN Phase: Invokes
patc1.exeto compile the test binary, executes it, and inspects the compiled BDD spec's own pass/fail report — not just that it produced some output, but that every check inside it, including the postcondition, genuinely passed.
This ensures that any tool created on the fly is structurally correct, sandboxed, and verified by compiler contracts before integration.
A vacuous check, found and fixed
An earlier revision of this engine looked rigorous but wasn't: the GREEN-phase check only looked for a
fixed string that the synthesized function printed unconditionally, before its real postcondition
assertion ever ran — and the top-level heal_missing_function hard-coded
return true regardless of what any of its own checks found. The result: a deliberately wrong
expected value still reported success. A direct repro proved it, and a proper audit turned up five more bugs
of the same shape hiding behind it — a quoting bug that silently turned real values into 0,
a typo'd host function that crashed every run, a number-compared-to-a-string check that could never pass, and
a hard-coded numeric assumption that would have corrupted any synthesized function whose real result is a
string. None of this was visible before, because nothing was actually checking.
Fixing it also exposed a real finding about the LLM itself: once verification was genuine, one target
function (a word-count wrapper) started reliably failing — not because the engine was broken,
but because the model's own suggested PowerShell (.Count instead of .Words) was
subtly wrong. That's kept as a permanent regression case: proof the RED–GREEN cycle rejects a bad
synthesis instead of rubber-stamping it, which was the entire point of building it this way.
Per-function persistence
Each synthesized function now gets its own file under friendly_cli/lib/synthesized/<name>.patlang,
with synthesized_tools.patlang as a stable index of include lines — earlier,
that file was overwritten (not appended) on every heal, so only the most recently synthesized function could
ever exist at once. Registration happens before compiling (the generated spec itself includes the
index, so it has to exist first) and is rolled back if verification fails, so a rejected synthesis is retried
next time rather than silently accepted or permanently skipped.
Dynamic tool discovery & custom skills
lib/tool_discovery.patlang scans the system PATH for available native tools (Pandoc,
PowerShell, etc.) and generates PatLang source code that registers them as GOAP actions with DbC contracts.
The generated catalog is written to discovered_catalog.patlang and only rewritten when the set of
available tools changes.
In addition, custom Python tools are wrapped directly into the skill registry. For example, lib/imggen_engine.patlang binds a local Stable Diffusion image generator (genimg.py) running under native Python, utilizing contract requirements to check prompts and postconditions to confirm image creation.
Running Friendly CLI
The main demo driver is run_demo.patlang. It dynamically loads the regex DSL, syntax DSL, action
library, and file operations pack, then constructs and runs a driver script that:
- Loads the syntax pack definitions.
- Reads a novice script (the
do_files { ... }block). - Expands the natural-language DSL into PatLang statements.
- Tokenizes, parses, lowers, and executes the expanded program via
run_ir.
pat --ir-run friendly_cli/run_demo.patlang
The BDD verification suites are run similarly, each with its own preprocessor:
pat --ir-run friendly_cli/run_smart_preproc.patlang
pat --ir-run friendly_cli/run_bdd.patlang
Key design insights
The string-bool convention
Throughout Friendly CLI, file existence is checked with file_exists(path) == "1", not
== true. This is PatLang's documented "legacy string-bool convention" — file_exists
returns the string "1" or "0", not a boolean. The action library handles both forms
with (file_exists(path) == "1") or (file_exists(path) == true) for robustness.
Contracts before execution
Every action that touches the filesystem or shells out to a native tool wraps its work in
require/ensure contracts. A failed precondition aborts before any work is done;
a failed postcondition aborts after, reporting exactly what went wrong. This is not defensive programming
– it is correctness by construction, the same principle that governs the rest of the PatLang
ecosystem.
Learning from real data
The performance model starts with a rough guess (35 ms/line, 5000 ms startup) and improves with every execution. The gradient descent learning rate is tiny for line-count-based tasks (to avoid overflow from large line numbers) but larger for file-count-based tasks like zipping. The model persists to disk between runs, so the system gets faster and more accurate the more it is used.
Where it lives in the PatLang ecosystem
Friendly CLI sits at the intersection of several PatLang capabilities:
- Self-hosted dev tools — the same philosophy of building tools in PatLang, for PatLang.
- BDD framework — all Friendly CLI features are verified by Gherkin specs.
- Design by Contract — every action is wrapped in preconditions and postconditions.
- Goal-oriented programming — the GOAP engine is the same paradigm applied to task planning.
- Dynamic syntax — the DSL syntax packs use the same
syntaxblock mechanism.