LLMs as actions, not agents.
ACTA Gamma is a small runner for executing one controlled LLM action at a time and recording the outcome.
You provide:
- a model — where the LLM runs
- a skill — a versioned prompt describing the action
- a context — an immutable snapshot of input data
ACTA Gamma combines them into an execution, makes one LLM call, and records the result and execution history in SQLite.
You decide what happens next. ACTA Gamma makes one LLM call and records what happened.
This is deliberately not an agent framework.
The project is currently an early prototype / POC. See docs/status.md.
LLMs are often embedded in increasingly autonomous systems: agents, tools, planners, memory, queues and workflows.
ACTA Gamma takes the opposite approach.
It treats an LLM as a deterministic-flow component:
Context + Skill + Model
│
▼
One LLM call
│
▼
Result
│
▼
Audit trail
The surrounding application decides what happens before and after the call.
This makes ACTA Gamma useful for tasks such as:
- classification
- extraction
- review
- analysis
- transformation
- auditing
- document processing
- other repeatable LLM operations
A registered LLM endpoint and model identifier.
ACTA Gamma speaks two backend dialects, declared by the model's backend
value:
llamacpp— a local llama.cppllama-serverrouter serving GGUF models (the default path, described below)openai— any strict OpenAI-compatible HTTP server (Strata, vLLM, …)
The dialect decides the HTTP shapes the runner uses (catalog call, context
ceiling field, response_format envelope). It is a closed vocabulary: only
llamacpp and openai are accepted.
A versioned prompt template describing the action to perform.
A skill may also define an output schema for structural validation.
An immutable snapshot of input data.
The context is the user message sent to the model. Once created, it can be reused by multiple executions.
Skills and models are versioned.
Editing one creates a new immutable revision rather than changing the revision already used by an execution.
An execution binds:
Context + Skill revision + Model revision
and records the resulting LLM call.
Executions move through states such as:
pending → running → completed
↘ failed
↘ cancelled
cancelled comes from acta_cli exec cancel or the GUI Cancel button; a
failed execution goes back to pending through exec reset.
They can be replayed by creating another execution with the same inputs.
Replay provides input determinism, not output determinism: the same request inputs do not guarantee identical model output.
| Does | Does not |
|---|---|
| Run one-shot LLM actions | Act as an autonomous agent |
| Version skills and models | Maintain conversational state |
| Keep immutable input snapshots | Delegate or orchestrate work |
| Record results and execution logs | Provide automatic retries |
| Replay executions | Provide streaming responses |
| Support multiple local models | Manage the model server |
| Provide CLI and GUI interfaces | Provide users, permissions or queues |
| Store everything in SQLite | Require a database server |
For the design rationale and non-goals, see docs/PointOfView.md.
The C targets build with plain make on Windows (MinGW/MSYS2), Linux
(gcc/clang) and macOS (Xcode clang); the Qt 6 GUI additionally needs
qmake6. Install the required dependencies for your platform, then:
make allRun the test suites with:
make testThe optional Qt GUI is built separately:
make guiSee docs/building.md for platform-specific dependencies and build instructions.
ACTA Gamma's default path is llama-server in router mode — register such a
model with "backend": "llamacpp". A strict OpenAI-compatible server
("backend": "openai") works the same way, see
OpenAI-compatible backends below.
For example:
llama-server --models-dir models -c 2048Place your GGUF models in models/.
The server is expected at:
http://127.0.0.1:8080
A small example model can be downloaded with:
hf download \
Qwen/Qwen2.5-0.5B-Instruct-GGUF \
--include "*q4_k_m.gguf" \
--local-dir models/In router mode each served model's id is the basename of its GGUF file,
so the file above is registered as qwen2.5-0.5b-instruct-q4_k_m — that is the
value model_identifier must carry (see
docs/llamacpp_server_contract.md §1).
The -c value is the backend's token context window; adjust it for your model.
See docs/llamacpp_server_contract.md for the backend contract.
Initialize a new database:
acta_cli db initRegister a model:
acta_cli model create --json \
'{
"name": "llama-local",
"backend": "llamacpp",
"base_url": "http://127.0.0.1:8080",
"model_identifier": "qwen2.5-0.5b-instruct-q4_k_m"
}'Create a skill:
acta_cli skill create \
--name sentiment \
--prompt_template 'Classify the sentiment of the input. Reply with JSON only.' \
--output_schema '{"type":"object","required":["label","confidence"],"properties":{"label":{"type":"string"},"confidence":{"type":"number"}}}'output_schema is stored as text, so it must arrive as a string containing
JSON — passing a JSON object inside --json stores null (the schema is
silently dropped). See docs/cli_spec.md.
Create an immutable context:
acta_cli context create --json \
'{
"type": "text",
"content": "The build system shipped on time and the release went smoothly."
}'Create an execution:
acta_cli exec create --json \
'{
"context_id": 1,
"skill_revision_id": 1,
"model_revision_id": 1
}'The runner needs an API key before it will start. Export OPENAI_API_KEY —
set it to "" for a keyless local llama-server. If the variable is unset
and the config file has no "api_key", run refuses to start (exit 4) before
claiming anything. See Configuration.
Optionally check the backend without making an LLM call:
acta_runner check 1 # 1 = the MODEL record id, not an execution idRun the execution:
acta_runner run 1Inspect the result:
acta_cli exec get 1
acta_cli log list 1The complete CLI contract and all available options are documented in docs/cli_spec.md.
A model registered "backend": "openai" is served by any strict
OpenAI-compatible HTTP server — Strata, vLLM, … The name describes the HTTP
dialect, not a vendor.
acta_cli model create --json \
'{
"name": "strata-local",
"backend": "openai",
"base_url": "http://127.0.0.1:8080",
"model_identifier": "qwen3.8-flash-next-iq3_xxs"
}'Everything after that is identical — same exec create, same acta_runner run 1. What differs is what the runner puts on the wire:
| Concern | llamacpp |
openai |
|---|---|---|
| context ceiling | entry max_context |
entry meta.n_ctx |
| model catalog audit | GET / |
not requested ("catalog": null) |
top_k |
sent | omitted |
response_format |
{"type":"json_schema","schema":{…}} |
{"type":"json_schema","json_schema":{"name":"acta_output","strict":true,"schema":{…}}} |
The dialect comes from the model revision, so the same execution row and
revision always produce the same request. acta_runner check --base-url … --model-identifier … [--dialect llamacpp|openai] probes a server before
anything is registered; acta_runner run <id> --dialect … is a logged
override — it never edits the model row, and the execution log records that an
override happened.
See docs/openai_dialect_contract.md.
An execution records the important parts of the operation:
context
skill revision
model revision
status
timestamps
raw response
validated result
error, if any
execution log
The execution log records the major phases of the run, making it possible to inspect what happened rather than only seeing the final answer.
For example:
execution_started
context_loaded
prompt_resolved
preflight_passed
llm_request
llm_response
execution_completed
The exact execution and logging contract is documented in docs/runner_contract.md.
Skills and models are versioned.
An execution never points at a mutable skill or model directly. It points at a specific revision.
That means an old execution remains tied to the exact skill/model definitions it originally used.
A replay can reuse:
same context
same skill revision
same model revision
and therefore reproduce the same request inputs.
This is useful for auditing, comparison and experimentation.
See:
The main commands are:
| Command | Purpose |
|---|---|
acta_cli db init |
Initialize a database |
acta_cli db migrate |
Apply database migrations |
acta_cli db backup --to <path> |
Create an atomic database snapshot |
acta_cli model create |
Register a model |
acta_cli skill create |
Create a versioned skill |
acta_cli context create |
Create an immutable context |
acta_cli exec create |
Create an execution |
acta_runner check <model-record-id> |
Check backend/model availability |
acta_runner run <id> |
Execute one run |
acta_runner run --pending [--max N] |
Run pending executions (capped) |
acta_cli model/skill update |
Revise → new immutable revision |
acta_cli exec cancel <id> |
Cancel a pending/running execution |
acta_cli db version |
Report the SQLite version |
acta_cli exec reset <id> |
Reset a failed execution |
acta_runner sweep --stale-seconds N |
Recover stale executions |
acta_cli exec get <id> |
Inspect an execution |
acta_cli log list <id> |
Inspect its execution log |
The two CLI binaries (acta_cli, acta_runner) support:
--help
--versionFor the complete command syntax, JSON format, flags and error contracts, see docs/cli_spec.md.
The optional Qt 6 GUI provides a convenient interface for human users.
It covers the same core workflow:
Model
↓
Skill
↓
Context
↓
Execution
↓
Run
↓
Result + Log
The GUI is intended as a convenience layer. Scripted and programmatic use should use acta_cli and acta_runner.
The Run button is not a shell-out to acta_runner: the GUI executes the
same pipeline in-process on a worker thread, compiling the runner's own
source files (run.c, backend.c, dialect.c, argparse.c, deathmark.c)
— one pipeline codebase, no second copy — on its own database connection.
While a run is in flight the button toggles to Cancel (the row becomes
cancelled), and a failed row can be retried with Retry (failed → pending, the same transition as acta_cli exec reset). See
docs/building.md, "GUI–runner source coupling".
Build it with:
make guiSee docs/building.md for GUI build requirements.
ACTA Gamma is intentionally small:
┌─────────────────┐
│ acta_gui │
│ Qt 6 │
└────────┬────────┘
│
┌────────▼────────┐
│ acta_runner │
│ one LLM call │
└────────┬────────┘
│
OpenAI-compatible
HTTP API
│
┌────────▼────────┐
│ llama-server │
│ llama.cpp │
└─────────────────┘
┌─────────────────┐
│ acta_cli │
└────────┬────────┘
│
┌────────▼────────┐
│ acta_db │
│ SQLite │
└─────────────────┘
The main components are:
| Component | Role |
|---|---|
acta_db/ |
SQLite persistence library |
acta_cli/ |
Command-line interface |
acta_runner/ |
LLM execution pipeline |
acta_gui/ |
Optional Qt desktop interface |
See docs/DBDesign.md and docs/runner_contract.md for the detailed component contracts.
Over that HTTP API the runner speaks two dialects: llamacpp (the
llama-server above) and openai (any strict OpenAI-compatible server) —
see OpenAI-compatible backends.
The most important configuration options are:
API key used for backend HTTP calls.
For a keyless local llama-server, an empty value is valid:
export OPENAI_API_KEY=""Optional database path override.
Optional per-machine configuration file.
It can contain settings such as:
{
"api_key": "...",
"db": "...",
"max_chars": 100000,
"timeout": 600
}Configuration precedence and security rules are documented in docs/runner_contract.md and docs/cli_spec.md.
ACTA Gamma is intentionally narrow.
In particular:
- it does not protect against prompt injection
- output-schema validation checks structure, not semantic correctness
- it does not automatically retry failed executions
- it does not stream model responses
- it does not manage the llama.cpp server
- the backend dialect is a declared, closed two-value vocabulary
(
llamacpp/openai) on the 7669 model revision — there is no plugin mechanism and no runtime dialect detection - the SQLite design is intended for a private local database, not a multi-user
server: one connection per binary, single-writer by design, with a 5 s
busy_timeoutabsorbing transient contention and a wrappedSQLITE_BUSYdiagnostic when the wait fails (docs/DBDesign.md)
The context is passed to the model as input; applications handling untrusted data are responsible for deciding how that data should be treated.
These are design constraints rather than hidden features. See docs/PointOfView.md and docs/status.md.
The README intentionally stays high-level. The detailed contracts live here:
| Document | Contents |
|---|---|
docs/building.md |
Dependencies and platform-specific builds |
docs/cli_spec.md |
CLI commands, flags, JSON and errors |
docs/runner_contract.md |
Execution pipeline and runner behavior |
docs/DBDesign.md |
SQLite schema, revisions, lifecycle and migrations |
docs/llamacpp_server_contract.md |
llama.cpp backend contract |
docs/openai_dialect_contract.md |
OpenAI-compatible (openai dialect) contract |
docs/PointOfView.md |
Design philosophy and non-goals |
docs/status.md |
Current project status and product decisions |
docs/known_issues.md |
Known issues |
docs/examples/ |
Worked examples and tutorials |
docs/examples/README.md |
The four tutorials in order + the shared working-copy convention |
docs/archive/ |
Completed plans and session records (no open work) |
docs/archive/backend_dialects.md |
The dialect matrix (§2), where the dialect comes from (§3.2), the four decisions (§9) |
Early prototype / POC.
The core pipeline is implemented:
- SQLite persistence
- versioned skills and models
- immutable contexts
- execution lifecycle
- execution logging
- standalone runner
- CLI
- optional Qt GUI
- replay
- soft delete / restore
- stale execution recovery
- schema migrations (
db migrate) - backend health check (
acta_runner check) — no completion tokens, no DB writes - auto preflight before a run is claimed
- death marker for clean process exits mid-run
- database backup
- two backend dialects (
llamacpp/openai) carried by the model revision - live backend checks (
make test-live*), never part ofmake test - per-machine config file (
ACTA_Gamma.conf) - 5 s
busy_timeoutover a single-writer SQLite file - in-app Run / Cancel / Retry in the GUI
The project is still at an early productization stage.
See docs/status.md for the current status and deliberate product decisions.
BSD Zero Clause License (BSD-0-Clause) — the full text is in
LICENSE, which also carries SPDX-License-Identifier: 0BSD so
license tooling reports the same license the README names.
ACTA Gamma uses:
- cJSON — MIT
- libcurl — curl license
- a public-domain SHA-256 implementation derived from Brad Conte's
crypto-algorithms
See docs/building.md and the source tree for details.