OpenCode is a free, open-source AI coding agent that runs locally on your machine and connects to over 75 different AI model providers. If you are like me you may use it as the agent/harness for local model inference.
As of today (or yesterday) OpenCode has shipped version 2.0 (or v2 depending on where you look), which comes with a few nice improvements. Of particular interest to me is the rebuilt API, background sub-agents, and enhanced plugin system (plugins are no longer considered experimental). Lets take a look at what is new in 2.0.
What’s new
Feature
What’s changed
Why it matters
Centralized background service
One persistent OpenCode service supports the terminal, desktop, web interface, and API clients.
Sessions and automation can be managed across interfaces.
Rebuilt API
Completely redesigned API with a new JavaScript client. Agents can manage sessions, worktrees, and other OpenCode operations.
Better scripting and external orchestration.
Code Mode
Agents execute JavaScript to discover, combine, and call MCP tools.
Potentially significant reductions in tool-call overhead and context consumption.
Background subagents
Agents can delegate tasks to other agents, including those running different models.
Parallel coding, testing, research, and code reviews.
Background shell commands
Long-running commands execute asynchronously without blocking conversations.
Builds and tests can run while the agent continues working.
Enhanced plugin system
More than 80 internal plugins, stable TUI plugins, and hot-reloading.
Easier customization without restarting OpenCode.
Embeddable SDK
New @opencode/sdk package exposes the engine as a library.
Build customized agents and integrations using OpenCode’s underlying infrastructure.
Personally I use switch back and forth between both OpenCode and PI for local LLM work. Both work well, and both can be a bit chatty. In particular PI, which even with Deepseek shares each and every though that is has with you – its a very chatty harness, OpenCode is much less chatty, but a bit less light weight than PI. So lets try OpenCode 2.0 and see how it performs.
Updating NPM Installed Opencode to Opencode 2.0
Note that if you installed OpenCode via npn the latest available version is 1.19.0, 2.0 is not available via npm at this time. Here are the steps to preserve your opencode configs (skills/MCPs) and seemlessly migrate to OpenCode 2.0
Back up the config. Version 1 and version 2 read the same config files, so keep a copy until 2.0 is working
cp -a ~/.config/opencode ~/.config/opencode.v1-backup
cp -a ~/.local/share/opencode ~/.local/share/opencode.v1-backup
Remove the npm opencode. This deletes only the program:
sudo npm uninstall -g --prefix /usr opencode-ai
hash -r && which opencode # should print nothing
Install 2.0 with the official install script. It installs to ~/.opencode/bin/opencode, doesn’t need sudo, and adds that folder to your PATH in ~/.bashrc
curl -fsSL https://opencode.ai/v2/install | bash
exec $SHELL
opencode --version # should report 2.x
Things to look out for:
Don’t reinstall opencode-ai from npm, or use the old install script. Both versions use the opencode command, and either one would put 1.x back or clash with 2.0.
No config changes are needed. 2.0 reads your existing opencode.json and converts the old format in memory without rewriting the file. The guide says converting the file to the new format is optional (for example, mcp. becomes mcp.servers., agent becomes agents, command becomes commands).
CLAUDE.md is no longer read. Your global AGENTS.md is still read. If any project relies on CLAUDE.md for opencode, copy that guidance into an AGENTS.md.
Skills might not be picked up. The migration guide only lists .opencode/skills/ folders inside a project; it doesn’t mention ~/.claude/skills or ~/.agents/skills. If your skills don’t appear in step 4, point the skills array in opencode.json at those two folders.
Plugins from 1.x don’t work in 2.0.
Personally I did not run into any issues, and did not need to restore my backup configs. Skills loaded, MCPs loaded, no issues thus far.
In early 2026, Meta reportedly created an internal token-spend leaderboard called “Claudeonomics,” ranking employees by how many AI tokens they consumed.
The predictable result was enormous consumption. When token usage becomes the metric, people optimize for token usage.
Meta may have been an extreme example, but the underlying idea was everywhere for a while. Across the technology industry, employees were being encouraged to experiment aggressively with AI, agents, automation, and new workflows.
Use AI.
That was often the entire strategy.
Perhaps you had a similar experience. I know I did.
Riding the Adoption Wave
I was happy to take my organization up on the offer.
These were powerful new tools, becoming more capable almost weekly. I wanted to understand them, so I dove in.
My workflows changed repeatedly. I adopted tools, abandoned others, experimented with models and agent harnesses, learned MCPs and Skills, and started integrating AI into the infrastructure and operational work I was already doing.
The objective seemed obvious:
These tools aren’t going away. Learn how to use them.
For me, that meant leaning even harder into Infrastructure-as-Code and AI-assisted operations. Services became reproducible installs committed to Git, documented, idempotent, and accompanied by health checks and operational tooling.
AI wasn’t replacing the work. It was changing how I approached the work.
And for a while, many organizations seemed to want exactly that.
The mythical “10x engineer” was back, this time carrying a token budget.
Then somebody saw the bill.
Coin a Phrase “Token-Min’ing”
Fast-forward a few months.
Token budgets were reduced. Monthly quotas appeared. Approved-model lists became smaller. Suddenly the employees who had been encouraged to integrate AI deeply into their workflows were being asked to economize.
Thus began the age of token-min’ing.
Tools and techniques appeared specifically to reduce context, shrink prompts, compress agent communication, and squeeze more work out of fewer tokens.
And that is not inherently bad.
Frontier models cost real money. Efficient use of them matters. Learning how to accomplish the same task with fewer tokens is itself an important engineering skill.
But the rapid swing from token-maxxing to token-min’ing raises a more important question:
What did organizations build while everyone was learning?
Did they develop internal AI capability?
Or did they simply develop dependency on external AI providers?
And if an organization’s AI strategy stops working the moment token budgets are reduced, was it really an AI strategy?
Adoption Was Never Supposed to Be the Destination
The early AI adoption wave should have been a discovery phase.
Give engineers access. Let them experiment. Find out what works. Learn which workflows generate real value.
Then turn those discoveries into organizational capability.
That could mean:
shared inference infrastructure;
model gateways and routing;
internal knowledge and memory systems;
RAG;
reusable Skills and MCP integrations;
standardized agent workflows;
security and secrets management;
governance and guardrails;
selectively hosted internal models;
and business-specific automation built around APIs and existing systems.
Not every company needs racks full of GPUs.
In many cases, the valuable intellectual property isn’t the model anyway. It is the workflow surrounding the model: the integrations, business logic, knowledge, guardrails, prompts, Skills, agents, and automation that allow employees to solve actual problems.
Some organizations understood this.
Others treated AI primarily as another SaaS product to provision.
That distinction matters now.
Organizations that used the experimentation period to identify useful workflows can move those capabilities into shared platforms, optimize model selection, route workloads intelligently, and decide which workloads belong on frontier models and which can run elsewhere.
Organizations that didn’t are left with a much simpler lever:
Reduce the token budget.
Employees Have Already Made a Different Decision
There is another problem.
The people who successfully incorporated AI into their work aren’t necessarily going backward.
AI subscriptions are rapidly becoming another part of the professional toolkit — not fundamentally different, from the employee’s perspective, from buying a better monitor, keyboard, IDE, or productivity application.
I see this in my own experimentation.
As I write this, I have OpenCode working through custom firmware for an open-source ESP32 watch using a locally hosted model running on an AMD GPU.
At home I experiment with local inference, different models, agent harnesses, routing, MCPs, Skills, and the infrastructure required to support them.
I don’t use personal systems for non-personal work, but they are increasingly where I continue learning.
That creates an uncomfortable problem for employers.
If employees reach their corporate AI quota and simply move some of their experimentation to personally funded tools, the organization hasn’t eliminated AI usage.
It may simply have eliminated its visibility into it.
That is a considerably worse outcome for governance, security, knowledge sharing, and organizational learning.
Once someone has experienced a meaningful productivity improvement from AI, telling them to stop using it doesn’t necessarily eliminate the demand.
It can simply make the usage invisible.
The Incentive to Hide AI Productivity
Now consider a different problem.
Suppose an employee discovers a workflow that lets them accomplish eight hours of work in six.
What incentive do they have to announce that?
If the likely response is:
“Great, here are two more hours of work,”
“Maybe we don’t need as many people,”
“Everyone should now achieve this output,”
or “This is simply the new baseline,”
then the rational behavior may be to say nothing.
The employee becomes invisibly more productive.
They continue delivering good work, but the organization never learns exactly why.
The employee keeps the workflow.
The company gets the output.
And the larger organization loses the opportunity to learn from the person who figured it out.
That is a terrible trade.
Harvest the Gain or Reinvest It
Organizations have a choice in what they do with AI-driven productivity improvements.
They can harvest the gain, or they can reinvest it.
Harvesting is easy.
An employee automates 25% of their workload, so management fills that newly available 25% with more tickets, meetings, projects, and responsibilities.
The reward for becoming more efficient is a larger workload.
There is another option.
Let some of that recovered capacity be reinvested into:
finding the next automation opportunity;
improving existing workflows;
turning personal tooling into reusable organizational tooling;
teaching coworkers;
creating shared Skills and integrations;
documenting successful patterns;
experimenting with new use cases;
and identifying where additional AI investment would produce the greatest return.
Consider a team whose job is to catch fish.
One person discovers better fishing holes, develops better techniques, and builds better lures.
Management can respond:
“You catch twice as many fish, so your quota is now twice as high.”
Or it can ask:
“Why are you catching twice as many fish, and how do we teach everyone else to do it?”
The first approach harvests the individual’s productivity.
The second creates organizational capability.
And if you consistently choose the first, don’t be surprised when your best fisherman stops telling you where the fishing holes are.ly how all organizations work.
The Employee Who Automates Their Job May Have Found Their Next Job
This may be the largest missed opportunity of all.
Some employees are naturally good at spotting repetitive work, undocumented processes, disconnected systems, and information that should flow automatically but doesn’t.
AI dramatically increases what those people can do.
If someone repeatedly automates parts of their own job, the best use of their newly available capacity may not be assigning them more of the same work.
It may be asking them to automate parts of everyone else’s.
Those employees can become force multipliers: building integrations, reusable agents, internal platforms, operational tooling, and workflows that turn individual productivity gains into organizational ones.
That requires organizations to think differently about AI productivity.
The objective shouldn’t be to maximize tokens.
It shouldn’t be to minimize tokens either.
It should be to maximize capability created per token spent.
The organizations that understand that distinction will come out of this period with more than a collection of AI subscriptions.
They’ll come out of it with people, platforms, workflows, and institutional knowledge that didn’t exist before.
And that was probably the real opportunity all along.
Over the last several months I have been doing a bit of experimentation locally hosted LLMs, primarily with vLLM, llama.cpp, different quantization methods, GPU configurations, and models. My overall goal is a smart enough local agent that can code and troubleshoot, and ask smarter frontier models for help/review.
Part of this journey has been learning about all the tuning knobs. As knowing the api is responding is not really enough.
I also want to know things like:
What model is actually loaded?
How much VRAM is it consuming?
How much VRAM do I still have available?
Is the GPU actually doing anything?
What context size am I running?
How much KV cache is being consumed?
What parameters did I start the inference engine with?
How fast is the model actually responding?
Most of this information is available somewhere, but I wanted to not have to hunt and peck for data.
Normally, I would check Docker, query an API endpoint, run amd-smi (or nvidia-smi), look through vLLM startup logs, inspect environment variables, and then run another script just to benchmark the model. Not hard, but requires a few steps or maybe a script.
Instead, I built a dashboard.
The project is called the Inference Engine Dashboard, and the goal is pretty simple: give me one place to see what my local inference server is doing.
This is not a proxy, or a chat engine. There are already plenty of those.
This is an operations and monitoring dashboard for vLLM and llama.cpp.
What I Wanted From the Dashboard
My primary use case is a workstation running a local LLM while I am also using the machine normally.
That makes VRAM particularly important, as I need to keep enough VRAM available if I want to fire up the desktop (DE).
For example, I may intentionally configure vLLM to use less than the maximum available GPU memory because I still need enough VRAM left for my desktop, browser, IDE, and other applications.
In that environment, I want to be able to open a webpage and immediately see something like:
GPU: AMD Radeon AI PRO R9700
VRAM used: 23 GB / 32 GB
Inference process: 21 GB
Other GPU applications: 2 GB
Remaining headroom: 9 GB
Model: Qwen
Context: 32K
KV cache utilization
GPU utilization
GPU temperature
Current token throughput
Basically, the things I normally end up checking manually.
Local-First Architecture
I deliberately designed the dashboard to run on the same physical machine as the inference engine as some information is easy to get remotely from the vLLM or llama.cpp API, but detailed hardware and process information is not.
To accurately determine what is happening on the host, the dashboard may need access to:
GPU devices
GPU process information
Docker container metadata
Inference process IDs
ROCm libraries
Model cache directories
Host CPU and memory information
The application itself runs using Docker Compose.
docker compose ps
I also did not want to simply give the application unrestricted access to /var/run/docker.sock, so Docker access is provided through a restricted Docker socket proxy.
Model caches and other host resources are mounted read-only wherever possible.
That was an important design decision because eventually I may allow the dashboard to manage inference engines, but I don’t want to jump directly from “monitoring dashboard” to “application that can arbitrarily manipulate containers.”
Current Dashboard Information
The main dashboard provides a quick overview of the host and inference environment.
It includes information such as:
Host
Hostname
IP address
Operating system
Kernel
CPU
Total system memory
GPU
GPU model
GPU utilization
Temperature
Power consumption
VRAM used
VRAM available
Inference-process VRAM
Other GPU process usage
Inference Engine
Active model
Backend
API health
Metrics health
Maximum context
KV-cache utilization
Current throughput
Runtime configuration
The dashboard also displays locally cached models so I can see what is already available on the host.
If the inference engine or GPU tooling doesn’t expose a particular value, the dashboard displays it as unavailable rather than trying to estimate it.
Monitoring vLLM
vLLM exposes quite a bit of useful information, but it isn’t all available from one location.
The dashboard therefore combines information from several sources, including:
/health
/v1/models
/metrics
along with:
Docker inspection
Container environment variables
vLLM startup logs
GPU process telemetry
From those sources the dashboard can display information including:
Active model
Served model name
vLLM version
Container image
Maximum model length
GPU memory utilization target
Precision
Quantization
Maximum sequences
Maximum batched tokens
Prefix caching
KV-cache type
KV-cache allocation
KV-cache utilization
KV-cache token capacity
CPU offload
Swap configuration
Maximum concurrency
Prompt throughput
Output throughput
Model-weight VRAM
Inference-process VRAM
Other GPU VRAM usage
Remaining VRAM headroom
Some of the more detailed memory information is only available in the vLLM startup logs.
This is another reason the dashboard runs locally.
If vLLM doesn’t provide enough information to reliably calculate a value, the dashboard simply reports it as unavailable.
Monitoring llama.cpp
One of the more interesting parts of the project was adding llama.cpp support as llama.cpp and vLLM expose very different information.
My first thought was to simply use the same dashboard fields and populate whatever llama.cpp could provide.
That quickly resulted in a screen containing a whole bunch of:
N/A
N/A
N/A
N/A
which isn’t particularly useful.
So the dashboard now uses provider-specific runtime views.
For llama.cpp it queries endpoints including:
/health
/v1/models
/props
/metrics
/v1/completions
Depending on what the server exposes, the llama.cpp runtime panel can display:
Active GGUF model
Configured context window
Native model context window
GGUF quantization
Model parameter count
Model file size
Parallel slots
GPU layers
Batch size
Microbatch size
Temperature
Top-K
Top-P
Min-P
Repeat penalty
llama.cpp build version
Container image
Token throughput
Inference-process GPU memory
This makes the llama.cpp view much more useful than trying to force llama.cpp into a vLLM-shaped dashboard.
GGUF Model Discovery
The dashboard can also scan a configured directory containing GGUF models.
For example:
/models
or wherever your llama.cpp model library happens to live.
The directory is mounted read-only into the dashboard container and any discovered GGUF files are displayed in the model selector.
At the moment this is informational.
The dashboard can see the models, but it cannot switch to them yet.
That is intentional.
AMD GPU Monitoring
My current workstation uses an AMD GPU, so ROCm support was a requirement from the beginning.
On AMD systems the dashboard primarily uses:
amd-smi
with ROCm SMI available as a fallback.
In addition to total GPU memory consumption, I wanted to know who was using the VRAM.
That allows the dashboard to distinguish between:
Inference Process
Other GPU Processes
Unattributed GPU Memory
Free VRAM
This is particularly useful on a workstation.
If my inference engine is configured to consume around 23 GB of a 32 GB GPU, I want to know whether the remaining GPU memory is actually available or whether Chrome, my desktop environment, an IDE, or something else has already consumed part of it.
The Interactive Benchmark
I also wanted a quick way to test a model after changing its configuration. Wasn’t looking for a massive benchmark suite.
Just something that answers:
“Does this model actually feel fast when I interact with it?“
The dashboard includes a:
Run TTFT + token rate test button.
The benchmark sends a deterministic streaming request through the OpenAI-compatible endpoint.
/v1/completions
The current test uses:
Temperature: 0
Seed: 1
Maximum output: 128 tokens
Concurrency: 1
Warm-up requests: 0
The test records three values that I care about for interactive use.
Time to First Token
How long did I wait before the model actually started responding?
Output Tokens Per Second
Once generation started, how quickly did the model generate tokens?
End-to-End Latency
How long did the complete request take?
The benchmark results are stored in SQLite along with information including:
Model
Prompt tokens
Completion tokens
Seed
Time to first token
Generation time
End-to-end latency
Output tokens/sec
Raw streaming evidence
That means I can start building some history as I experiment with different models and runtime settings.
What This Benchmark Is Not
The built-in benchmark is intended as a quick interactive performance test.It is not intended to replace a proper inference benchmarking suite.
Currently it does not test:
Multiple simultaneous users
P50/P95/P99 latency
Long-context performance
Sustained GPU utilization
Warm versus cold requests
Maximum batch throughput
Extended resource sampling
Those are all things I may eventually add.
For now I mainly want to answer:
I changed something. Did the model get faster or slower?
For that, the test works pretty well.
Deployment
Deployment is intentionally simple.
Clone the repository, configure the environment, and run:
./scripts/deploy.sh
Host-specific settings are stored in a gitignored:
.env
file.
The same dashboard can be configured for either inference engine by setting:
INFERENCE_BACKEND=vllm
or:
INFERENCE_BACKEND=llama_cpp
and providing the appropriate API endpoint and model-cache location.
The containers use Docker’s:
unless-stopped
restart policy, so the dashboard will come back automatically after the host reboots and Docker starts.
Why Model Switching Is Disabled
You may notice that the dashboard can discover models but currently doesn’t allow you to activate one.
This is intentional.
It would actually be fairly easy to add a button that stops an inference container and starts another model.
The problem is everything that can happen after I push that button.
For example:
Stop the running inference engine.
Start the new model.
Model doesn’t fit in VRAM.
Container crashes.
API never becomes healthy.
Previous configuration is now gone.
My inference server is offline.
Not particularly useful.
Before enabling model switching I want the application to support something closer to a transaction:
Save current configuration
↓
Stop current model
↓
Start requested model
↓
Validate container
↓
Validate API
↓
Validate model
↓
SUCCESS
If something fails:
FAIL
↓
Restore previous configuration
↓
Restart previous model
↓
Validate health
Once that logic exists, enabling lifecycle controls makes much more sense.
Security
Authentication is also not implemented yet.
So at this point the dashboard should only be exposed on a trusted network.
That is less concerning while the application is primarily read-only, but it becomes much more important once lifecycle controls are introduced.
Before enabling model switching or container management I plan to add authentication and additional authorization controls.
Why I Built It
This really came out of repeatedly doing the same troubleshooting process.
I would SSH into the workstation and run something like:
docker ps
Then:
amd-smi
Then:
curl http://localhost:8000/v1/models
Then:
curl http://localhost:8000/metrics
Then look through the container logs:
docker logs <container>
Then maybe run another script to see how fast the model was responding.
None of those things are particularly difficult.
It is just a lot of little pieces of information spread across several different tools.
Now I can open one page and immediately see:
What model is running
Whether the inference server is healthy
How much VRAM it is consuming
What else is using the GPU
How much VRAM I have left
What context size is configured
How much KV cache is being used
How the inference engine was configured
How quickly the model starts responding
How many tokens/sec it is generating
Which is basically what I wanted.
What’s Next
There are several things I would eventually like to add.
The big one is obviously safe model switching.
Beyond that I would like to explore:
Model configuration profiles
Transactional model switching
Automatic rollback
Authentication
Historical GPU metrics
Historical VRAM consumption
Benchmark history and comparison charts
Longer-context benchmarks
Concurrent request testing
NVIDIA GPU support
Additional inference engines
There is also an interesting possibility of having the dashboard recommend runtime settings based on available GPU memory and the selected model.
But I don’t want this to become another giant AI management platform.
The original goal is still the most important one:
Open one page and understand what my local inference server is doing.
So far, it has made experimenting with vLLM and llama.cpp considerably easier.
It’s the first time I’ve gone non-Nvidia since before 2010 as I’ve been a Nvidia loyalist since the GeForce 256 back in 1999 (was 3dfx prior). But Nvidia GPU pricing on anything with decent VRAM has gotten absurd, even on the used market. I kept hearing that ROCm (Radeon Open Compute) support for inference has finally matured. So I decided to find out for myself.
I picked up the AMD Radeon AI PRO R9700, 32GB. Not cheap, but brand new its cheaper than an RTX 3090 24gb from 2020.
Since this is my first AMD in very long while, going to have to figure out drivers. So let’s get started. First lets gather some info by running the commands below.
lspci -nn | grep -Ei 'VGA|Display|AMD|ATI'
lsmod | grep amdgpu
dmesg | grep -i amdgpu | tail -50
Verify GPU Detection
Prior to card swap, purged my Ubuntu 24.04 system of all Nvidia drivers and tools.
Lets check to see if the card is detected properly
lspci -nn | grep -Ei 'VGA|Display|AMD|ATI'
This command searches the PCI device list for anything matching VGA, Display, AMD, or ATI. In the output below we see the following
03:00.0 is the PCI id of the R9700 GPU
Vendor ID 1002 confirms AMD Device
7551 is how the kernel identifies this GPU generation/device
The two PCI bridge entries show the GPU exposes an internal PCIe switch/bridge topology
01:00.0 PCI bridge: AMD Navi 10 XL Upstream Port of PCI Express Switch
02:00.0 PCI bridge: AMD Navi 10 XL Downstream Port of PCI Express Switch
The important takeaway is that Ubuntu already recognizes the R9700 correctly and the in-kernel AMDGPU driver is successfully initializing it.
lsmod | grep amdgpu
This command above shows whether the AMDGPU kernel module is loaded and what other kernel modules depend on it.
amdgpu 21188608 26
This tells us:
amdgpu is loaded
the module occupies roughly 21 MB of kernel module memory
26 references/users currently depend on it
dmesg | grep -i amdgpu | tail -50
The command above shows the last 50 kernel messages associated with AMDGPU initialization.
The most important things we see are:
MEM ECC is active.
The GPU’s VRAM ECC capability is active.
VRAM: 32624M
The kernel detects approximately 32 GB of VRAM.
Detected VRAM RAM=32624M, BAR=32768M
Preparing for ROCm Install
First lets make sure that the kernel has created the ROCm compute device
ls -l /dev/kfd
crw-rw---- 1 root render 235, 0 Aug 9 16:20 /dev/kfd
/dev/kfd is the Kernel Fusion Driver device that ROCm/HIP uses to submit compute workloads to the AMD GPU. AMD’s ROCm installation process relies on access through the render/video device groups.
In the output above, you can see that the Kernel Fusion Driver is owned by root with group membership of render. We need to add my user to the render group. Note that I had to reboot to pick up the changes.
sudo usermod -aG render cpaquin
Now we can add the ROCm 7.2.2 package repo, which is two steps. This one below…
gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null
And this next one to actually add the repo.
sudo tee /etc/apt/sources.list.d/rocm.list <<'EOF'
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/7.2.2 noble main
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/graphics/7.2.1/ubuntu noble main
EOF
Now let’s be smart and ensure that the repo was added properly.
cat /etc/apt/sources.list.d/rocm.list
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/7.2.2 noble main
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/graphics/7.2.1/ubuntu noble main
Now before we install anything, per AMD recommendation, we need to pin the above repo priority. Pinning means settings which repository Ubuntu prefers when the same package is available from multiple repositories, which means prefer the AMD repos over Ubuntu repos for AMD bits and bobbles.
sudo tee /etc/apt/preferences.d/rocm-pin-600 <<'EOF'
VRAM usage is 661 MB / 32,624 MB, which is normal for the display/driver stack.
No uncorrectable ECC errors: UEC 0.
No compute processes are currently using the GPU.
Test ROCm inside Docker
Can a Docker container access the R9700 through ROCm? Lets find out.
docker run --rm \
--device=/dev/kfd \
--device=/dev/dri \
--group-add 992 \
--group-add 44 \
rocm/rocm-terminal:latest \
rocminfo
What this does:
starts a temporary AMD ROCm container
passes /dev/kfd into it for ROCm compute
passes /dev/dri into it for the GPU device nodes
gives the container the needed video (44) and render (992) group access
runs rocminfoinside the container
deletes the container afterward because of --rm
If it works, we should see the same R9700 identification inside Docker that we saw on the host, including something like:
Pytorch Test
Next we’ll use AMD’s validated PyTorch container for ROCm 7.2 / Ubuntu 24.04 / Python 3.12 / PyTorch 2.9.1. AMD recommends Docker for PyTorch because the image already contains a tested PyTorch+ROCm combination.
This will likely download a fairly large container image the first time.
What we want to see is essentially:
PyTorch: 2.9.1+rocm7.2.0.git7e1940d4
ROCm: 7.2.26015-fc0010cf6a
GPU available: True
GPU: AMD Radeon AI PRO R9700
One potentially confusing point: PyTorch keeps the API name torch.cuda even when using AMD ROCm.
torch.cuda.is_available() returning True means PyTorch has a usable GPU backend; on this container that backend is HIP/ROCm, not NVIDIA CUDA. AMD uses this same test in its ROCm PyTorch verification instructions.
So the R9700 is now validated for actual ROCm/PyTorch compute inside Docker. Next Step VLLM and test some models.
Fired up my RHEL9 server today, something that I have not done in a while due to the savage summer heat in the southern US. All my Dell servers have had the summer off for this reason.
Upon login via ssh I saw a warning message, which I do not think I have seen before on this machine – all my other Dell servers are RHEL 10, and this one was built with RHEL 9 due to deprecated support for older processors in RHEL 10. Specifically the Intel Xeon E5-2697 (Ivy Bridge) v2.
Anyway, below is the warning message.
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
This warning means that the SSH client supports post-quantum key exchange, but the RHEL 9 SSH server did not offer one. It does not indicate that the current connection is broken; it warns about possible future decryption of captured traffic.
Hence the “store now, decrypt later“ message above.
SNDL/HNDL
“Store Now, Decrypt Later” (also called Harvest Now, Decrypt Later) is when an attacker intercepts encrypted traffic, not with the idea of decrypting it now, but rather once they have access to a more powerful quantum computer running advanced algorithms. Today’s standard SSH connections rely on classical asymmetric cryptography (like Elliptic Curve Diffie-Hellman) which quantum computers can theoretically break.
Meaning, you may not be vulnerable now, but you could be later.
Most newer OpenSSH clients warn when a connection does not negotiate a post-quantum key-exchange algorithm because long-lived or sensitive data captured today could remain valuable in the future.
Honestly this is not a big concern in my lab, but the warning will drive me up a wall.
The RHEL 9 Fix
The good news is that RHEL 9.1 and later support the following hybrid post-quantum SSH key-exchange method. So we can remediate.
sntrup761x25519-sha512@openssh.com
This combines the post-quantum sntrup761 algorithm with the traditional X25519 elliptic-curve key exchange. Because it is hybrid, the connection remains protected as long as either component remains secure.
Red Hat added support for this algorithm to the RHEL 9 system-wide cryptographic-policy framework, but it must be explicitly enabled through a custom subpolicy.
Confirm the RHEL Version
First, lets confirm that the server is running RHEL 9.1 or later:
cat /etc/redhat-release
You can also check the installed OpenSSH and crypto-policy package versions:
rpm -q openssh-server crypto-policies
Update OpenSSH and the Crypto-Policy Packages
sudo dnf update openssh-server crypto-policies
This ensures that the server has the RHEL packages that include support for the SNTRUP hybrid key-exchange algorithm.
In my case, this step initially revealed a separate Red Hat subscription problem:
Status code: 403 for https://cdn.redhat.com/...
A 403 from the Red Hat CDN generally means that the server can reach the repository, but the system is not currently authorized to download its content. This may be caused by an expired developer subscription, stale registration data, an invalid entitlement, or a system identity left over from an earlier installation.
Useful commands for checking the registration state include:
sudo subscription-manager identity
sudo subscription-manager status
sudo subscription-manager repos --list-enabled
As I said this machine has been powered off for a while, so lets refresh registration and repository metadata.
sudo subscription-manager refresh
sudo dnf clean all
sudo rm -rf /var/cache/dnf
sudo dnf makecache
In my case, the system registration is stale, so I need to unregister and re-register.
sudo subscription-manager unregister
sudo subscription-manager clean
sudo subscription-manager register
After registration, we ensure that the standard RHEL 9 repositories are enabled:
sudo subscription-manager repos \
--enable=rhel-9-for-x86_64-baseos-rpms \
--enable=rhel-9-for-x86_64-appstream-rpms
Then rebuild the DNF cache:
sudo dnf clean all
sudo rm -rf /var/cache/dnf
sudo dnf makecache
Create a Custom Crypto-Policy Module
Next we create a new policy module named SNTRUP:
sudo tee /etc/crypto-policies/policies/modules/SNTRUP.pmod >/dev/null <<'EOF'
key_exchange = +SNTRUP
EOF
The module name must use uppercase characters because crypto-policy module filenames are conventionally uppercase and end with .pmod.
The policy adds the SNTRUP algorithm to the key-exchange methods permitted by the existing system-wide policy.
Apply the Policy
Apply the custom module on top of the normal RHEL DEFAULT policy:
sudo update-crypto-policies --set DEFAULT:SNTRUP
Confirm that the new policy is active:
update-crypto-policies --show
You are looking for expected output as shown below.
DEFAULT:SNTRUP
RHEL uses system-wide cryptographic policies to configure applications including OpenSSH, TLS libraries, IPsec, DNSSEC, and Kerberos. Using a crypto-policy module is therefore preferable to manually changing generated OpenSSH backend files.
Restart SSH
Next we restart sshd so that it reads the updated cryptographic policy:
sudo systemctl restart sshd
And we confirm that the service restarted without issue
sudo systemctl status sshd --no-pager
Verify the Server Configuration
Run the command below to check the current SSH server key-exchange configuration:
sudo sshd -T | grep -i '^kexalgorithms'
The output should include:
sntrup761x25519-sha512@openssh.com
We can also inspect the generated OpenSSH server crypto-policy backend:
Files beneath /etc/crypto-policies/back-ends/ are generated by the crypto-policy framework and should not normally be edited directly. Manual changes may be overwritten the next time the system policy is updated.
Now it’s time to test.
7. Test a New SSH Connection
Open a new terminal window, leaving the existing window open just in case we broke something. In the second window, connect with verbose logging:
You can also validate the SSH configuration before restarting the service:
sudo sshd -t
No output means that the configuration passed validation.
Important FIPS Consideration
Do not replace an existing FIPS policy with DEFAULT:SNTRUP on a system required to operate in FIPS mode.
Check the current policy first:
update-crypto-policies --show
Also check whether the kernel is operating in FIPS mode:
cat /proc/sys/crypto/fips_enabled
A value of 1 means that FIPS mode is active.
Algorithms permitted by the normal RHEL DEFAULT policy are not automatically permitted under the RHEL FIPS policy. Red Hat documents that certain Curve25519-based SSH key exchanges are not allowed in FIPS mode, so the SNTRUP hybrid method should not be enabled without validating the applicable compliance requirements.
Rolling Back the Change
To remove the SNTRUP subpolicy and return to the standard RHEL default policy:
sudo update-crypto-policies --set DEFAULT
sudo systemctl restart sshd
Confirm the result:
update-crypto-policies --show
Expected output:
DEFAULT
The custom policy file can then be removed if it is no longer needed:
After enabling the RHEL crypto-policy module, restarting sshd, and creating a new connection, the server and client should negotiate:
sntrup761x25519-sha512@openssh.com
The SSH session still uses familiar symmetric encryption and authentication mechanisms. The portion being changed is the initial key exchange used to establish the session keys.
This does not make the entire SSH protocol “post-quantum.” It adds a hybrid post-quantum key-exchange mechanism designed to protect the session-establishment process against both traditional attacks and the future possibility of cryptographically capable quantum computers.
This server that has a few years of useful service (specifically for my Infiniband testing), but cannot move to RHEL 10. So enabling the supported RHEL 9 hybrid key exchange is “good enough”.
The tool sprawl in AI engineering has officially outrun our collective memory, at least it has mine.
Between native terminal agents, newly minted desktop clients, and entirely divergent API cost models, keeping the ecosystem straight is a full-time job.
I built this reference guide for a very simple reason: I needed a single, no-nonsense source of truth to track what tools each frontier provider actually offers right now, and exactly how running autonomous developer loops will affect my wallet.
Here is the state of the stack as I know it and use it. Sure there are plenty of options I am missing.
The Tooling Matrix: Interfaces, CLIs, and IDEs
This matrix maps out exactly where each provider expects you to interact with their models, highlighting the shift toward dedicated desktop environments and autonomous terminal agents.
None natively (Relies on community tools like Ollama or Aider)
Native integration into open-source extensions like Continue and Cline
Perplexity
Perplexity.ai (Pro search & “Pages” workspace)
Perplexity Desktop(Mac & Windows)
None (Relies on API keys for third-party scripts)
None (Primarily used as a data-grounding search API)
The Economic Matrix: Quotas, Costs, and Loop Behavior
This is the most critical matrix, IMHO. As someone who is experimenting with numerous paid frontier model services, each with a limited budge, I am not all in on any service yet, and want to maximize my low-paid token tiers.
This table explains how the subscription models and APIs.
Provider / Service
Developer Plan & Cost (USD)
IDE / CLI Billing Mechanism
Quota / Rate Limit Mechanics
The “Agentic Loop” Behavior (The Cost Trap)
GitHub Copilot
Pro: $10/mo Max: $100/mo
Flat-rate subscription for native tools.
Hybrid Metered: Unlimited standard completions + a fixed pool of premium model credits.
The Safest Route: Because it’s integrated, Microsoft absorbs a massive amount of the token cost, but heavy loop usage will hit a speed throttle unless on the Max tier.
Anthropic(Claude)
Pro: $20/mo Max 5x: $100/mo
Billed via web account or separate Pay-As-You-Go API.
5-Hour Rolling Window: Dynamic usage budget that resets every 5 hours based on server demand.
High Risk for Pro: Running Claude Code on a large infrastructure repository will hit your 5-hour limit fast. Full-time agentic workflows require switching to Claude Max or the raw API.
OpenAI(ChatGPT)
API Account (Pay-As-You-Go)
BYOK (Bring Your Own Key): Paid per token used in Cursor, Aider, etc.
Volumetric Tiers: Limits depend on lifetime account funding (Tiers 1–5), scaling from 200k to millions of tokens/min.
Predictable but Costly: Perfect for episodic troubleshooting, but an out-of-control recursive autonomous loop can easily rack up a $20–$50 bill in an afternoon.
Google(Gemini)
Code Assist Standard ~$19/mo
Flat-rate per user or metered hourly via Google Cloud.
Concurrently Throttled: Throttles users if background parallel multi-agent loops request too much compute at once.
Great for Standard Dev: Highly stable for individual coding, but hard to hook into un-vetted, third-party autonomous command-line tools.
DeepSeek
Platform API (Pay-As-You-Go)
BYOK: Funded via a pre-paid developer wallet.
Wallet Funded: Strictly bound by whatever dollar amount you load into your account.
The Economy Champion: Because of their unified architecture and heavy 90%+ prompt caching discounts, running heavy agentic loops costs literal pennies compared to western models. $10 can last a month.
Perplexity
Perplexity Pro $20/mo
Subscription includes a small monthly API credit.
Fixed Credit Ceiling: API access stops or charges your card when your small monthly search token allowance runs dry.
Not For Coding Loops: Great for pulling real-time documentation or API schemas into your agent, but financially unviable for writing or refactoring code blocks.
Understanding the Layers of Agentic LLM Tool Execution
To expand this post, it is useful to define several concepts that are now common in agentic AI systems: tools, skills, MCP servers, local execution layers, and IDE-integrated actions.
Out of the box, an LLM is primarily a reasoning and text-generation system. You can ask it questions, request explanations, have it draft a script, or ask it to review configuration, and it will respond with generated text. By itself, however, the model does not inherently have access to your operating system, local files, terminal, IDE, logs, repositories, ticketing systems, monitoring platforms, or infrastructure.
That distinction is important.
A hosted model may appear to have additional capabilities, such as reading uploaded documents, searching the web, generating files, or analyzing code, but those capabilities are usually provided by the surrounding platform, client application, or agent runtime rather than by the model alone.
This is where concepts such as tools, skills, MCP servers, and agent runtimes become important. These components extend an LLM from a passive chatbot into an interactive agentic system. The model can reason about what needs to happen, request the appropriate tool, inspect the result, and continue working through a task. The runtime around the model is responsible for exposing those tools, enforcing policy, executing approved actions, and returning results back to the model.
When discussing LLMs that can inspect logs, modify files, run scripts, query systems, or interact with development environments, it is important to separate the layers involved. These capabilities are often described broadly as “agentic AI,” but agentic behavior is usually the result of several distinct components working together.
The model provides reasoning and structured tool-call requests. The orchestration layer manages the workflow, state, prompts, and tool-calling loop. The local execution layer performs approved actions against the operating system, filesystem, terminal, repository, or other target environment. IDE-integrated actions expose workspace-specific capabilities such as file editing, diagnostics, Git state, and terminal access. Skills provide reusable task-specific instructions or workflows. MCP servers provide a standardized way to expose tools, resources, and prompts to compatible AI clients.
In other words, the LLM is tool-aware, but the surrounding runtime is tool-authoritative.
The model may decide that it needs to read a log file, edit a configuration file, run a script, or inspect a repository. However, the runtime determines which tools exist, which actions are allowed, where those actions execute, what policy controls apply, and what results are returned to the model.
The table below provides a high-level comparison of the major concepts involved in LLM-driven local execution and tool integration.
LLM Agent Execution Layers and Integration Concepts
Concept
Formal Name
What It Means
What It Controls or Provides
Example in Practice
Tool calling
Tool calling / function calling / structured tool invocation
The model’s ability to request an external action using a defined schema instead of free-form text.
Provides the structured interface between the LLM and external capabilities.
The model requests read_file(path="/var/log/app.log") or run_tests(command="pytest").
Tool
Agent tool / callable tool / local execution tool
A specific function or action that can be invoked by the agent workflow.
Provides a single capability, such as reading a file, tailing logs, running a script, or checking Git status.
Do you remember where you were on November 30th, 2022. If you were in the US (or perhaps Liberia or parts of Mexico as I have recently come to learn), you may have been sitting around the house eating leftover turkey, or a nice ham sandwich (if you were fortunate to have both available on Thanksgiving).
November 30th, 2022 was the launch date for ChatGPT.
Although it was very rough around the edges and not ready for prime-time, many immediately saw into the future. I cannot say that I did immediately, but I was out there experimenting, watching it “lie” to me. Those “lies” were quickly labeled hallucinations and sounded much cooler. Anyway the race was on apparently. Not that I was fully paying attention at the time.
Like most engineers across infrastructure, hardware, or software, I’ve spent the last four years just trying to keep my head above water. Figuring out what to learn and what to ignore has become almost as exhausting as the actual learning process. On one side, you have the performative hype from influencers desperate to prove how far ahead of the curve they are. On the other, there’s a deafening wall of noise from frontier model providers and newly christened startups, all frantically trying to dump a product before their razor-thin window of entrepreneurship slams shut.
As a traditional infra-engineer, LLMs and agentic coding have been quick to deliver value. I have never identified as a software engineer, but rather ahardware and infra-centric practitioner. I’ve spent my work-life architecting, deploying, and supporting Unix/Linux, virtualization platforms, and enabling hardware functions such as SR-IOV for Telcos across North America.
I’m the guy who knows the difference between raid 0+5 and 5+0.
Suddenly, I am now a technologist who now has the tools to quickly deliver true infrastructure as code without having to become an evangelist for any specific automation platform. Suddenly, its much easier to deploy tools, supporting services, create dashboards, and troubleshoot and resolve system/hardware issues.
Strangely I have always loved creating documentation, and have spent many years being frustrated with the lack of documentation that I have seen from others. To me, a measure of success is the fact that a solution that you built, is still relied upon once you are gone. Without proper documentation, and the ability for your successors to truly understand, operate, and enhance something that you built is dependent on great documentation. If you can not transfer the appropriate level of operational and design knowledge to those who replace you, they will replace your solution as well. With AI, everyone now has the power of documentation. “Review this repo and describe how it works, update the README.md”. It’s truly a wonderful thing.
The Micro-Eras of Agentic Coding
Having done my best to keep up via reading, experimenting, and building, I have learned quite a bit in a very short time. Those who share the same passions and beyond are often learning the same things at the same time. Separately, but also together, as they uniformly change the vernacular of agentic coding. First, we learn how to prompt from the perspective of a persona, defining outcomes. Next, we discover the need for guardrails and frameworks, especially when the word security rears its ugly head.
Nothing below is unique or original, and I am not sure that there is actually a difference between 3 & 4, or possibly the Eras are in the wrong order, but here is my table for trying to keep straight concepts and terminologies that describe those contexts
Era & Focus
The Primary Problem Solved
The Technical Stumbling Block
The AI Engineer’s Job
1. Prompt Engineering (2022-2023)
Expression: Shaping the tone, persona, and output formatting of the LLM.
Hallucinations: Cannot conjure up data or facts it wasn’t trained on.
Writing clear, contextual instructions and few-shot examples.
2. Context Engineering (2023-2024)
Information: Injecting real-time, proprietary data via RAG and Vector DBs.
Passivity: The model can read your data, but cannot act on it or use tools.
Managing token budgets, chunking data, and optimizing semantic retrieval.
3. Harness Engineering (2024-2025)
Environment: Giving the model tools, secure sandboxes, and safety guardrails.
Rigidity: Linear chains break completely when the model makes a minor mistake.
Building secure execution environments, MCP servers, and input/output parsers.
4. Loop Engineering (2025-2026)
Autonomy: Letting the system evaluate, self-correct, and iterate recursively.
Compute Cost: High token consumption due to repetitive agent cycles.
Designing state machines, testable termination conditions, and exit criteria.
“It’s all vibecoding, right”
The term “vibe-coding” was officially coined by Andrej Karpathy (former Director of AI at Tesla and co-founder of OpenAI) in a viral post on X (Twitter) on February 8, 2025.
And it’s a term that I despise, despite fully understanding how fitting that term was at the time.
Fast forward 1.5 years, and now “vibe-coding” sounds silly and very passe, as to me it implies sitting down with a chat window and an idea, and nothing more. This is what kids do in the basement to build a fun yet forgettable android game, right?
And this is not where we are today. Today we are knee-deep in learning, and imagining what the next step is in this agentic evolution.
Planning, context, guardrails, loops, governance, frameworks, and harnesses is where we are, at least mainly in theory, and sometimes in practice.