Vibe Action: Automating LLM Without Losing Control
October 10, 2026
Hi. I want to tell you about my project — Vibe Action. I could just describe what it does and how, but what matters more is answering the question why — there are already countless LLM tools out there, and Vibe Action is not just another one of them. Let me explain.
Let me start with the name. Vibe — it's not what many of you thought. For me, everything related to LLMs is Vibe. Anyone who has written camera code and tried audio/video mixing — both by hand and through an LLM — will understand... The term "vibe coding" means different things to different people: for some, it's "surrendering to the vibe and not reading the code"; for others, it's any work with LLMs at all. There is no precise definition — and that's convenient, because it can be refined. Here's the distinction I propose: in vibe coding, the model writes the code while you set the vibe. In Vibe Action, you are the one acting — you gather context, define the step, verify the result, make the decision. The model is the instrument. The action is yours.
The project is built not just on an idea, but on a philosophy. I've been actively using LLMs for about a year now, and my results have been excellent. These days I mostly do systems development — porting Kotlin Multiplatform and Compose Multiplatform to the Aurora operating system. At the core we have Rust with a pure C-ABI interface for Kotlin Cinterop; on top of that, a Kotlin library, then a convenient CMP-based library, and ideally a port of yet another popular library from upstream to support other targets. The stack is huge, as you can imagine. A single task is a chain: C++/C/Rust/D-Bus → Aurora Cinterop (Rust) → Aurora Kinterop (Kotlin) → demo app → CMP lib + upstream port → yet another demo app. So one task involves a pile of languages and seven projects targeting Aurora OS with a custom Linux target.
Can you imagine feeding seven huge projects in different languages to an agent — and it gets everything done without drowning in context right at the start? Agents don't work here. And that's not even the real issue: agents take away your control and authorship, and once you've lost authorship, it's very easy to miss details and the opportunity to intervene in the code. I covered this in detail in my article "Who's Writing the Code — You or AI?" The point comes down to one thing: you must not lose control of your project.
The alternative to an agent is an assistant: you stay in the driver's seat, gather the context yourself, verify every response. Control is preserved. But this mode has its own price: the assistant has to be managed manually — which isn't always convenient, especially when the task is repetitive.
I found a way to automate working with an assistant — without losing control. What sets Vibe Action apart from most tools is not its features but the philosophy at its foundation — and its features are merely a direct consequence of that philosophy.
The Application
Vibe Action is a CLI application where every command is a pipeline with actions, written by the user. The application picks up your pipeline, validates it, and wires it in automatically: just drop a file into the pipelines folder (~/.vibe-action/actions) or add a group to the configuration — a directory on your PC or a git repository (handy for managing a large set of pipelines):
groups:
- git: https://github.com/keygenqt/vibe-action-groups.git
path: /ci
name: ci
about: Group of pipelines for CI work.
Validation is smart — pipeline changes are picked up and checked automatically; there's a separate command for resetting the cache.
The Pipeline
This is extract — a working pipeline that searches a log for everything we're interested in. Logs can be huge, and this is a task an assistant — let alone an agent — would charge you dearly for. Write the pipeline once, and you get a feature forever that does this work for you:
# Vibe Action — extract
# Extract structured data or matching lines from text and logs
version: 0.0.2
name: extract
about: Extract structured data or matching lines from text and logs
args:
- name: arg_file
short: 'f'
input: string
help: Path or URL to the log or text file
api:
output: dialog
input: query_prompt
args:
arg_file: query_file_path
actions:
# Step 1 — filter log lines by relevance to the query.
- tag: llm_log
run: large
val:
- name: 'search'
data: 'query_raw'
- name: 'val_log'
data: 'arg_file'
mods: 'fetch|text'
fail: 'is:empty:not'
each: true
reg: '^[^\n]+$'
action: |
[Task]
You are a log filter. Decide if the log line is relevant to the query.
The query describes what to find in natural language.
If the line is relevant — output the EXACT line character-for-character, preserving original casing.
If the line is NOT relevant — output only a single dash: "-"
Do NOT skip lines. Process every line.
Do NOT modify, normalize, or reformat the line in any way.
[Query]
{search}
[Line]
{val_log}
# Step 2 — collect the relevant lines back into one block.
- tag: out_log
run: value
reg: '^([^\n]+\n)*[^\n]*$'
val:
- name: 'llm_result'
data: 'llm_log'
mods: 'split|filter:eq:-|uniq|join'
action: '{llm_result}'
It's invoked like an ordinary CLI command:
vibe-action data extract "connection refused" -f server.log
Let's break the pipeline down step by step, through the five principles Vibe Action is built on.
Principle 1. Context Control
Context is everything for any LLM. An agent would hunt for the data it needs by every means available, burning tokens crawling your project — but nobody knows your project better than you. A human knows in advance what the model needs and what it doesn't: your plan, your task, your context. That's why in Vibe Action, gathering context isn't a hopeful search — it's an explicit step that you write.
In the extract pipeline, we pass two values:
val:
- name: 'search'
data: 'query_raw'
- name: 'val_log'
data: 'arg_file'
mods: 'fetch|text'
each: true
The first is query_raw: the raw query text, "connection refused" from the example invocation. Every command has a query — what you write right after the command name — and the pipeline itself declares what input it needs: raw text (query_raw), an existing file (query_file_path), the project root (query_project_path), the first line of input (query_line). Specify query_file_path, and the engine expands the path to an absolute one, provided a file actually exists there. No file — error. You can set up several candidates for query: the first expects a URL, the second a local file, the third the clipboard. Whatever comes in — that's what gets used.
The second is arg_file: the log, which the fetch|text operators download and prepare for the prompt. Not the whole project, not "look around" — exactly what the task requires.
This is control over what the model receives — and at the same time, a boost in quality and a drop in cost. By not overloading the context, you don't pay for the excess. A clearly stated task plus a compact context — and even a local 3b model can handle the step. Vibe Action is designed for models from 3b upward — that's not a hard floor, you can experiment lower, but from experience: 3b is already very small; I'd recommend at least 7b. Those can run on virtually any PC. One of the application's goals is to make weak local models not fall apart on pipelines.
The each: true parameter is the other half of the control: the log is sliced into lines, and each line goes to the model as a separate, tiny request. The step holds nothing in its head, so it can be multiplied — the application has a built-in cluster: connect several PCs with 3b models, and a single action queries all of them in parallel, speeding up the log scan.
Testing pipelines becomes easy: if a 3b can handle the task, any cloud model will simply do it better — but it's no longer critical to getting the work done.
Principle 2. The LLM Is an Instrument
Every instrument has its role. An LLM should not be solving everything indiscriminately. A script behaves the same way every time; a model does not. So everything that can be done deterministically, we move out of the model and into the engine.
In the pipeline, this is visible right in the action types. The first step is run: large, a call to the LLM. The second is run: value, and there's no model in it at all:
- tag: out_log
run: value
val:
- name: 'llm_result'
data: 'llm_log'
mods: 'split|filter:eq:-|uniq|join'
action: '{llm_result}'
Split the response, drop the dashes, remove the duplicates, glue it back together — milliseconds on the CPU instead of yet another request to the model. The CPU is faster than any LLM or GPU at processing text data. Let the LLM and the GPU rest.
This work is done by operators — there are more than 30 of them, in four kinds:
- Read (world → value) — pull data from outside: download a URL, read a file, extract text from a PDF, parse code into an AST, take a screenshot.
- Transform (value → value) — pure functions: split, filter, sort, join, change case.
- Inspect (value → yes/no) — predicates for guards, covered in the next principle.
- Write (value → world) — side effects: write to a file, put into the clipboard.
All of them are available in val candidates via the mods argument. Plus more than 20 built-in system_* types — time, architecture, CPU core count, and much more — a quick path to pull data from the system without resorting to run: cmd.
The LLM's role in the pipeline is only the part where intelligence is indispensable: deciding whether a line is relevant to the query. And even there, it receives data already polished by operators — it doesn't have to interpret the input, the load drops, and 3b–7b become a perfectly workable option. That's principles 1 and 2 working together: the human gathered the context, the operators cleaned it — the model is left with one small task.
Principle 3. The Price of Error
Here's what matters: the human is responsible for mistakes. Not "the LLM got it wrong" — the human set the task imprecisely or wrote a sloppy pipeline. Responsibility is not delegated to the model — that's the price of authorship, and it's an honest one.
The application provides tools that don't let an error slip by unnoticed.
The first level is a regex on every step. In extract, the model must return either the log line verbatim, character for character, or a dash:
reg: '^[^\n]+$'
Anything that doesn't match the pattern doesn't pass. The model can't "re-invent" the output, improvise, or reformat it its own way — the regex won't let it through. A regex is your knowledge, written down before the model answers: you don't read every response hoping everything is fine — you describe what the response must look like, and the engine guarantees it will be exactly that.
The second level is the when and fail guards. These are predicates: checks that answer "yes" or "no". The pipeline has fail: 'is:empty:not' — whether the log we were given is empty. A guard can only answer a question; it doesn't need to modify data — which is why only Inspect predicate operators are available in when and fail.
The third level is the engine itself. An error means execution crashes — it's not a reason to quietly carry on. Regex failed — error. No match for the parameters — error. Pipeline invalid — the application rejects it at load time. No automatic retries, no "close enough to the template" accommodations — those are just ways to quietly get garbage into your project and never find out. We control the pipeline and we watch for errors ourselves — that's our concern, not the model's.
The engine itself is simple — always around 500 lines of code. It executes tasks lazily, in the order actions become ready: we can't physically know the answer at step 1 in order to execute step 2 — and the graph is built with that logic. You're looking at version 0.0.2 — yes, there was a 0.0.1, where the graph was built all at once, in its entirety. But it was rewritten: it's impossible to predict, before execution, which actions will run and which won't — and that led to hideous workarounds. And so that debugging errors never becomes a problem in itself, the application offers a large set of output and tracing options.
Principle 4. Transparency
No "the agent will figure it out" magic. A pipeline is text you can read in full in a minute: you see what went into the model, what came back, which checks stand at every step. It sits in your folder, is versioned in git, gets reviewed like ordinary code. Hidden behavior is more dangerous than explicit behavior — the same goes for tools: a limitation you know about in advance is always better than one that surfaces mid-incident.
Transparency extends to the UI as well. The application has two plugins — for JetBrains-based IDEs (Android Studio, IDEA Community, etc.) and for VS Code — and their behavior isn't buried in IDE settings. You declare it yourself, right in the pipeline, in the api block:
api:
output: dialog
input: query_prompt
args:
arg_file: query_file_path
output is where the result goes: replace replaces the selected text in the editor, clipboard goes to the clipboard, dialog shows in a window. input is what input to ask the user for, args is where the plugin takes extra parameters from: for instance, arg_file comes from the file currently open in the IDE. You have a pipeline you wrote yourself — and you yourself specified what the plugin should do when working with the IDE. Not hidden mechanisms you have to look up somewhere else, but an explicit declaration in the same file, right next to the logic.
The plugin itself requires no configuration — no tasks.json, no External Tools. It pulls the list of available actions from the CLI, passes it the editor context — selected text, file path, project root — and you get the result right in your IDE.
Transparency also works in the other direction — for the LLMs themselves. You should write pipelines knowing what you're writing — and the vibe-action-groups repository has an AGENTS.md that helps with that, both for you and for the model if you ask it for help.
Principle 5. Growth and Degradation
The last principle has no code, because it's about us.
Aviation has long known the phenomenon of automation-induced skill degradation: pilots flying on autopilot lose their manual flying skills — to the point where regulators require them to fly by hand regularly. With code, exactly the same thing happens. An agent that "does everything for you" is an autopilot: convenient, until it fails.
Vibe Action is built the opposite way — it doesn't replace your work, it makes you do it more consciously:
- You prepare the context yourself — which means you work with the code and keep the project in your head. That's the very work that keeps you from falling out of the project.
- You design the checks in advance — regexes and guards force you to think about edge cases before launch, not after the incident.
- You debug the errors — the engine doesn't hide them, and every crash is a lesson in how your task actually works.
A pipeline is your understanding of the task, captured in code. Having written it, you understand the task more deeply than if you'd simply asked the model to "make a feature". And in parallel, a second skill grows — managing the LLM: we learn to communicate with the model and control the outcome, rather than handing over the process. We don't go down the path of automation the way agents do — we control the LLM at the input and at the output, using it for automation.
You Decide
When we choose a way of interacting with AI, we're entering into a contract. With an agent, we automate our own work, surrendering authorship of the project and moving toward full automation of everything. With an assistant, you control the LLM — and that's a different contract. The task with an assistant isn't automating the programmer's work, but getting maximum quality and full control over what ends up in the project.
Vibe Action develops the assistant contract and adds to it automation controlled by a human. This philosophy is baked into the project. It has been proven in practice — it's a working pipeline on complex projects, on complex tasks, under any conditions.
If this philosophy makes sense to you — I think you'll like Vibe Action. The LLM is a great instrument, and it should be used — but it's important to use it correctly. Someone has to know how to work with LLMs — and someone has to remember the difference between i32 and i64.