Skip to content

model: serve through geistd (GEISTSHELL_GEISTD) — resident session, pinned prefix, same decoder - #155

Merged
geisten merged 3 commits into
mainfrom
feat/geistd-adapter
Sep 25, 2026
Merged

geisten merged 3 commits into
mainfrom
feat/geistd-adapter

Conversation

@geisten

@geisten geisten commented Sep 25, 2026

Copy link
Copy Markdown
Owner

Agent runs are short processes; loading the GGUF in each was the expensive part. With GEISTSHELL_GEISTD (Unix socket path or host:port; GEISTD_TOKEN off loopback) the GEIST adapter talks to a geistd daemon instead of loading libgeist: session + KV cache live there and outlive the process, the constant prefix is pinned there, a run pays only for the tokens it adds.

  • Decode paths untouched: every geist_session_* call goes through an eng_* layer with an in-process and a remote implementation (append-style prefill via a history shadow, pin-aware reset, full logit vector, vocabulary pieces fetched once, EOS/end-of-turn → nullptr as before).
  • Wired at the agent, run and eval config sites and in chat; deps/geistd/geistd_client.h vendored.
  • Verified with SmolLM2 behind geistd: free decode, the constrained decoder yields (recommend (kind finish) (reason …)), two agent runs, and smoke_eval_real.sh with the same outcome as in-process (256 tokens, rejected). test_model_adapter gains a geistd path (GEISTSHELL_TEST_GEISTD); test/smoke_geistd.sh drives it. make test: 61 passed.
  • Side fixes: append_bytes no longer claims [static n] for n == 0 (UBSan); map_geist_status covers GEIST_E_STALE_CALIBRATION.

🤖 Generated with Claude Code

geisten and others added 3 commits September 25, 2026 09:02
…inned prefix, same decoder

Agent runs are short processes and loading the GGUF in each one was the
expensive part. With GEISTSHELL_GEISTD set (Unix socket path or host:port,
GEISTD_TOKEN off loopback) the GEIST adapter talks to a geistd daemon
(geist-serve >= 0.2 + pin) instead of loading libgeist: the session and
its KV cache live there and outlive the process, the constant prefix is
pinned there, and a run pays only for the tokens it adds.

The decode paths are untouched: every geist_session_* call now goes
through an eng_* layer with two implementations. Remote: prefill appends
(a shadow of the daemon's history turns geistd's whole-context prefill into
an append), reset keeps the pinned prefix, peek returns the whole logit
vector, token_to_str is a local table fetched once (the decoder scans the
vocabulary by surface form) and yields nullptr for EOS/end-of-turn so the
existing `piece == nullptr` test still ends a turn. Errors from the
daemon are logged and mapped to geist statuses.

Wired at the three GEIST config sites (agent, run, eval) and in chat
(which then skips the file resolve). deps/geistd/geistd_client.h is the
vendored single-header client; jsmn from deps/jsmn.

Verified with SmolLM2 behind geistd: free decode, the constrained decoder
producing (recommend (kind finish) (reason ...)), two agent runs, and the
real eval path with the same outcome as in-process (256 tokens, rejected).
test_model_adapter gains a geistd path (GEISTSHELL_TEST_GEISTD), and
test/smoke_geistd.sh drives it plus smoke_eval_real through a daemon.
Also: append_bytes no longer claims [static n] for n == 0 (UBSan), and
map_geist_status covers GEIST_E_STALE_CALIBRATION from the pinned engine.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…re the first system header for getaddrinfo

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…by the client implementation)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@geisten
geisten merged commit 0dd32ac into main Sep 25, 2026
5 checks passed
@geisten
geisten deleted the feat/geistd-adapter branch September 25, 2026 11:55
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