Skip to content

Latest commit

 

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

codesmith

A CLI coding agent and harness, built from scratch in TypeScript. Runs against local models through Ollama.

Why

I wanted to understand what actually happens inside a coding agent, not just use one. The fastest way to find out was to build the three pieces by hand: a streaming LLM client, an agent loop that executes tool calls, and a harness that wires them to a terminal and the filesystem. No framework, no copy-paste.

It runs on local models on purpose. Iterating on an agent loop means a lot of throwaway calls, and I didn't want to pay per token to learn. Ollama exposes an OpenAI-compatible endpoint, so the provider is a thin layer over the openai SDK pointed at localhost. If you have never hosted a model locally, llm/README.md walks through the setup.

Architecture

architecture diagram

Three packages, one for each layer. The dependency direction is strict: the harness knows about the loop, the loop knows about the provider, and the provider knows nothing above it.

  • packages/ai - the provider. OllamaProvider.stream() is an async generator. It yields text chunks as they arrive, accumulates any tool calls from the stream, appends the finished assistant message to the history, and returns the collected toolCalls and reasoning as its final value.
  • packages/agent - the loop. runAgentLoop() is also an async generator. It drains the provider stream and forwards the chunks, then looks at the returned tool calls. No tool calls means the turn is over. Otherwise it executes each one, pushes a tool message with the result, and goes round again.
  • packages/coding-agent - the harness. Owns the system prompt, the tool implementations, the map from tool name to function, and the REPL. Each user prompt goes into the shared message history and the loop's output is written to stdout as it streams.

The loop does not know which provider it is talking to. It only relies on the shape of the stream: yield chunks, return { reasoning, toolCalls }. That is what makes adding a second provider a contained change.

Tools

Four tools ship today.

  • bash - runs a shell command. Input is tokenised with shlex and checked against an allowlist (ls, cat, grep, find, head, tail and a few others). Any shell operator in the arguments rejects the whole command. It is a read-only shell for now.
  • read - reads a file at a path.
  • write - writes content to a path.
  • get_weather - hits Open-Meteo for a temperature. It's the tool I used to prove tool calling worked before the filesystem tools existed. It stayed because it is a nice sanity check that costs nothing.

Tool schemas live in tools/all.ts and are sent to the model on every call. The harness validates argument types before dispatching, so a model that sends the wrong shape gets an error instead of a crash.

Running it

You need Node 22 or newer (it runs .ts files directly) and Ollama serving a model that supports tool calling.

ollama pull glm-4.7-flash
cd packages/coding-agent && npm install
cd ../ai && npm install
node packages/coding-agent/harness.ts

The model name is set in harness.ts. Swap it for whatever you have pulled.

Status

Working end to end: streaming responses, multi-step tool use, a REPL that keeps history across prompts. MIT licensed. Progress is tracked in CHANGELOGS.md.

Not built yet, and why:

  • Anthropic provider. The loop is provider-agnostic on paper. It has only ever run against one provider, so that claim is untested. Adding Claude is the test.
  • Context compaction. Nothing trims the history yet. Long sessions will eventually hit the model's context window and fall over. The plan is to summarise once usage crosses 80%.
  • Tool permissions. write and bash run without asking. The allowlist keeps bash harmless, but the real answer is a permission prompt before anything destructive, and that isn't there yet.
  • Reasoning display. The provider already collects reasoning tokens. The harness throws them away. Surfacing them is a UX change, not a plumbing one.
  • Tests, grep, sessions, a TUI. All on the list. None block the core loop, so they wait.

About

CLI coding agent in TypeScript. Provider-agnostic loop with streaming read, write and bash tools.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages