ofa β Technical OverviewΒΆ
Project: OnField Assistant π΅ (ofa)
Deployment: NLR Kestrel HPC
Module: assistant (Lmod)
Repo: https://github.com/nileshsawant/onfield-assistant
Document date: June 2026
1. QuickstartΒΆ
On KestrelΒΆ
module load assistant
ofa # General coding assistant (default)
ofa --openfoam # OpenFOAM dictionary generator (was default in <= 1.0)
ofa --hpc # Kestrel HPC documentation assistant
ofa --code # General coding assistant (redundant β this is the default)
ofa --amrex # AMReX C++ framework
ofa --marbles # MARBLES (LBM thermal solver on AMReX)
ofa --quantum-computing # Quantum computing (rigorous gate / matrix verification)
ofa --vasp # VASP (Vienna Ab initio Simulation Package)
ofa --rhel9_reframe # ReFrame for RHEL9 migration
ofa --resume # Resume your last session
ofa --openfoam "set up a cavity case" --save ./case # Single OpenFOAM query + save case files
ofa auto-allocates a quarter-node H100 GPU (debug partition, 30 min walltime)
via SLURM on first invocation. Override the defaults before running:
export OFA_ACCOUNT=<your-slurm-account> # default: your default account
export OFA_PARTITION=gpu-h100 # default: debug
export OFA_WALLTIME=04:00:00 # default: 00:30:00
Inside any interactive session, type /help for the full slash-command menu
(skills, memory inspect/edit, model switch, history, save case, shell escape).
From VS Code ChatΒΆ
The recommended path is the bundled extension (vscode-ext/), installed on
the Kestrel-remote side of a Remote-SSH session. It runs the --serve
bring-up for you, so there is no tunnel to manage and no token to paste:
OFA: Connect from the command palette, then pick any ofa Β· β¦ entry in the
Chat model picker. See Β§5.4 and the
repo README.
For other editors, or a laptop-local VS Code that is not attached to Kestrel, start the server by hand:
# On Kestrel
ofa --serve --serve-enable-tools
ofa --serve prints a labelled connection block with the exact ssh -L line
(compute-node hostname + ports already filled in), the BYOK URL, and the
bearer token. Paste the ssh -L in a laptop terminal, then register the URL
and token in VS Codeβs chatLanguageModels.json. The helper
tools/byok-update-config.py generates that
config in one shot. Full walkthrough including the known VS Code-side
gotchas: docs/byok-vscode.md. Do not set up both routes β
the model picker ends up with two redundant groups.
DiscoverabilityΒΆ
module help assistant prints a usage summary; module load assistant shows
a short banner with the BYOK quick-start; the GitHub repo
(https://github.com/nileshsawant/onfield-assistant) holds the live source
and this document.
2. Executive summaryΒΆ
ofa is a domain-specialised AI assistant that pairs a local 31-billion-parameter language model (Gemma 4) on Kestrel H100 GPUs with retrieval-augmented generation over indexed Kestrel/OpenFOAM/AMReX/MARBLES/ReFrame/quantum-computing corpora. It exposes two surfaces:
Interactive CLI (
ofa,ofa --hpc,ofa --code,ofa --amrex,ofa --marbles,ofa --quantum-computing,ofa --rhel9_reframe) β a full agent loop that reads files, executes bash, edits code, and persists session state on Kestrel.OpenAI-compatible HTTP server (
ofa --serve) β a Bring-Your-Own-Key (BYOK) endpoint so VS Code Chat,opencode, or any OpenAI-compatible client can route requests through the same domain layer.
The codebase is ~6,000 lines of Python (no exotic dependencies β stdlib + httpx + chromadb + rank_bm25 + sentence-transformers + ollama). All inference runs locally on a quarter-node Kestrel GPU allocation; no data leaves NLRβs network. 124 commits as of this writing; production-stable on the OpenFOAM/HPC modes.
The remainder of this document covers whatβs in the repo, how the pieces fit together, and the operational/safety properties anyone evaluating ofa for wider use will want to know.
3. What ofa is β and is notΒΆ
IsΒΆ
A local-first LLM assistant: model weights, indexed corpora, and runtime state all live on Kestrel under the userβs account or
$OFA_ROOT.Domain-specialised through five mode-specific system prompts, six pre-built vector indices (~27,500 documents), per-user long-term memory, and a skill system.
Agentic on the CLI side: it can run bash, read/write files, save OpenFOAM cases, retry on tool errors, and persist its working memory across sessions.
Interoperable via OpenAIβs
/v1/chat/completionsshape β usable from VS Code BYOK,opencode,curl, or any client that speaks the same wire format.
Is notΒΆ
A frontier model. Gemma 4 31B is materially less reliable than GPT-4/Claude at long-horizon agentic work; the prompts and execution-side guardrails compensate but do not match frontier behaviour.
A multi-user shared service. Each user gets their own SLURM allocation (a quarter of a 4-GPU H100 node) and their own per-user scratch state. Concurrency across users is achieved by everyone running their own
ofainvocation.A general-purpose coding agent. The system prompts and the RAG corpora are tuned for OpenFOAM 13, AMReX/MARBLES, Kestrel HPC, ReFrame RHEL9 migration, and adjacent C++/Slurm work. It will answer general programming questions but its strengths lie in those domains.
4. Architecture (high level)ΒΆ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Kestrel compute node β
β β
user shell β ββββββββββββββββ β
βββββββββββββΆβ β ofa CLI β bin/ofa wrapper: β
β β (ofa_main) β - auto-allocates GPU via sallocβ
β ββββββββ¬ββββββββ - sets OFA_ROOT, OLLAMA_MODELS β
β β β
β βββ prompts/ (8 mode prompts + common) β
β βββ vectordb/ (8 ChromaDB collections, β
β β ~30.1K indexed docs) β
β βββ repos/ (live git clones for RAG β
β β grep + ad-hoc reads) β
β βββ models/ (Ollama weights, e.g. β
β β gemma4:31b-it-q8_0) β
β βββ $OFA_SCRATCH/ (per-user state: β
β .ofa_session.json, prefs, lessons, β
β serve port, api key, history) β
β β β
VS Code β ββββββββΌββββββββ ββββββββββββββββ β
BYOK ββββΆβ β ofa --serve βββββΆβ Ollama β GPU β
opencode β β (ofa_server) β β (port 11434)β inference β
curl β ββββββββββββββββ ββββββββββββββββ β
β OpenAI-compat β
β HTTP shim β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Three layers, in order of how a request flows through them:
Surface β either the interactive CLI (terminal stdin/stdout, agent loop) or the BYOK HTTP server (
POST /v1/chat/completions).Domain layer β the same in both surfaces: system-prompt selection, long-term memory injection, RAG retrieval, optional skill content.
Inference layer β Ollama running
gemma4:31b-it-q8_0(the default; seeMODEL_REGISTRYinsrc/ofa_main.pyfor the full set) on an H100. Both surfaces use the same Ollama process viaofa_main.chat_stream().
5. Interaction surfacesΒΆ
5.1 CLIΒΆ
Command |
Mode |
System prompt |
RAG retriever |
|---|---|---|---|
|
general code R/W/X |
|
|
|
OpenFOAM case / dictionary generator |
|
|
|
Kestrel HPC docs |
|
|
|
general code R/W/X (same as default) |
|
|
|
AMReX C++ framework |
|
|
|
MARBLES LBM thermal solver |
|
|
|
Quantum computing (code + papers, rigorous math verification) |
|
|
|
ReFrame for RHEL9 |
|
|
|
VASP (Vienna Ab initio Simulation Package) |
|
|
CLI behaviour:
Auto-allocation:
bin/ofadetects whether itβs running inside a SLURM job. If not, itsallocs a quarter-node H100 allocation (defaults: debug partition, 30 min, 32 cores, 80 GB RAM, 1 GPU) and re-execβs itself on the compute node.Ollama bootstrap:
ensure_ollama_running()starts a per-userollama serveon a UID-derived port if none is already running, then waits for/api/tagsto respond.Session resume:
--resumereloads$OFA_SCRATCH/.ofa_session.json. Sessions auto-compress when they grow past 100 KB (see Β§6.6).Interactive slash commands:
/help,/clear,/compact,/history,/cwd,/retry,/memory,/remember <text>,/forget [prefs|lessons|all],/skills,/skill <name>,/skill off <name>,/models, plus shell escapes ($ <cmd>) and file inlining (@<path>).Tool execution: the model emits fenced
=== TOOL ===blocks;_run_react_loopparses them, executes (bash, file read/write, planned-file generation), feeds output back, and continues until the model stops asking for tools or the consecutive-error limit (3) is hit.
5.2 BYOK HTTP server (ofa --serve)ΒΆ
OpenAI-compatible HTTP shim. Three endpoints:
Method |
Path |
Auth |
Notes |
|---|---|---|---|
GET |
|
none |
Liveness probe; returns |
GET |
|
bearer |
Lists seven model IDs ( |
POST |
|
bearer |
OpenAI format; supports |
The model ID in the request body selects the mode β system prompt β RAG retriever. Inbound system messages from the client are dropped (the BYOK clientβs own prompt would override ofaβs domain knowledge). RAG retrieval is applied only to the most recent user message.
The full client setup (SSH port-forward + VS Code config) is documented in docs/byok-vscode.md. A helper at tools/byok-update-config.py generates the VS Code chatLanguageModels.json provider entry in one shot.
5.3 Python client (ofa_client)ΒΆ
For calling ofa from inside a userβs own code (typically a simulation
loop that wants AI summaries of plots, log snippets, or config
files), src/ofa_client.py is a stdlib-only client that talks to a
running ofa --serve on the same node. module load assistant adds
$OFA_ROOT/src to PYTHONPATH, so it imports cleanly from any Python
environment (venv, conda, bare interpreter) β the client has no third
-party dependencies and doesnβt import anything from ofa_main or
ofa_server.
Two API entry points:
ask(prompt, ...)β stateless one-shot; the natural fit for a sim loop where each summary is independent.Session(model=...)β accumulates a message history client-side and sends the whole thing on each.ask(); the natural fit for multi -turn scripts (code diagnosis, exploratory back-and-forth).
Usage:
from ofa_client import ask, Session
# 1. Plain text
text = ask("what is a good turbulence model for cavity flow at Re=1e4?")
# 2. Text with inline context
text = ask(
"diagnose this run",
context="Simulation was cavity flow, Re=1000. Diverged at step 4200.",
)
# 3. Attach a file (tail-reads last 32 KB by default; full_file=True to
# override β useful for huge solver logs)
text = ask("why is this crashing?", file="output/solver.log")
# 4. Attach an image (base64-encoded and sent as OpenAI image_url)
text = ask("describe this plot", image="output/step_0100_pressure.png")
# 5. All at once + explicit model
text = ask(
"diagnose this simulation",
image="output/step_4200_pressure.png",
file="output/solver.log",
context="Re=1000, cavity flow, k-omega SST turbulence model.",
model="ofa-code",
timeout=60,
)
# Multi-turn
sess = Session(model="ofa-code")
sess.ask("what turbulence model for cavity flow at Re=1e4?")
sess.ask("show me a controlDict for that") # sees the previous turn
Auto-detection order for URL and bearer token:
Explicit
url=/token=kwargs toask/Session.$OFA_BYOK_URLand$OFA_BYOK_TOKENenvironment variables.$OFA_SCRATCH/.ofa_serve_portand$OFA_SCRATCH/.ofa_api_key./scratch/$USER/.ofa_serve_portand/scratch/$USER/.ofa_api_key.
Raises RuntimeError with a clear message if no server is reachable β
sim loops should wrap the call in try/except so a slow model or
expired allocation skips the summary rather than crashes the sim:
try:
summary = ask(f"Summarise pressure field at step {step}", image=fname,
timeout=60)
with open("output/ai_summary.log", "a") as f:
f.write(f"[step {step}] {summary}\n")
except Exception as e:
print(f"[ai summary skipped: {e}]")
Any of the eight ofa modes (ofa-openfoam, ofa-hpc, ofa-code,
ofa-amrex, ofa-marbles, ofa-reframe, ofa-quantum-computing,
ofa-vasp) can be passed as model=. Images pair
with any mode β Gemma 4βs vision head handles them regardless of
which system prompt is loaded.
5.4 VS Code extension (vscode-ext/)ΒΆ
The recommended way to drive ofa from VS Code Chat, and a wrapper
around Β§5.2 rather than a separate protocol. Installed on the
Kestrel-remote side of a Remote-SSH session, it:
registers the eight modes via
vscode.lm.registerLanguageModelChatProvider(vendorofa), so they appear in the Chat picker without any laptop-sidechatLanguageModels.json;runs the
salloc/srun/ofa --servechain by delegating tobin/ofa, then scrapes the connection banner for node, port, and bearer token;keeps a login-node
ncatrelay so VS Code reaches the compute node across reallocations;derives the advertised
maxInputTokens/maxOutputTokensfrom the selected modelβsnum_ctx, mirroringMODEL_REGISTRYinMODEL_CONTEXT(vscode-ext/src/modelProvider.ts);translates VS Codeβs tool calls to and from OpenAI
tool_calls, whichofa --serve --serve-enable-toolsthen maps onto Ollamaβs native format, so Agent mode can apply edits and run commands.
Because both the extension and the server run Kestrel-side, the bearer
token never reaches the laptop and VS Codeβs secret storage is not
involved β the opposite of the BYOK route, where the apiKey in
chatLanguageModels.json is only a hint.
6. The domain layer (what makes ofa more than vanilla Gemma)ΒΆ
Both surfaces share these five components, layered into the request before it reaches Ollama.
6.1 System promptsΒΆ
File |
Lines |
Role |
|---|---|---|
|
117 |
Cross-mode rules: tool-fence convention, RAG citation policy, two-channel long-term-memory contract (PREFS + LESSONS), output style. Included in every prompt. |
|
42 |
Dictionary-generator mode β OpenFOAM 13 case file conventions. |
|
11 |
Kestrel HPC documentation assistant. |
|
7 |
General coding assistant. |
|
29 |
Layered onto |
|
8 |
AMReX C++ framework. |
|
10 |
MARBLES lattice-Boltzmann thermal solver (built on AMReX). |
|
27 |
Quantum computing; enforces per-answer verification of gate matrices, tensor ordering, and unitarity. |
|
10 |
ReFrame RHEL9 migration. |
|
6 |
Plan-stage prompt used by |
System-prompt construction is in load_system_prompt(prompt_type). After the per-mode prompt and common.txt are concatenated, the function:
Appends
--- LESSONS LEARNED ---block from$OFA_SCRATCH/.ofa_lessons.txtif present.Appends
--- USER PREFERENCES ---block from$OFA_SCRATCH/.ofa_prefs.txtif present.Substitutes
{OFA_ROOT}and{OFA_SCRATCH}placeholders so prompts can reference deployment-specific paths without hard-coding them.
6.2 RAG (retrieval-augmented generation)ΒΆ
Eight ChromaDB collections served from $OFA_ROOT/vectordb/ (Chromaβs persistent client, default cosine distance, 384-dim Sentence-Transformers embeddings). Counts below are live as of this revision β re-read them with chromadb.PersistentClient(path='vectordb').list_collections() rather than trusting this table, since the corpora are rebuilt independently of the docs:
Collection |
Documents |
Source |
|---|---|---|
|
11,305 |
AMReX source ( |
|
10,221 |
OpenFOAM 13 source tree ( |
|
4,068 |
OpenFOAM tutorials (cleaned via |
|
2,115 |
Quantum-computing code + papers ( |
|
924 |
MARBLES source ( |
|
730 |
Kestrel documentation (Markdown) |
|
620 |
ReFrame source tree (RHEL9 migration tests) |
|
150 |
VASP documentation ( |
Total |
30,133 |
β |
Hybrid retrieval: each retriever combines dense (ChromaDB embedding similarity) and sparse (BM25 over tokens) scores. BM25 indices are pre-built at startup via _init_rag() and cached in memory for the session β first-query latency was prohibitive before the prebuild was introduced. The merge weights are tuned per retriever (see retrieve_context, retrieve_hpc_context, retrieve_amrex_context, retrieve_marbles_context, retrieve_quantum_computing_context, _get_reframe_rag).
Greeting bypass: retrieval is skipped for trivial queries (hi, hello, thanks, etc.) to avoid spending tokens on irrelevant context.
Fencing: retrieved snippets are wrapped via _fence_rag() in clearly delimited === RETRIEVED REFERENCE === tags, with a defence-in-depth reminder telling the model that fenced content is data, not instructions. Mitigates prompt-injection risk from documents we index.
Private data layer: each user can index their own data with
ofa --add-private <dir> into a separate per-user ChromaDB store at
$OFA_SCRATCH/vectordb-private (mode 0700), managed by
src/ofa_private_rag.py, which reuses rebuild_indices.pyβs chunking so the
store is format-compatible. _init_private_rag() discovers collections via
list_collections() rather than hardcoded names; retrieve_private_context()
runs the same hybrid search and _append_private_context() merges the hits
into whatever the active mode retrieved, labelled PRIVATE DATA. It applies
to every mode and to ofa --serve. The store is deliberately separate from
the shared vectordb/ because _init_rag() stages the latter with
rsync --delete, which would wipe anything colocated. .ofa_session.json and
.ofa_history are written 0600 since they persist retrieved snippets, and
--serve-no-auth prints an extra warning when a private store exists.
6.3 Long-term memory (two channels)ΒΆ
Per-user files in $OFA_SCRATCH, persistent across sessions:
File |
Channel |
Trigger |
Cap |
|---|---|---|---|
|
PREFS β user-explicit standing instructions |
User says βalwaysβ, βneverβ, βpreferβ, βfrom now onβ, etc. |
4 lines/turn |
|
LESSONS β model-autonomous observations |
Command failure understood, user correction, environment quirk discovered |
2 lines/turn |
Both channels share the implementation in _save_marker_block(text, label, channel, max_per_turn):
Scans for
=== LABEL === ... === END LABEL ===blocks withre.findall(multi-block per turn supported).Strips list-bullet prefixes (
-,*,β’) so the model can write bulleted lists naturally.Dedupes new entries against the existing file.
Caps at 16 KB total per channel; drops oldest entries on overflow.
Atomic write via temp file +
os.replaceso a crash mid-write canβt corrupt the file.Logs each save to stderr in magenta:
[memory] saved preference: <line>.
Both channels are injected into every requestβs system prompt (Β§6.1). The model is instructed in common.txt that PREFS overrides LESSONS on conflict (the user is ground truth).
The whole memory machinery was also unit-tested β see test_ofa_memory.py, 14 tests covering extraction, dedup, caps, atomic write under simulated os.replace failure, and the byte-cap eviction (see the caveat in Β§11 β the suites are not currently in the repo).
6.4 SkillsΒΆ
Markdown files in prompts/skills/ that the user can inject into the running session on demand:
prompts/skills/
βββ README.md # contract: name, format, lifecycle, security
βββ kestrel-debug-jobs.md # example: Kestrel debug-partition rules
Slash commands /skills (list), /skill <name> (load), /skill off <name> / /skill off all (unload). When loaded, a skill becomes a system-role message tagged [SKILL: <name>] inserted right after the base system prompt; /clear and session exit drop loaded skills.
A separate filename-stem allow-list refuses path traversal (.., leading dots, /, \) so users can only load files that actually live in the skills dir.
6.5 Safety guardsΒΆ
The CLI surface executes commands; thatβs where safety lives.
Destructive-command pattern matcher: regex screen catches
rm -rf /,dd of=/dev/..., recursivechmod/chownon system paths,mkfson real devices, etc. before they reachsubprocess.run. Match β red-banner approval prompt requiring exact-case confirmation typed by the user (Ctrl+C / EOF treats as βnoβ, not a crash).Consecutive-error pause: after 3 consecutive tool failures, the react loop hands control back to the user instead of letting the model thrash.
Tool output truncation: any single commandβs stdout/stderr is capped at 96 KB before being fed back to the model (head + tail keepers); session-wide context is compressed when it exceeds 100 KB (see Β§6.6).
Catastrophic command confirmation phrase: certain irreversible operations (e.g.
git push --force,rm -rfafter the regex screen passes due to a non-system path) require typing an exact phrase, not justy.Untested-model warning: at startup, if the active LLM is not in
TESTED_MODELS(currentlygemma4:31bandgemma4:31b-it-q8_0, the default), a loud red banner explains that destructive-command guards have only been validated against those. The model registry, picker UI, and the/modelsslash command are deliberately not exposed on the startup banner β see commit a4f1124 for the rationale.
6.6 Session context compressionΒΆ
When messages exceeds 100 KB, manage_session_context() walks oldest β newest (skipping the system prompt and the last 2 messages) and applies three compression strategies in order:
Strip
<thought>...</thought>deliberation from old assistant turns.Replace old
Output from executed commands: β¦user messages with a short placeholder.For any other old user message > 8 KB containing fenced code blocks, replace each
``` β¦ ```body with[Older code/output block omitted by system to preserve context memory.]β keeps the language tag (β```pythonetc.) so the model knows what kind of content was there.
Compression stops when below target (75 % of cap). If progress is made but the result is still over cap, a yellow /clear hint is emitted. If compression can free nothing, the function stays silent β earlier versions printed [System: Context size (N) near limit. Compressing old logs...] every turn even when no compression was possible, which was the original UX bug that drove the refactor (commit df81e1b).
Eight unit tests covered this in test_ofa_compress.py (see the caveat in Β§11 β the suites are not currently in the repo).
7. Code organisationΒΆ
7.1 Repository layoutΒΆ
$OFA_ROOT/
βββ bin/ofa # SLURM-aware shell wrapper
βββ src/
β βββ ofa_main.py # ~4,200 LOC: CLI, agent loop, RAG, memory
β βββ ofa_server.py # ~1,040 LOC: BYOK HTTP shim
β βββ ofa_client.py # ~ 390 LOC: stdlib-only Python client
β βββ ofa_site.py # site.toml loader (portability layer)
β βββ build_index.py # legacy index builder
β βββ build_index_v2.py # current index builder
β βββ ingest_amrex.py # AMReX source ingestion
β βββ ingest_reframe.py # ReFrame source ingestion
β βββ pdf_extract.py # PDF β text for the papers corpora
β βββ rebuild_indices.py # rebuild all collections
β βββ rebuild_tutorials_clean.py
β βββ rebuild_tutorials_of13.py
βββ vscode-ext/ # VS Code extension (TypeScript; see Β§5.4)
βββ tools/
β βββ byok-update-config.py # VS Code chatLanguageModels.json helper
βββ prompts/ # 8 mode prompts + common.txt + cpp/plan helpers + skills/
βββ vectordb/ # ChromaDB persistent store (8 collections)
βββ repos/ # live git clones for RAG + grep
βββ models/ # Ollama model weights (gemma4:31b-it-q8_0, etc.)
βββ embedding_model/ # bundled BAAI/bge-small-en-v1.5 weights
βββ env/ # bundled Python 3.13 virtualenv
βββ install.sh # one-command install on a new HPC
βββ site.example.toml # annotated site config template
βββ collections.toml # RAG collection definitions
βββ docs/
β βββ byok-vscode.md # BYOK setup walkthrough
β βββ byok-vscode-chatLanguageModels.example.json
β βββ rag-maintenance.md # corpus rebuild playbook
β βββ ofa-technical-overview.md # this file
βββ ARCHITECTURE.md # high-level architecture notes
βββ README.md
Total Python under src/: ~5,600 LOC across 12 files, plus the
TypeScript extension in vscode-ext/src/.
7.2 src/ofa_main.py walkthrough (~4,200 LOC)ΒΆ
Logical sections, in roughly the order they appear:
Section |
Purpose |
|---|---|
Path/env constants (~15β60) |
|
Model registry (~60β230) |
|
Behavioural constants (~230β250) |
Tool-output cap, session-compress thresholds, max-consecutive-errors. |
Terminal colouring (~245β280) |
|
Safe input helpers (~280β305) |
|
Scratch resolution (~305β340) |
|
Session persistence (~370β510) |
|
Long-term memory (~510β620) |
|
Skills (~620β720) |
|
Thinking-channel filter (~840β920) |
Streaming filter that hides |
|
The CLI agent loop: stream response β save prefs/lessons β execute tool calls β loop. |
Tool-fence parsing (~1100β1700) |
|
Ollama bootstrap (~1330β1700) |
|
RAG retrievers (~1770β2680) |
|
|
The Ollama API call. All chat traffic flows through here. |
|
Banner, slash-command dispatch, REPL loop. |
|
One-shot CLI mode and the planβgenerate-per-file pattern used by |
|
Argparse, dispatch to interactive vs single-query vs |
7.3 src/ofa_server.py walkthrough (~1,040 LOC)ΒΆ
Linear file, easier to read top-to-bottom:
Function / class |
Lines |
Purpose |
|---|---|---|
|
50 |
|
|
66 |
Dispatches to the right RAG retriever in |
|
89 |
Fences RAG context and prepends it to the user message. |
|
118 |
Drops inbound system messages, injects ofaβs, RAG-fences last user msg. |
|
147 |
OpenAI SSE chunk formatting. |
|
170 |
Bearer token persistence with 0o600 file perms. |
|
213 |
Used when |
|
252 |
Translates an Ollama |
|
281 |
The |
|
~370 |
Streaming branch. Two paths (tools-on / tools-off) emitting OpenAI SSE chunks. |
|
~460 |
Non-streaming branch (full JSON response). |
|
580 |
Persisted-or-random port helper, 0o600 file. |
|
613 / 629 |
Per-user stable ports in 40000β49999 (REMOTE) and 49200β64200 (LOCAL). |
|
645 |
Bootstrap: ensure Ollama, init RAG, resolve ports, set bearer token, start |
The whole thing depends on ofa_main only via _retrieve_for_mode and _augment_messages (RAG + system prompt) and chat_stream / _ollama_chat_raw (Ollama I/O). A colleague wanting to expose their own offline LLM via BYOK can use ofa_server.py as a template and replace those four call sites.
8. Deployment on KestrelΒΆ
8.1 Module fileΒΆ
The Lmod modulefile is at /nopt/nrel/apps/cpu_stack/modules/default/application/assistant.lua (outside the repo). Two functions:
help([[...]])β shown bymodule help assistant. Lists CLI invocations, env-var overrides, BYOK quick start, slash-command pointer.LmodMessage([[...]])at load time β banner printed when the user doesmodule load assistant. Includes the active-model warning, override env vars, usage table, and the two-line BYOK pointer.
Environment exports:
Variable |
Value |
|---|---|
|
|
|
|
|
|
|
|
The modulefile is updated in-place; changes are live for everyone on the next module load.
8.2 Per-user runtime stateΒΆ
Everything else lives under $OFA_SCRATCH (defaults to /scratch/$USER):
File |
Purpose |
|---|---|
|
Last interactive-mode message history. |
|
readline history for the interactive prompt. |
|
Long-term user preferences (PREFS channel). |
|
Model-autonomous lessons (LESSONS channel). |
|
(none on disk β skills are session-only.) |
|
Bearer token for |
|
Per-user persisted REMOTE port (0o600). |
|
Per-user persisted LOCAL port for the printed ssh -L line. |
|
PID + port of the userβs |
|
Per-user lock to serialise heavy ChromaDB inits. |
The 0o600 mode on the api-key / port files matters: $OFA_SCRATCH can be group/world-readable depending on filesystem ACLs, and these files contain user-specific secrets or routing info.
8.3 SLURM allocation flowΒΆ
User runs
ofaoutside a SLURM job βbin/ofacallssallocwith the userβs default account, requesting--gres=gpu:1 --ntasks-per-node=32 --mem=80G --time=00:30:00 -p debug.Within
salloc, the wrappersrun --ptys itself onto the compute node and re-execsofa.The compute-node-side wrapper unsets/cleans Cray PMI and SLURM step env vars (so any
srunthe agent kicks off later creates fresh steps) and finally execspython3 src/ofa_main.py "$@".ensure_ollama_running()finds a free per-user port (UID-derived hash), spawnsollama serveif no responding instance exists, waits for/api/tags._init_rag()loads ChromaDB collections and the BM25 token caches into memory.
The user can override account / partition / walltime via OFA_ACCOUNT / OFA_PARTITION / OFA_WALLTIME before invoking ofa. The recommendation for longer sessions is OFA_PARTITION=gpu-h100 OFA_WALLTIME=04:00:00 ofa.
9. Safety and securityΒΆ
Concern |
Mitigation |
|---|---|
Destructive commands |
Regex screen + red approval prompt + exact-phrase confirmation for catastrophic patterns. |
Tool-output exfiltration |
Output cap (96 KB) keeps massive command stdout from being fed back verbatim. Session compression strips old fenced blocks. |
Prompt injection via indexed docs |
|
BYOK auth |
|
BYOK network exposure |
|
Untested model risk |
Models outside |
Per-user isolation |
Each user has their own |
ofa --serve runs entirely within Kestrelβs internal network; the SSH port-forward is the only path from outside, and bearer tokens are mandatory unless the user explicitly opts out.
10. Performance characteristicsΒΆ
Metric |
Value |
|---|---|
Model |
|
Hardware |
NVIDIA H100, 1Γ per user (quarter-node) |
Cold-start |
~30β90 s (salloc + ollama load + model into GPU + RAG init) |
First chat reply |
~5β15 s (model already warm, RAG retrieval included) |
Subsequent replies |
~1β5 s for short turns, ~10β30 s for plan-then-generate file batches |
Throughput |
~30β60 tokens/sec generation on a single H100 |
RAG retrieval |
< 200 ms per query (ChromaDB warm; BM25 caches in memory) |
Index size on disk |
~740 MB ( |
Model weights |
~34 GB for the default model; |
The dominant latency on first reply is GPU warm-up; on subsequent replies itβs token generation. RAG and prompt construction are insignificant by comparison.
11. TestingΒΆ
The suites described below are not currently in the repository. They were written to
/tmpduring development and/tmphas since been cleaned, so nothing here is runnable or verifiable today. The table is retained as a specification of the coverage that existed at commita24a58band as a starting point for reinstating them under atests/directory wired into CI.
Four hermetic suites, all stdlib unittest, no network and no GPU.
Suite |
Cases |
Coverage |
|---|---|---|
|
14 |
|
|
15 |
Listing (empty, with README, with non- |
|
7 |
Session-history compression: old tool outputs, @file pastes, protected-tail silent no-op, under-threshold no-op, partial-progress + |
|
26 |
BYOK HTTP: routing, auth (5 header formats), |
Total |
62 |
β |
All 62 passed at commit a24a58b. Note that vscode-ext/ has its own
check β npm run typecheck β which is wired into CI via
.github/workflows/vscode-ext.yml.
12. AppendixΒΆ
12.1 Environment variablesΒΆ
Variable |
Default |
Purpose |
|---|---|---|
|
|
Code + assets root. |
|
|
Per-user runtime state. |
|
|
ChromaDB store. |
|
|
Override the LLM. |
|
1.0 |
Sampling temperature. |
|
0.95 |
Top-p sampling. |
|
64 |
Top-k sampling. |
|
1.15 |
Penalise repetition. |
|
32768 |
Max tokens per response. |
|
per-model (262144 for the default) |
Context-window tokens. Taken from |
|
99 |
GPU layers (99 = all). |
|
userβs SLURM default |
salloc account. |
|
|
salloc partition. |
|
|
salloc walltime. |
|
|
salloc GRES. |
|
|
salloc job name (the VS Code extension sets |
|
UID-derived |
Pin the per-user |
|
(unset) |
Path to an external model registry merged over |
|
|
Site config file (see |
|
deploy roots |
Extra path prefixes the agent refuses to write to. |
|
(unset) |
Disables ANSI colour. |
12.2 CLI flags (selected)ΒΆ
Flag |
Effect |
|---|---|
(none) |
General coding assistant β equivalent to |
|
OpenFOAM case / dictionary generator. |
|
Kestrel HPC documentation mode. |
|
General coding assistant. |
|
AMReX C++ framework assistant. |
|
MARBLES (LBM thermal solver on AMReX) assistant. |
|
Quantum-computing assistant (rigorous math verification). |
|
VASP assistant. |
|
ReFrame testing for RHEL9 migration. |
|
Reload |
|
Write the assistantβs |
|
Skip RAG retrieval. |
|
OpenFOAM single-shot (skip plan stage). |
|
Override |
|
Print model registry and exit. |
|
Index a directory into the per-user private RAG store. |
|
Collection label for |
|
Vision-OCR for PDFs during |
|
List private RAG collections and exit. |
|
Delete a private collection (or |
|
Start BYOK HTTP server. |
|
Pin REMOTE port (default: per-user persisted). |
|
Pin LOCAL port (default: per-user persisted). |
|
Bind address (default |
|
Bearer-token file (default |
|
Disable bearer-token auth. Local dev only. |
|
Suppress the per-request log line (used by the VS Code extension). |
|
Forward |
12.3 Slash commands (interactive mode)ΒΆ
quit | exit | q β exit
/clear β reset conversation (keeps system prompt, drops loaded skills)
/compact β aggressively compress history now (strips old RAG/tool blocks, keeps prose + last 2 turns intact)
/history β show session size with a per-bucket breakdown
/cwd β show current working directory
/retry β re-prompt the model and demand a proper tool fence
/memory β show what's stored in long-term memory
/remember <text> β manually add a lesson to long-term memory
/forget [prefs|lessons|all] β clear stored memory
/skills β list available skill files
/skill <name> β load a skill into this session
/skill off <name> β unload a skill (use 'all' to unload every skill)
/models β list pulled models and how to switch
save <dir> β save last assistant response into <dir>
$ <shell command> β run a shell command locally (cd persists)
@<path> β inline a file into your prompt (relative to cwd)
/help β this message
12.4 Key referencesΒΆ
Repo: https://github.com/nileshsawant/onfield-assistant
BYOK walkthrough:
docs/byok-vscode.mdBYOK config helper:
tools/byok-update-config.pyVS Code BYOK docs: https://code.visualstudio.com/blogs/2026/06/18/byok-vscode
Ollama: https://github.com/ollama/ollama
Gemma 4: https://blog.google/technology/developers/gemma-4/ (Apache 2.0)
ChromaDB: https://www.trychroma.com/
ReFrame: https://reframe-hpc.readthedocs.io/
12.5 Recent commit highlightsΒΆ
a24a58b docs(byok): rewrite byok-vscode.md to lean 5-step quick start
e45be78 feat(serve): per-user stable Kestrel-side port (default)
9816511 feat(serve): --serve-enable-tools forwards tools/tool_calls to Ollama
3b137e9 feat(tools): byok-update-config.py to register all 5 OFA modes
0fe7eb8 fix(serve): accept multiple auth header formats; bind 0.0.0.0 default
08d7e18 docs(serve): label REMOTE vs LOCAL port explicitly
b933e0b feat(serve): dynamic port allocation on both sides
79b52dc feat(serve): print exact ssh tunnel command at startup
a4f1124 refactor(banner): hide model menu from startup, expose via /models
d2a83b4 feat(serve): add `ofa --serve` OpenAI-compatible BYOK shim for VS Code
df81e1b fix(context): make session compression effective and quiet when no-op
497722d feat(skills): on-demand skill loading via /skill
00e3f76 feat(memory): autonomous long-term memory with prefs + lessons channels
124 commits total at the time of writing.
Document maintained alongside the code. If anything in this overview disagrees with the repo, the repo is the source of truth.