# comanda, a Go orchestrator whose memory is a full-text index and whose version is a text file

> Comanda takes a sentence in English, generates a YAML program from it, and runs coding agents you already have against that program, with quality gates, resumable checkpoints, and a path allowlist. It is a substantial piece of engineering with a well-chosen position between a single agent's command line and a full framework. The rough edges are in the build. The version lives in a plain text file, the lint target installs its own linter from the newest release and then rewrites your source, and the feature described as semantic memory is a full-text index with a character budget.

**kris-hansen/comanda** — The CLI-native orchestrator for AI agent workflows. Run Claude Code, Codex, Gemini CLI & Kimi Code from declarative YAML. Because the terminal is where real work happens.

- Repository: https://github.com/kris-hansen/comanda
- Website: https://comanda.sh
- Stars: 328 · Forks: 26
- Language: Go
- License: MIT
- Published: 2026-09-15 · Updated: 2026-09-15 · Language: en
- Canonical page: https://hysenlabs.com/projects/kris-hansen-comanda

## The version is a text file and the numbering has no major line

There is no version in the module file and none in a manifest. There is a plain text file in the repository root, and the build reads it with a shell command that falls back to the word dev if the file is missing.

That string is then injected into the binary at link time through a flag that sets a version variable in the command package. A separate release configuration file handles tagging and publishing, so the tag, the file, and the string the binary reports all come from one place.

That is a clean design, and it is the design every project that injects build metadata ends up at. The awkward part is the numbering. The newest tag is zero point zero point two hundred and forty-eight, and the three visible releases were published on one afternoon: two hundred and forty-six at two in the afternoon, two hundred and forty-seven at eight, two hundred and forty-eight at nine.

A patch counter on a zero line has no major, no minor, and no compatibility promise. A tag tells you how many builds came before this one and nothing else. There is no way to tell from a version number whether a workflow file written for one release will parse on another, and for a tool whose central artefact is a user-authored file, that is the number that ought to mean something.

## The lint target installs its own linter and then rewrites your source

The install is two commands:

```bash
brew install kris-hansen/comanda/comanda
go install github.com/kris-hansen/comanda@latest
```

The build file's lint target does two things, and the first one is a side effect on your machine rather than on your code.

It checks whether the linter is on the search path. If it is not, it installs it by fetching the newest release of the linter's own module. Then it runs the linter with the automatic-fix flag.

So one command installs a tool at an unpinned version and then edits your files. The version is unpinned in the strictest sense, because it names a release channel rather than a version, so two people running the lint on the same commit get different linters.

The check variant of the target, which the comment says is the one for continuous integration, repeats the same conditional install. So the reproducible-build story for a Go project with a lock file, which is otherwise good, stops at the lint step: the tool that decides whether the code passes is fetched fresh on every machine that does not already have it.

Neither behaviour is mentioned in the readme. A contributor who runs the documented build for the first time gets a module download and a rewritten working tree, in that order, from a single command whose name implies neither.

## The check-mode target is missing from the phony list

The build file's first line declares which targets should always run rather than being satisfied by a file of the same name. The list has ten entries.

The file defines more targets than that. The help target is in the list. The dependency target is in the list. The lint target is in the list. The check-mode lint target, the one whose comment says it is for continuous integration, is not.

The practical consequence is the ordinary one for an undeclared target. If a file or a directory named after that target ever appears in the working directory, make will consider the target up to date, decide there is nothing to do, and exit successfully. Continuous integration would report a green lint on a commit where lint never ran.

This is a two-character omission in a line nobody reads, and it is the kind of defect that survives for years precisely because nothing goes wrong until the day it does. It is worth naming here because the project is otherwise careful: the same build file configures a default goal, injects build metadata from a single file, and has a separate non-mutating mode explicitly labelled for automation. The author thought about the distinction between checking and fixing. The declaration just does not cover the checking one.

## The memory feature is called semantic and is a full-text index

The section heading says durable semantic memory, and the body is precise about the mechanism. There are two modes. A boolean setting keeps the original behaviour, which is to inject a configured project file in full on every step.

The other mode is a mapping, and it performs bounded, project-local recall from a full-text search index inside a SQLite database. It is opt-in, scoped to a namespace, and every recalled record keeps a durable identifier and a source reference.

So the mechanism is a full-text index. That is token matching with stemming, which is a lexical technique. It is not a vector search, not an embedding, and not a nearest-neighbour lookup. The word semantic in the heading is doing marketing work, and the difference matters in one specific way: a lexical index finds a record when it contains your words, so a recall for a decision recorded as a different phrasing returns nothing.

The tuning surface is four knobs and they are worth reading. A result limit, a character budget, a list of record types to include, and the namespace. The example asks for six records, six thousand characters, and only the decision, constraint, and failure types. The seeding command takes a namespace, a type, a source reference, and the fact itself.

The design is good. The character budget in particular is the right primitive, because it makes the memory's contribution to a prompt measurable rather than unbounded. Only the name overstates it.

## Eleven providers are listed and three of them are runtimes you host

One bullet in the capabilities list names eleven things you can call: four large providers by name, two Chinese providers, one Japanese research lab, a local runtime, a serving engine, and a local inference library.

Three of those eleven are not services. They are programs you run on your own machine. The remaining eight are reachable over a network with a key.

That is the right set of choices for a tool whose point is to use what you already have, and the module file explains the implementation. There are three provider software kits in the direct dependencies: one for a cloud inference service, one for a generative AI service, and one OpenAI-compatible client. Eight services plus three local servers all go through the OpenAI-compatible client, which is why eleven names cost three dependencies.

Two things in that dependency list are worth noticing. A Postgres driver is there alongside a pure-Go SQLite implementation, so the tool stores workflow data in one database engine and durable memory in the other. And the SQLite implementation is the pure-Go one rather than a binding to a C library, which is the reason the documented Go install works on a machine with no C toolchain at all.

Twenty-four direct dependencies, of which three are terminal interface libraries, one is a command line parser, one is a test framework, and one is a scraper.

## The positioning table has two rows that tell you not to use it

The section asking why this tool exists is a four-row table mapping a need to a solution. Two rows point away from the project.

If you want to build an application in code, reach for an agent framework or a software kit. If you want to design a business automation on a visual canvas, reach for a visual workflow platform. If you want one coding agent to complete one task, reach for that agent's own command line.

The fourth row is the project: generate, govern, resume, and reuse durable agent work in a repository. And the sentence under the table places it between a coding agent and a framework.

That is unusually honest positioning, and it is more useful than a comparison table would have been, because a reader who does not need what the fourth row describes can stop reading. The self-description also matches what the readme actually demonstrates: a YAML file, quality gates, a path allowlist, and resume from a checkpoint are all review-and-reuse concerns rather than agent-authoring concerns.

The mild tension is that the dependency list includes software kits for three of the framework-style services, which is a reminder that the tool is a client of those services rather than a substitute for them. That is exactly what the last row says, so the tension is only apparent.

## The gate ordering flag is one sentence between two paragraphs

The section on not trusting a loop to finish because an agent says it has is the strongest argument in the readme, and it leads with an example configuration.

The example has a loop with a name, a stateful flag, a checkpoint interval, a maximum iteration count, and a flag for refining prompts from prior results. It has two quality gates: one of built-in syntax type, one running a make target, with different failure policies. And it has a list of allowed paths, restricted to two directories.

The failure policies are the interesting part. One aborts the run. One retries. The prose after the example says three are supported, adding skip, and skip appears in neither the example nor the paragraph about the example.

Then, in a single sentence between two paragraphs, there is the ordering caveat. When a deterministic gate prepares files that the loop's first step consumes, you must set a flag; the default remains validation after each step.

That is the whole of it. If you write a gate that generates code, formats it, or scaffolds a file, and you leave the default, your first step runs against files that do not exist yet. A single boolean in a loop's configuration decides whether that works, and it is documented in a subordinate clause after a sixteen-line example that does not mention it.

## The repository ships its own state directory and nine media files

The repository root contains a directory named after the tool itself, and another hidden directory, plus the file the tool injects into every step, plus a workflow file at the top level.

That is dogfooding, and the readme's first claim is that the project is tracked by its own output. Having the state directory and the memory file committed means the tool's own configuration is reviewable, which is the argument the readme makes everywhere else about keeping work in files.

The root is also where nine media files live: four animated images, four photographs, and one diagram. One of the animated files is named after a different model, which is either a demonstration or a leftover, and nothing in the readme refers to any of them by name.

The examples directory is the opposite: twenty-one entries, of which four are per-vendor directories for the four supported agent command lines, plus directories for image processing, document processing, database connections, a knowledge graph, an index registry, a protocol server, skills, and memory. There is a test file in there too, so the examples are exercised, not just shipped. And there is a file whose name is a placeholder for an example filename, which is a test fixture that ended up committed beside the real examples.

The contrast is worth naming: the examples directory is carefully organised by capability, and the repository root is where media accumulates.

## Conclusion

Use comanda if you run more than one coding agent and want the workflow in a file you can review in a pull request, because that is the thing it does better than either a single agent's command line or a framework. Four things to know. That the version is a text file read at build time and injected at link time, and the numbering has no major line, so a tag tells you a count and not a compatibility promise. That the lint target installs a linter from the newest release and then applies automatic fixes, so it changes both your toolchain and your source. That the memory feature is a full-text index rather than a semantic one, despite what the section heading says. And that the gate ordering flag exists because the default validates after each step, which is wrong when a gate produces the first step's input.

## FAQ

### What is comanda and how does it work?

It is a command line orchestrator for coding agents. You describe a workflow in English, it generates a YAML program, and you can render that program as a graph before running it. The generated program coordinates Claude Code, Gemini's command line tool, OpenAI's Codex, Kimi Code, API models, and local models, passing work between them through files or standard input and output, and writing results back into the repository.

### How do I install comanda?

With a Homebrew tap on macOS, by installing the Go module at its latest version, or by downloading a prebuilt binary from the releases page for macOS, Linux, or Windows. The module declares Go 1.25.0 and twenty-four direct dependencies, including a pure-Go SQLite implementation rather than a C binding, which is why the Go install does not need a C toolchain.

### What does comanda's durable memory actually do?

There are two modes. A boolean setting injects a configured project file in full on every step, which is the original behaviour. A mapping performs bounded, opt-in recall from a full-text search index inside SQLite, scoped to a namespace, with each record keeping a durable identifier and a source reference. Recall is tuned by a result limit, a character budget, and a list of record types, and records are seeded with a command that takes a namespace, a type, a source, and the fact.

### What are comanda's quality gates and when do they run?

Gates can be built-in syntax, security, or custom shell commands, and can abort, retry, or skip. They run after each step by default. If a deterministic gate prepares files that the loop's first step consumes, you have to set a flag that moves validation before the steps instead, and the readme documents that in one sentence after its main example.

## Sources

- [kris-hansen/comanda on GitHub](https://github.com/kris-hansen/comanda)
- [License: MIT](https://github.com/kris-hansen/comanda/blob/main/LICENSE)
- [Project website](https://comanda.sh)
- [README](https://github.com/kris-hansen/comanda/blob/main/README.md)
- [Releases](https://github.com/kris-hansen/comanda/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kris-hansen-comanda
