Skip to content

feat: taraengine as taracode's native backend - #27

Merged
dayanstef merged 41 commits into
mainfrom
feat/taraengine-backend
Oct 13, 2026
Merged

dayanstef merged 41 commits into
mainfrom
feat/taraengine-backend

Conversation

@dayanstef

Copy link
Copy Markdown
Member

Description

taraengine becomes taracode's native backend. taracode finds it at http://127.0.0.1:8272 when no host is set,
loads the model at the resolved context window, maps /think to the engine's thinking switch and levels, and reads
the engine's own timings for tokens per second. The native Ollama client goes: Ollama keeps working on the generic
OpenAI-compatible path, like vLLM and llama.cpp, and each Ollama session says in one line what that path cannot do.

This PR merges after the board proof, with go.mod on taraengine v0.2.0. The user-facing files (README.md,
install.sh, the .goreleaser.yaml texts, docs/evals/ and the regenerated board) follow in the release PR with
taraengine v0.3.0, because code.tara.vision serves install.sh and the /evals board from main.

Changes by area:

  • Backend: internal/llm/taraengine, the engine client over taraengine's pkg/client: one retry after 2 s on
    a 503, loads with a 15-minute backstop, chat over /v1 with temperature, top_p, max_tokens, the thinking switch
    and levels, usage and timings; stream errors arrive as the engine's refusals, and engine text reaches the terminal
    without control characters. Typed errors: ServerError, UnreachableError, ErrStreamCut.
  • Detection: /api/health first (2 s); Ollama by /api/version; a host that refuses or never answers the
    connection stops detection with a clear error; vendor: taraengine skips detection; the default host is
    http://127.0.0.1:8272.
  • Session: the session start and /model load the model at the resolved window and say so; a failed switch
    restores the previous model and its settings; first-run and not-pulled advice name taraengine pull <model>; an
    imported model starts before the engine has loaded it once.
  • Errors: the engine's hint comes first; startup errors get advice by kind (nothing pulled, a model without
    tools, no connection).
  • Doctor: the engine's version and runtime, the machine and its tier, the pulled models, the window the
    configured model loads at, the recommended model, and a warning below engine 0.2.0; a refusal reads as a refusal,
    not as an unreachable server.
  • Evals: run on taraengine; results name their server, tier and default; 3.2.x results stay readable and write
    back byte for byte; the board keeps a row per model and engine; a memory figure the engine measured replaces the
    probe.
  • Removed: the native Ollama client and its fake, the embedded model registry and the RAM sizing, and the
    per-request window, keep_alive and format options. Existing configs still load; doctor notes a keep_alive that
    no longer does anything.
  • Tests: the whole suite runs on a scripted fake engine (internal/llm/taraenginetest).
  • CI: a new Engine job runs a tagged integration test against a real taraengine built at the go.mod pin
    (scripts/engine-ci.sh): health with and without the key, version, models, doctor, load, a streamed chat with
    timings, unload, a not-pulled refusal, and the key on /api and /v1.
  • Docs: CONTRIBUTING, SECURITY, ROADMAP, RELEASE, config.example.yaml, the issue and PR templates, and the
    CHANGELOG's [Unreleased] section with the migration notes.

Related Issue

None.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected): Ollama moves
    to the generic OpenAI-compatible path; see the CHANGELOG's Migration section
  • Documentation update
  • Refactoring (no functional changes)

How Has This Been Tested?

Describe the tests you ran:

  • Tested locally with taraengine: make eval-lab with gemma4:12b against a taraengine 0.1.0 lab engine wrote a
    results file with the server block, tier 16, the default mark, gpu_percent 100 and gpu_memory_mib; a session
    on glm-4.7-flash called a tool and answered from its result; taracode doctor read the engine, the machine and the
    models
  • Added/updated unit tests: every changed line is covered by its own package's tests
  • Tested on macOS: the full suite with -race, the coverage gate, golangci-lint and govulncheck
  • Tested on Linux: scripts/engine-ci.sh passes against taraengine v0.1.0 in a linux/arm64 container; the
    Engine job's amd64 run is this PR's CI

Checklist

  • My code follows the project style guidelines
  • I have performed a self-review of my code
  • gofmt -s -l . shows no files (code is formatted)
  • go vet ./... passes
  • make test passes
  • make build succeeds
  • I have updated documentation if needed
  • My changes generate no new warnings

The fake's default tier recommendations are literal data again. Reading them from internal/models would
make the fake import internal/llm, and tests in the models package import the fake, which would form an
import cycle. The model-name guard therefore skips the fake's directory, which mirrors the engine's catalog.
A real server gives every tool call an id of its own, so a conversation that
replays several replies never holds two calls with one id. The fake numbered
the calls of each reply from call_0; it now runs the numbers on for the life of
the server (call_0, call_1, call_2, ...), and a test pins it.
…e's calls

The detection probes give up dialling after 1.5 s, below the engine probe's 2 s,
so a host that drops the packets (a powered-off box, a VPN that is down, a
firewall that drops) stops detection with the not-running error, as a refused
port does. net/http dials apart from the request's context, so only a dial
bound below the caller's deadline shows that nothing answered.

With vendor taraengine the engine client's dial gives up after 3 s, inside the
session's 5 s bound on reading a model, so the session stops with "nothing
answers at <host>" and prints no capability warning. Only the dial is bounded:
a load keeps its 15-minute backstop.

The probe test's comment says what an idle connection is: one nothing will
close.
…dvice

At startup, an engine with nothing pulled and a model without tool calls get
the suggestion table's advice (pull a model, `taracode doctor` names one; choose
a model with tools, `taracode doctor` lists them) instead of the box that says
taracode cannot connect. A connection failure, and an error nothing explains,
still get that box.

A refusal the server explained shows its hint once: the dimmed line under the
title carries the message without the hint, and the hint is the first
suggestion. The same holds for a server nothing answers at.

A session on Ollama says how to get a larger window there: set
OLLAMA_CONTEXT_LENGTH on the Ollama server. The changelog's migration note says
the same.
…s unknown

The engine infers an import's capabilities while the import is loaded, lists
none before that and forgets them on every unload. Show reports such
capabilities unknown (inferred, and none listed), and the session takes them as
it takes a server without capability data: no tools gate, thinking left as
configured, the window capped by the native one, and no warning. An import can
be started, and switched back to with /model, and the evals' check takes it
too.

A catalog model without tools is still refused, and an import whose
capabilities are listed counts as any other model. The fake engine marks an
import's capabilities as inferred.
…g one

Before a load on the engine, which can take up to the engine's load_timeout,
the session start and /model print "Loading <model> at a <n>-token window..."
in the dim style, to the session's output like its other notes. The generic
path loads nothing and prints nothing.

When the configured model is not pulled and the session falls back to the
first pulled one, a second line on the engine says "Pull it with: taraengine
pull <model>". Other servers get no such line.

The warning about a retired hosts: section says where taracode goes instead:
the host in host:, or taraengine at http://127.0.0.1:8272 when none is set.
…loaded one

A server that answered and refused (a wrong key, a 500) reads "refused:
<message>", with the key line for a 401; "unreachable:" stays for a server
nothing answers at.

The OpenAI model list has names only, so a model whose size and window are both
unknown is listed by its name alone instead of "0.0 GB ctx 0".

The engine lists a sleeping model among the loaded ones, with its window: when
the configured model sleeps, the Model line says "(sleeping at a <n>-token
window)".
…rol characters off the terminal

An error chunk the engine sends mid-answer is a *llm.ServerError (status 500,
its message and hint, the status text when it sent no message), so it gets the
same rendering as a refusal before the answer. A stream line longer than the
8 MiB bound is its own error, "taraengine: a line of the stream is longer than
8 MiB", not a server that stopped mid-answer.

Text the engine supplies reaches the terminal without its control characters
(tab and newline kept): refusal messages and hints on /v1 and /api, the error
chunk, and doctor's engine report (version, runtime, platform, health detail,
machine, runtime note, recommendation and a faulty model's log tail, which
echoes an imported model's metadata). A refusal body that is not the engine's
JSON, a proxy's error page say, is cut to 300 bytes.

"nothing answers at <host>" masks the password of a host URL's user info.

The first 503's body is read to its end before the retry, so the retry reuses
the connection even when the body comes after the status.
…or and imports

scripts/engine-ci.sh selects ^TestAgainstARealEngine$ and fails unless the
output holds that test's PASS line: go test passes when no test matches, so a
renamed test or a changed build tag would otherwise run nothing. Its header
says the port is fixed at 18272.

The real-engine test runs as steps and stops at the first that fails. It adds
a health call without a key, which detection relies on, a doctor call whose
machine names a tier and whose recommendations are not empty, and a Show of the
imported model before its first load, whose capabilities are unknown.

A test of CleanHardware uses a documentation address (192.0.2.10), a comment
on the engine's URL forms uses the engine's port, and a test comment says what
its 5 s bound is for.
CONTRIBUTING installs taraengine with go install, installs its runtime, starts
it and pulls a model, the way scripts/engine-ci.sh does, until the release
brings the cask. The generic path is described exactly: no window control, no
thinking on or off, no evals.

The Makefile's lab-smoke names TARACODE_KEY, as eval-lab does, and the
scoreboard comment says that on the engine a model that does not fit fails to
load instead of spilling to the CPU, so the board stops at it; the eight
catalog models fit a 32 GB card.

SECURITY no longer lists taraengine among the third-party servers out of
scope; its reports go to its own policy.
@codecov

codecov Bot commented Oct 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ssword

The engine lists no capabilities for an import it has not loaded and marks
them inferred; the session starts such a model. Doctor now says so too: the
Model line reads "(capabilities show once it loads)" and the capabilities
column "unknown until loaded", where it said the model has no tool calls. A
model the engine marks inferred but lists capabilities for shows them as any
other. Doctor's exit status and its other lines are as they were.

llm.RedactedHost, the masking "nothing answers at <host>" already used, is
exported. Doctor's Server line and the connection box's Host and curl lines
print the host through it, so the password of a user:password in the host URL
never reaches the screen; a host without user info prints as it is.
… length is not waited for

The message of a refusal on the control API is cut to 300 bytes, at a rune
boundary, as the body of a /v1 refusal that is not the engine's JSON is.
pkg/client passes on up to a mebibyte of a body as the message, a proxy's
whole error page. One helper does the cut for both; a message that fits is
unchanged.

The first 503's body is read to its end before the retry only when the
response declares a length of at most 64 KiB, as the engine's 503s do, so the
retry still reuses the connection. A body of unknown length that may never
end is closed at once, and the retry goes out after its delay instead of when
the turn's context ends.
…drops control characters

When model detection finds nothing answering at the engine (an
*llm.UnreachableError, which only the engine client returns), New returns
that error at once: before the saved and the configured model are trusted and
without the "Could not detect models" line. The session used to go on with the
configured name and stop at its next call to the engine, a dial's wait later.
Any other detection error keeps the fallbacks.

The first-run advice prints the tier and the model's name, which come from the
engine's doctor, without their control characters; a recommendation of
nothing but control characters is no recommendation.
@dayanstef dayanstef self-assigned this Oct 11, 2026
The /status Host line, the config warning that names the host of a retired
hosts: section, and the error evals give for a server that is not an engine
print the host through llm.RedactedHost, so the password of a user:password in
the host URL is masked there as it is in doctor and the connection box. The
session and the evals keep the host as configured; a host without user info
prints as it is.
@dayanstef
dayanstef merged commit a02f1c0 into main Oct 13, 2026
14 checks passed
@dayanstef
dayanstef deleted the feat/taraengine-backend branch October 13, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant