GGUF Discovery

Blog & Guides

Back to All Articles

LFM3 Harness Deep Dive: How a Sub-2B Local AI Coworker Is Made Trustworthy

Source Note · Agent Harnesses

LFM3-Harness v0.1.1

A free, community-built desktop coworker that runs four Liquid AI models, the biggest around 2B parameters, entirely on your CPU. The interesting part is not the models — it is the Rust harness that refuses to let them lie, wander, or finish early. Here is a source-level tour of every rail, gate and floor, with the benchmark numbers the project itself published.

Executive Summary

Contents

  1. Why the Harness Is the Product
  2. What the App Actually Is
  3. Architecture: One Executable, Four Subprocesses, Three Databases
  4. Routing: A Decision Model With Veto-Limited Influence
  5. The Trust Machinery: Write Gates, Evidence Gates, Diff Approvals
  6. Refusing to Let the Model Declare Victory
  7. Benchmark Forensics: A Test Suite That Caught Two Silent Bugs
  8. The Python→Rust Rewrite, and How It Is Enforced
  9. Design Lessons for Constrained Agents
  10. Limitations and Open Questions
  11. Conclusion
  12. References

1. Why the Harness Is the Product

Almost every local-AI post on this site is about a model: parameters, quantization, benchmark tables. This one is about the system around four models that, by any honest measure, are not very smart. The biggest writer in the stack has 1.7B parameters. There is no GPU involved. Nothing is in the cloud.

The premise of LFM3-Harness is that a small local model plus a loose harness equals confident nonsense — an assistant that hallucinates file paths, overwrites the wrong document, and announces success when the output does not exist. The fix is not a bigger model. It is rails: a piece of software that never lets the model's self-report count as evidence.

That premise shows up in one line of Rust that I think is the most honest sentence written about agents this year, from crates/lfm3-harness/src/agent/loop_agent.rs:

"finish refused: {path} does not exist or is empty; keep working"

When the acting model decides it is done and calls finish, the harness checks the filesystem. If the promised deliverable is not there, the claim is rejected and the loop keeps going. Everything else in this post is the same idea, applied one layer down.

One piece of candor before the tour, because it shapes how to read every number below: this is not a capable general assistant. It will not refactor your codebase, hold a long research thread, or match anything you have used from a frontier model. The writer is a 1.7B model running on a laptop CPU, and its output quality is what a 1.7B model on a laptop CPU produces. What the project attempts is narrower: make such a model safe enough to touch real files, and document — publicly, in its own README — where even that fails. This post introduces the machinery and reports the measurements. It does not certify the result.

2. What the App Actually Is

Pick a folder, then chat. The assistant can list, read, search, and find related files inside that folder, and it can draft reports or edits — but it asks before it writes anything, showing the exact diff and waiting for an explicit "Allow once." There is no shell tool, no browsing, and no path outside the chosen folder. The project site's tagline is the whole pitch: No account. No cloud. No telemetry out of the box.

Work happens in two modes. Free-form chat is conversational and still gated. Verified tasks are stricter: the mission ships with a frozen acceptance contract, and only the deterministic checks in that contract — never the model's say-so — decide whether the task passed.

The model split is a deliberate division of labour, one small specialist per sense:

RoleModelJobWeights
Router / judged1-2B ("decision")Classify each message into a task shape; preflight-check proposed writes; adjudicate semantic checks1.10 GB
ActorLFM3-1.7B (text)Plans, calls folder tools, writes the report or edit1.10 GB
VisionLFM3-VL-2BLooks at PNG/JPEG/WebP/GIF and reports measured facts1.25 GB
VoiceLFM3-Audio-2BTranscribes dictation and audio files; speaks replies via llama-tts1.40 GB

Four models, ≈4.9 GB of weights total, downloaded separately inside the app (~15 MB installer). Key facts from the project site and README, v0.1.1.

Everything below is verified against the source tree on main (16 commits at time of writing); file paths are given so you can check each claim yourself.

3. Architecture: One Executable, Four Subprocesses, Three Databases

The README's architecture section is direct about what it is not:

"The shell embeds the harness engine in-process: there is no sidecar, no loopback engine server, and no shell command surface." — README.md, §Architecture

So: a React/TypeScript webview inside a Tauri 2 shell, talking to an embedded Rust engine (crates/lfm3-harness) only through typed commands in apps/desktop/src-tauri/src/commands.rs and durable event channels (lfm3:run-event, lfm3:job-event, lfm3:chat-event). No Python sidecar, no local HTTP server that anything else on the machine could talk to. The one thing the engine does spawn is four llama.cpp children:

Server childEndpoint (pinned)ContextSampling
text / actor127.0.0.1:808032,000 tokenstemp 0.1, top-k 50, repetition penalty 1.1
vision (VL-2B)127.0.0.1:80818,192 tokensgreedy
audio (Audio-2B)127.0.0.1:80828,192 tokensgreedy
d1 (decision)127.0.0.1:80838,192 (batch 8,192)greedy; native choice protocol

Ports and defaults in crates/lfm3-harness/src/config.rs; launch arguments built in apps/desktop/src-tauri/src/engine.rs; per-role sampling in crates/lfm3-harness/src/agent/profile.rs. The 32k window is not arbitrary — LFM3 family members are trained for 32k context.

Three details are worth noting, because they are the parts that keep the topology honest rather than decorative:

The children cannot be talked into anything. The launch arguments are built in one place, as the doc comment on crates/lfm3-harness/src/model/process.rs says: "Builds the fixed command line; no planner-provided text is accepted." A model that hallucinates a clever flag has nowhere to put it. Readiness is confirmed by polling each server's identity endpoint, and an IdentityMismatch is a hard fail rather than a warning — the wrong weight on a port stops the boot.

The children cannot outlive the parent. On Windows every child is assigned to a job object created with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE (crates/lfm3-harness/src/supervisor/windows.rs) — the processes are created suspended, joined to the job, then resumed. On Linux each child gets its own process group. Kill the app and nothing keeps chewing your RAM at 100% CPU. This sounds trivial; anyone who has orphaned a llama-server knows it is not.

The loopback pin is enforced at packaging time. Config::validate_local rejects any non-loopback URL under the Packaged profile, and the packaged build forbids user-supplied download switches and external-server reuse. The offline claim is a property of the shipped binary, not a privacy policy.

State is split across three separate SQLite files, and the comment on crates/lfm3-harness/src/chat/store.rs explains why: "a conversation is not a verified run." The mission journal (append-only, user_version-keyed migrations, WAL mode) records runs, steps, attempts, tool results, check results, events, and artifacts keyed by sha256 UNIQUE; chat history and its approvals live in a separate chat.db; jobs in jobs.db. A chatty mistake can never pollute the evidence trail of a verified task, and a verified task's journal stays replayable.

4. Routing: A Decision Model With Veto-Limited Influence

Every chat message gets classified into one of seven intents — general, explore, summarize, related, report, vision, voice — and the route decides which capabilities get hinted to the actor. The classifier is d1-2B, consulted through a native choice protocol rather than free text:

The protocol itself is where the design gets specific. d1 answers POST /v1/systemone with a typed choice and a probability, and the client enforces type == "choice", a declared label, and a confidence in [0,1]. If you ask a native decision model for ordinary chat, it answers HTTP 501 — which, as we will see in §7, is exactly how a benchmark caught the router pretending to work.

And the invariant that ties the section together: a route grants capability hints, never authority. Being classified as "report" tells the actor which tools might help. It does not let anything write a file.

5. The Trust Machinery: Write Gates, Evidence Gates, Diff Approvals

Three independent layers stand between "the actor wants to change a file" and "the file changed." None of them can approve or execute anything on their own — they can only advise, hold, or refuse.

Layer one: the filesystem itself. Every model-supplied path is resolved through crates/lfm3-harness/src/paths.rs, whose header comment reads:

"Parent components, absolute paths, reserved device names, link and reparse-point traversal, and canonical escapes are rejected before any caller can touch the filesystem."

Symlinks are checked component by component; on Windows, trailing dots, trailing spaces and device names like CON and COM1 are rejected too. The release acceptance suite includes an escape fixture asking the mission to write ../escaped.md — the request is rejected before planning, in 0.25 s, with no leftover processes.

Layer two: the preflight gates. A second model — d1, not the actor grading its own homework — reviews each proposal. The constants, from chat/decisions.rs:

GateFloorLabelsOutcomesPowers
Write gateWRITE_GATE_FLOOR_BPS = 7_500 (75%)aligned / uncertain / contradictsAllow / Advise / HoldA Hold suppresses the approval card entirely; the change is not written and not retryable
Evidence gateEVIDENCE_GATE_FLOOR_BPS = 6_500 (65%)sufficient / insufficient / off_topicAdvisory note onlyNever blocks; flags thin evidence after read-only work

Gate decisions time out at 12 s (DECISION_TIMEOUT); evidence is capped at 7,000 chars total, with the user request at 3,000 and the file excerpt at 2,000.

The gate prompt is deliberately paranoid. It feeds d1 two clearly labelled inputs:

REQUEST (the person's words, the only authorization):
{...}

PROPOSED ACTION (model-authored, untrusted):
{...}

The actor's proposal is data under review, not an instruction. When the write gate holds, what the actor sees is hold_message() — and note there is no appeal path:

"Harness preflight held the change to {target} (d1, {n}% confidence): {reason}.
No approval card was shown and nothing was written. Do not retry the same
change. Either propose a change that clearly matches the request, or answer
with what you need."

Layer three: you. If a proposal survives the gates, it arrives as an approval card in the UI (apps/ui/src/chat/ActionCard.tsx): a "Review the exact change" expander with the old and new text side by side as a remove/insert diff grid, and two buttons — Allow once, Deny. There is no "allow always." Approvals expire: APPROVAL_TTL = 600 seconds in apps/desktop/src-tauri/src/chat.rs, ten minutes, batch-expired by the store. A diff you thought about for eleven minutes is a diff you must be re-asked about.

6. Refusing to Let the Model Declare Victory

Sections 4–5 gate the model's actions. The hardest problem is gating its claims, because a 1.7B model will claim success with complete sincerity and nothing to show for it. The harness treats three kinds of claim as unprovable until checked:

Semantic judgments — the cases a deterministic check cannot express — exist in the code (verify/semantic.rs: VL criteria, d1 choice and grounded checks, default confidence floor 8_000 bps, and a d1_grounded rule requiring the claimed string to occur literally in frozen input bytes before any model is consulted). But for three of the four missions — screen_qa, voice_intake, doc_extract — the acceptance spec declares "mode": "human_review_only" and the engine refuses to start them under automatic judgment. The documented bar for flipping a semantic check to automatic is at least three packaged end-to-end runs with zero confident false completions. That qualification has never been performed, and the README says so instead of quietly enabling it.

Only desk_task — frozen inputs, ten file tools, seven deterministic checks, required artifact out/summary.md, empty command templates — has passed end-to-end acceptance with real models: 5.99 s, with peak llama-server memory of 1,603,924 KB recorded, across an eight-case release suite that includes escape, crash, timeout and missing-model scenarios, all leaving no leftover process IDs.

7. Benchmark Forensics: A Test Suite That Caught Two Silent Bugs

On 2026-10-31 the project ran its multimodel benchmark — one run, one machine, results published in bench/multimodel/REPORT.md with the caveats attached. Headline numbers first:

SuiteResultNotes
Router accuracy21/21Keywords alone would have managed only 14/21; d1 was consulted 21/21 times and replaced the keyword route 20 times with zero regressions
Route latency122 ms medianPer d1 decision
Edge cases7/7 + 3/3Override-winners 7/7; keyword blind spots recovered by d1 3/3
Chat turns12/12With tool rounds, gates consulted live
Vision / voice2/2 · 1/1TTS + ASR round-trip 2/2 at 0% WER (text-level)
Gates in the run2 write · 8 evidence0 write holds fired; one gate answer at 55% fell below the 75% floor; 2 evidence advisories; 2 gate answers rejected as inconclusive

One real run on one local machine with a pinned llama.cpp runtime; synthetic fixtures; generated PNGs; no webview involved — the report states nothing is scripted. All caveats are the project's own.

Read the 21/21 with one caveat the report itself implies: before the bug below was fixed, the router that actually shipped was the keyword table, and the keyword table alone scores 14/21. The headline number describes the system as it was intended to work on the bench day — not a capability that had ever been running in the app.

What makes the benchmark worth reading is not what it proved but what it caught. The report was written around questions like: "Does d1 actually receive the prompt, or silently fall back?" Two genuine defects answered that question the hard way:

Bug one: the router was theater. The client was calling /v1/chat/completions on the decision model — an endpoint native d1 answers with HTTP 501. Routing had been "silently using keywords for every request." The app still felt right because the keyword table handles the obvious cases; the 21/21 number, had it been measured a week earlier, would have been 14/21 while claiming the d1 path was exercised. The fix speaks /v1/systemone and enforces the declared label, the choice type, and a [0,1] confidence — so a fallback can never masquerade as a decision again. The lesson generalises to any multi-model agent setup: instrument whose answer you actually used, not which endpoint you configured.

Bug two: approval purgatory. A chat action the user approved could stay stuck in approved forever, because the store's transition rule only accepted moves from pending. The two-transition path (approved → applied | failed, preserving decided_at) now exists, with the rationale kept as a comment in chat/store.rs — the sort of state-machine bug that a fake-model test suite misses and a real click-through finds.

The 50-job agent benchmark (bench/agent50/REPORT.md) is equally unsparing: 43/50 harness-pass (86%), 41/50 on the stricter semantic judgment, two false passes (jobs t27/t36, both from word-count bounds that said "too long" where the real failure was different), zero false failures, average 5.7 turns, median 30.5 s — and a weakest category, search, at 1/5. The report is explicit that the harness being more lenient than the semantic judge is precisely the gap you should worry about, because the harness is the thing shipping success messages.

8. The Python→Rust Rewrite, and How It Is Enforced

The harness began as a Python prototype. The README now opens with: "The Python implementation, tests, caches, and Python project configuration have been removed" — while the model weights, llama.cpp runtime, demo materials and missions carried over. PLAN.md bans Python from "development, packaging, or runtime" outright.

The part worth noticing is the enforcement. A dead stack is easy to resurrect accidentally: one old script left in the repo, one CI job invoking python, one packaged .py nobody notices. So removal is a checked assertion, not a claim. scripts/accept.ps1 runs a step literally titled "no Python project files" across the source tree; the packaged smoke test installs the NSIS build into a path containing spaces and verifies the installed tree contains no Python source; STEPS.md requires an audit expecting none. Legacy .lfm3/ data from the prototype is deliberately not migrated. There is no blog-post lesson-learned narrative in the repo — the rewrite's memory survives as gates in the pipeline, which is a more durable kind of record anyway.

9. Design Lessons for Constrained Agents

Strip away the specific models and the harness leaves a reusable checklist for anyone building on small, cheap, local models:

10. Limitations and Open Questions

V0.1.1 is honest about being experimental — in its own words, per the README's "Known gaps" and product-constraints sections:

LimitationStatus, as documented
Single-run numbersEvery benchmark figure is one machine, one run, synthetic fixtures; zero real contradictory-write holds measured in the benchmark (the 55%-confidence case was the closest)
Semantic missions unqualifiedscreen_qa, voice_intake, doc_extract are human-review-only; the ≥3 packaged-run semantic holdout has never been performed
Under-qualified pathsRouting, search, report, streamed calls, cancellation and approval-expiry not yet qualified against real models across scenarios; replanning tested only by automation
Token caps not wiredlimits.max_tokens is not enforced by the executor or planner, which hardcode 512 and 1,536 output tokens respectively — flagged in the README as a known gap
Voice playback in-appllama-tts is not exercised end-to-end inside the desktop app; coverage is by components and an ignored test
Platform reachWindows 10/11 x64 and Linux x64 only (no macOS); CPU-only by design (-ngl 0; a 594 MB CUDA backend exists in llama.cpp but is deliberately not bundled); Linux needs WebKitGTK 4.1 + GTK 3
Unsigned installerThe 15.2 MB Windows NSIS build is unsigned, so SmartScreen will warn; SHA-256 hashes are published for every artifact instead
ScopePurpose-bound by policy: one folder, no shell, no web browsing, no cloud services, no telemetry unless explicitly enabled, no resume of interrupted jobs, legacy prototype data not migrated

The open questions mostly reduce to scale: this design assumes the actor is unreliable and spends real engineering on compensating. Whether the gate floors (75/65/55%) still feel right with a 7B actor, whether semantic holdout qualification will survive contact with false-pass rates like agent50's 2/50, and whether search at 1/5 is a model ceiling or a tool-design bug — the project has measured the questions; the answers are still ahead.

11. Conclusion

LFM3-Harness is a small app with a clear thesis: with small models, the harness is the product. The four LFM3 members are modest, and the design accepts that — it wraps every model output in a check performed by something that is not the model: a path resolver that answers before the filesystem does, a decision model that grades proposals it did not write, a finish verb that refuses until the file exists, an acceptance contract frozen in SHA-256 before anyone starts working, and a benchmark whose most useful result is two silent bugs it caught in its own plumbing.

What the harness cannot do is make 1.7B smarter. Rails that reliably prevent damage are not the same thing as capability, and the measurements in this post are the project's own ceiling talking: 43/50 on jobs with two false passes, search at 1/5, one mission qualified end-to-end, everything else single-run. If you are looking for a coworker that does real work, this is not it yet — and at this model scale, arguably it never will be; the rails make a weak assistant safe, not strong. The honest pitch is narrower: if you build agents on small or cheap models, the repo is worth reading as a catalogue of verification patterns — chat/decisions.rs, agent/loop_agent.rs, paths.rs — and the Known-gaps section is worth reading first, because it tells you exactly which of those patterns have not survived contact with a real model yet. Treat this post as an introduction, not an endorsement.

References

  1. LFM3-Harness project site — lfm3-harness.github.io (capabilities, download hashes, model table, FAQ), accessed November 1, 2026.
  2. LFM3-Harness/LFM3-Harness on GitHub — README.md (architecture §144; benchmark defects §231; licenses and Liquid AI notice §3, §443; known gaps §439), plus PLAN.md (system spec: in-process engine, child supervision, no-Python rule) and STEPS.md (implementation and audit steps), accessed November 1, 2026.
  3. crates/lfm3-harness/src/config.rs (loopback port pins 8080–8083, validate_local), chat/router.rs (MIN_DECISION_CONFIDENCE_BPS = 5_500, 24-char skip, General veto), chat/decisions.rs (WRITE_GATE_FLOOR_BPS = 7_500, EVIDENCE_GATE_FLOOR_BPS = 6_500, hold_message, gate prompt labels), paths.rs (escape rejection header), agent/loop_agent.rs (finish refusals), verify/semantic.rs (default_confidence_bps() = 8_000), model/process.rs (fixed command line), supervisor/windows.rs (kill-on-close job object), store/migrations.rs (journal schema) — LFM3-Harness repository, main branch.
  4. apps/desktop/src-tauri/src/chat.rs (MAX_ROUNDS = 12, APPROVAL_TTL = 600s, TURN_TIMEOUT = 900s), chat_bench.rs (benchmark turn timeout 300 s), engine.rs (TEXT_CONTEXT_TOKENS = 32_000, -ngl 0 launch), apps/ui/src/chat/ActionCard.tsx (diff approval card) — same repository.
  5. bench/multimodel/REPORT.md (2026-10-31 run: 21/21 router, 122 ms median route latency, gate consult counts, caveats) and bench/agent50/REPORT.md (43/50 harness pass, 41/50 semantic, t27/t36 false passes) — same repository.
  6. missions/desk_task.json, missions/screen_qa.json, missions/voice_intake.json, missions/doc_extract.json (frozen acceptance specs and human_review_only declarations) — same repository.
  7. scripts/accept.ps1 ("no Python project files" step) and README smoke-test description — same repository.
  8. Liquid AI LFM3 model weights under the LFM Open License; llama.cpp runtime bundle v0.8.0 (build 11960), CPU-only.
  9. Related Local AI Zone coverage: "Low-Resource AI Agents: Experiment Archive" and "System-One Decision Models Deep Dive," this site, 2026.

LFM3 Harness deep-dive recap: four local models, gate floors, benchmark results

Frequently asked questions

What is LFM3-Harness?

LFM3-Harness is a free, MIT-licensed, community-built desktop app for Windows 10/11 x64 and Linux x64 that runs Liquid AI's LFM3 models entirely on your own CPU through llama.cpp. You pick a folder and chat; the assistant can list, search, read and summarise files and draft reports or edits, but every write is shown as an exact diff and happens only after you explicitly allow it. It is an independent project and is not affiliated with, endorsed by, or sponsored by Liquid AI.

How does it stop a 2B model from doing damage?

Through the harness rather than the model: a separate decision model preflight-checks every proposed write against your request with a 75% confidence floor and, when it holds a change, shows no approval card at all; every path is canonicalised and rejected before touching the filesystem if it tries to escape the chosen folder; claims of completion are refused until the deliverable exists on disk; and verified tasks are judged only by frozen, SHA-256-hashed acceptance contracts that cannot be weakened mid-run. Diff approvals expire after ten minutes, and you can only ever "Allow once."

What hardware does it need, and is it really offline?

A CPU-only x64 machine with at least 8 GB RAM — it deliberately launches every model server with GPU offload disabled. Once the ≈4.9 GB of weights are downloaded in-app (verified byte-for-byte with SHA-256), everything runs locally: the four llama-server children are pinned to 127.0.0.1 on ports 8080–8083, the packaged build refuses any non-loopback endpoint, and there is no account, no telemetry by default, and no engine sidecar.

Can it be trusted as "done" work?

Read the project's own numbers before deciding. Its 50-job agent benchmark passed 43/50 with two false passes and a weak search category (1/5), all documented with caveats; three of its four missions remain human-review-only because the semantic qualification holdout has never been run; every benchmark figure comes from one machine and one run. The harness is transparent about exactly how far its guarantees reach — which is worth more, when evaluating experimental software, than a stronger product with a vaguer story.

But is it actually capable?

No, not by frontier standards, and the project would not claim it is. The writer is a 1.7B model on CPU: expect small-model output quality with a hard ceiling, roughly one job in twelve failing its own checks even when the harness reports success, and search — the most agent-critical skill in its benchmark — at 1/5. What it is, is careful: the design's real achievement is that the assistant rarely gets to lie about what it did. Safe to try on a folder of notes; not a replacement for anything you currently use an agent for.

Need help with this?

Tell me what you’re working on and I’ll help you work through it — whether you want to run LFM3-Harness yourself, adapt its gate design to your own agent, or pick the local models that fit your machine. Your message arrives with this article attached, so I’ll know exactly what you’re reading.

Connect with me on Instagram