Skip to content

Repository files navigation

g023 Code

A pure-Python AI coding agent for your terminal, powered by DeepSeek V4.

Subagent-First · Context is Currency · Terminal-native

PythonVersionLicenseModel


g023 Code reads your project, searches it, searches the web, runs commands, fetches pages, and looks at images — while telling you exactly what it is doing and what it just cost. Data-heavy work is delegated by default: a subagent reads the file in an isolated context and what comes back to the orchestrator is a structural summary plus a symbol → line-range map. When the summary is not enough, the orchestrator asks for an exact line range and gets verbatim source — so "summary first, raw on request" is the design, not "never raw".

That trade is a real trade, and it is worth being precise about which half of it is measured. The call counts below are asserted by the test suite. Whether delegation is cheaper overall is not: it depends on how many turns the file's text would otherwise have sat in context, which no test here can know. See break-even for the arithmetic and its assumptions.

It talks to DeepSeek exclusively through the Responses API, which is what makes DeepSeek's own native web search a first-class tool of the loop rather than a search provider bolted on the side.

The g023 Code startup banner: version, project root, program home, model and thinking level, vision backend and a live Ollama reachability probe

The banner on launch — version, project root, program home, model and thinking level, and the vision backend with a live reachability probe of the Ollama daemon. Every screenshot in this README is a real terminal from a real 1.3.2 session; docs/README.md is the full index and explains how they were captured.

Table of contents

Quick start

Requirements: Python 3.11 or newer, and a DeepSeek API key.

Drop the folder anywhere you like and run the installer inside it:

cd /path/to/g023-code
./installer.sh
Windows
cd C:\path\to\g023-code
installer.bat

It finds a Python 3.11+, builds a .venv in the folder, installs only the dependencies you are actually missing, asks once for your API key, writes a default config.json, and puts g023 on your PATH. Every step checks what is already true first, so running it again is safe — it is also how you repair a half-finished setup or add the optional extras later.

Installer options
Linux / macOSWindowsEffect
--yes/yNon-interactive; take the recommended default everywhere
--key sk-…/key sk-…Write the key into K.dat instead of prompting ($DEEPSEEK_API_KEY is used if set)
--with-optional / --no-optional/optional / /nooptionalDecide the extras (vision preprocessing, browser-grade fetching) up front
--no-venvInstall into the current interpreter instead of a venv
--no-path/nopathDo not touch ~/.local/bin, your shell rc, or the user PATH
--uninstall/uninstallRemove the launcher shim and PATH entry (the folder, .venv, K.dat and config.json stay)

Then launch from whatever project you want it to work on:

cd~/some/project
g023

Doing it by hand instead is four commands — pip install -r requirements.txt, your key on the first line of K.dat, then /path/to/g023-code/g023.sh (or g023.bat) from a project folder. The launchers prefer .venv/ in the install folder when one exists, and set two environment variables for you:

VariableMeaning
G023_HOMEThe installation folder — where K.dat and config.json live
G023_PROJECT_ROOTThe directory you launched from — your project

A per-project .g023/ folder holds the SQLite cache and your input history.

prompt_toolkit is listed as a dependency but is not strictly required: with it you get tab completion, persistent history, and a live status bar pinned to the bottom of the terminal. Without it the input line falls back to a plain prompt and everything else behaves identically.

What you see while it runs

The point is to never wonder what it is doing, or what it just cost.

One read-only question answered: a tool trace of list, search and two reads with timings, the boxed answer, and the per-turn accounting line

One question, end to end. Each tool call prints its result line and duration (list . → 2 entries, search "api" → 8 matches in 3 files, two reads), then the answer, then the accounting: 3 iteration(s) · 4 tool call(s) · slowest ReadFile 5 ms · model 5.5 s / tools 11 ms over 4,299/900,000 (0%) · cache 79% · turn $0.00044.

Tool traces say what happened (708 lines, 9 classes, cached), not what was sent. The status line after every turn carries the numbers that only mean something together — context used, cache hit rate, cost of this turn, cost of the session — because on V4 Flash a cached input token is billed at $0.0028/M against $0.14/M for an uncached one. That 50x is DeepSeek's price sheet, not something this harness achieves; what the harness does is avoid breaking the prefix so more tokens fall on the cheaper side.

Costs shown are computed locally from the token counts the API returns and the price table in SPEC.md §3.2. They are an estimate of your bill, not a reading of it — /cost prints the price line it used so the arithmetic is checkable, and splits cache hit from cache miss because they are priced an order of magnitude apart.

The /cost dashboard: tokens and spend split into cache-hit and cache-miss columns, with the price line printed underneath

Nothing that writes, runs a command, or leaves your machine happens without a prompt. The prompt shows the path and an excerpt of the exact content, and takes y (once), a (always for this tool this session) or n:

WriteFile stopping for permission, showing the target path and an excerpt of the content to be written

The same turn after approving: wrote 197 B in 7 ms, followed by the answer

The trace above the prompt also shows the escalation path in use: read app/schemas.py returns a summary (25 lines, 3 classes), then read app/schemas.py:1-8 fetches that head verbatim.

Anything you type that does not start with / is a prompt. Everything else is a slash command.

Configuration

/settings shows every setting in one place and marks which ones persist; /tools shows what the agent may do and what it has done this turn. Both are editable from inside the session, and the ·saved keys are written to config.json next to K.dat, so they follow you across projects.

The /settings panel: every setting with its live value, with the persistent ones marked ·saved

The /tools panel: all eight tools, their current permission, and which run in an isolated subagent context

/tools also marks which tools run in an isolated subagent context rather than in the orchestrator's — that column is the delegation policy, visible.

Defaults out of the box: Flash for the orchestrator and subagents, thinking on at high effort, verbosity low, auto-compaction on at 85% of the window, acting tools set to ask, and vision disabled.

/status puts the whole session on one screen when you want the summary rather than the knobs:

The /status dashboard: model, session counters, vision, project, cache size, context and spend on one screen

Architecture

Everything — orchestrator, subagents, compaction — goes to a single endpoint: POST /responses. That is a deliberate choice, not an implementation detail. It is the only DeepSeek endpoint that exposes the server-side web_search tool, so using it everywhere is what lets web search be part of the loop.

The Responses API does not take a list of chat messages. It takes items — and the model's own output items go back into the next request verbatim:

  • message items (your input; the model's narration and its final answer)
  • reasoning items — echoed back unmodified so the model keeps its own chain
  • web_search_call items — the server's record of a search it ran
  • function_call / function_call_output pairs, one per tool call

Two rules fall out of that, and every part of the program respects them. A function_call with no matching output is a hard 400 in either direction, so compaction, truncation and error recovery all repair pairing before the next request. And items are echoed byte-identical, because rewriting them both breaks the prefix cache and desyncs the model's reasoning.

  • Orchestrator (deepseek-v4-flash) keeps only high-level reasoning, tool schemas, and compact summaries.
  • Subagents handle data-heavy work in isolated contexts:
    • FileReader → structural metadata, a symbol → line-range map, and a 2–4 sentence summary, cached by content hash
    • Searcher → metadata-first grep results, pure local Python — no API call
    • Explore / Plan → isolated reasoning with thinking mode
    • Vision → an Ollama daemon, local or remote (DeepSeek V4 Flash is text-only)
  • Native web_search runs server-side inside the model's turn — no client executor, no provider chain
  • SQLite cache → a repeated read of unchanged bytes is a local lookup and costs no API call
  • Prefix-cache-friendly: instructions and tool definitions stay static
  • Thinking mode with a reasoning_effort dial (low / high / max, or off — which maps to reasoning: {"effort": "none"}, the only thing /responses actually honours)

One model.deepseek-v4-flash is the only model sent to the API, in every role. /model refuses deepseek-v4-pro because calls to it on /responses returned an error when this was last checked, and refusing with that reason attached beats handing you a model every call fails on. If DeepSeek has since enabled it, that refusal is stale — it is a hardcoded list in config.py, not a capability probe. Vision is the only other exception, and it does not use the DeepSeek API at all.

The presentation and command layers are deliberately separate from the agent:

ModuleResponsibility
commands.pyThe command catalogue as data — one source of truth shared by /help, tab completion, and "did you mean". A command cannot be completable but undocumented.
ui.pyTheme, glyphs, gauges, and the compact renderings of tool calls and their results. Falls back to ASCII on terminals that cannot draw box characters.
prompt.pyThe input line and the shared numbered picker. Degrades cleanly without prompt_toolkit.
ollama_client.pyHost resolution, reachability probes, model discovery, and vision inference against any daemon.

See SPEC.md for the full specification.

What delegation costs

Delegating a read is not free — it is a second API call, and sometimes it is the wrong trade. Here is what is actually true, separated by how well it is known.

Measured. These call counts are asserted in tests/test_call_accounting.py, so they are checked rather than remembered:

OperationAPI calls
SearchContent, any size0 — pure local regex
ReadFile, Python under 12k chars, no focus0 — summarised from the local AST
ReadFile, same bytes again (cache hit)0
ReadFile with start_line/end_line0 — returns verbatim source
ReadFile, first read of a non-Python file, a file over 12k chars, or any focused read1

Arithmetic, given assumptions. A 40 KB file is roughly 10k tokens. Inlined into the orchestrator, it is re-sent on every subsequent turn — cheaply, if it stays in the cached prefix, at $0.0028/M. Summarising it costs one Flash call (~10k in, ~300 out ≈ $0.0023 at miss rates) and leaves ~300 tokens in context instead of 10k. Which wins depends on how many turns follow and whether the prefix survives them, and those depend on your session. Delegation is not unconditionally cheaper; it is unconditionally smaller, and smaller context is what keeps the model on task and the window from filling.

Not measured. Nobody has run a controlled A/B of end-to-end session cost. The flag to do it exists:

G023_READFILE_RAW=1 g023 # ReadFile returns raw content, no subagent

With it set, whole-file reads return the file's bytes and cost zero API calls — the baseline a plain agent loop would have. Run the same task script with and without it and compare /cost. Until someone does that on their own codebase, the honest claim is the one above: fewer tokens in the window, an extra call to get there.

When the summary is not enough. The known weakness of summary-first is that a summary can silently drop the one detail that mattered, and nothing detects that. What exists instead is a cheap way out: every Python summary carries

"symbols": {"Orchestrator.run_turn": [412, 498], "parse_reply": [199, 262]}

with line numbers taken from the AST (decorators included), not from the model. Escalating is then one targeted ReadFile with those two numbers, which returns verbatim text and costs no API call. That turns "the summary was too thin" from a re-read of the whole file into a precise second look. It does not turn it into something the orchestrator notices automatically — that detector does not exist.

Drift signals

The client does not validate responses on purpose: an unknown field has to reach the model without a client release. The price is that a renamed field fails quietly — output_text returns "", the model looks silent, and nothing raises. Silent degradation, not a crash, is the realistic worst case here.

/signals (alias /drift) shows the three cheapest observations that would move first — what the loop noticed but nobody raised an error about:

The /signals panel: prefix cache hit-rate drift, unrecognised response item types, and unexplained empty responses

SignalWhat it catchesWhat it cannot tell you
Unknown item type in a responseAn additive API change, at the earliest moment it is visibleWhether it matters
Empty output with no incomplete_reasonWhat a renamed content field looks like from outsideWhether the model simply said nothing
Prefix hit rate diffed against previous daysAnything that started breaking the prefixWhether the cause is your prompt, the tool list, or the server

The hit rate is stored per day in .g023/cache.db and compared against the trailing baseline, so the comparison survives restarts. It needs two prior days before it will call anything a baseline, and says so until then. /cache clear does not erase it — it is a record of what happened, not cached data.

None of these is a diagnosis. A model behaviour change, a schema change, and drift in your own prompts all present identically from here: same call, worse output, no error. These signals make the change visible and date it; they do not separate the causes.

Slash commands

Commands that take a fixed set of options open a picker when typed bare, so /vision is as usable as /vision qwen3.5:2b — there is nothing to memorise. /help <command> explains any one of them in full, and bare /help puts the whole command surface on one screen, grouped by what it touches:

The /help panel: every slash command on one screen, grouped by what it touches

Session
CommandAction
/help [command]List commands, or explain one in detail
/statusOne-screen dashboard: model, context, cost, vision, cache
/clearReset the conversation and usage counters
/exitQuit
Model
CommandAction
/model [flash]Show or set the orchestrator model — flash is the only one the Responses API serves
/thinking [low|high|max|off]Set reasoning effort, or turn thinking off
/verbose [low|mid|high]How much detail to print while working
/steps [n|off|default]Cap the tool steps one turn may take before it gives up (default 255; off for no cap, with progress printed every 10 steps)
/max-tokens [n|off|default]Cap how long one reply may be (default off — no budget is sent, so the model's own maximum is the only ceiling)
Context & cost
CommandAction
/compact [focus]Summarise the history (micro = free local pass, auto on|off)
/contextBreak down what is occupying the context window, by role and size
/costToken usage and spend, split by cache hit/miss, with a per-turn sparkline
/settings [save|reset]Every setting, marking which persist; save or restore defaults
/signals(alias /drift)Prefix hit rate against its own history, unknown item types, unexplained empty responses
Tools
CommandAction
/toolsList tools, their permission level, and how often each ran
/tools <tool> allow|ask|blockChange one tool's permission
/fetch <url>Fetch a URL yourself, with the cache/fresh prompt
/fetch statusReport how closely fetches imitate a real browser
/cache [stats|web|clear]Inspect and purge the local SQLite caches

The /cache panel: entry counts for each local SQLite cache, plus the database's size and path

Vision
CommandAction
/ollamaStatus: host, version, latency, installed model count
/ollama host <addr>Point vision at another machine (tested before saving)
/ollama host defaultFall back to OLLAMA_HOST, then localhost
/ollama modelsList installed models, sizes, quantisation, vision capability
/ollama test [model]Run a real inference — proves the whole path, not just the port
/ollama psWhat the daemon currently holds in VRAM
/visionInteractive picker of installed vision models
/vision <model> · /vision offEnable a specific model, or disable
Work
CommandAction
/goal <text>State a high-level objective; runs one turn at max reasoning effort with no step cap

Verbosity, context & cost

/verbose has three levels, persisted to config.json:

LevelShows
low(default)The answer, plus a one-line trace of every tool call
mid↑ plus each tool's outcome and duration, and the per-turn token/cost line
high↑ plus the orchestrator's reasoning excerpts and per-iteration token counts

/context answers the question /cost cannot: what is filling the window. It breaks the history down by role and size, and the hint under the table names the cheapest fix:

The /context breakdown: tool results are 76% of a ~1,752-token history, with a hint naming the cheapest fix

If tool results dominate, /compact micro clears the stale ones for free; if it is real conversation, /compact summarises it with the cheap model in an isolated context. The command tells you which of the two applies. Compaction also runs automatically once the context passes compact_threshold (85%) — turn that off with /compact auto off.

The four frames below are one continuous run — three read-heavy turns, then /context, /compact micro, /compact, /context:

Before compacting: 33 items, about 3,922 tokens, tool results 46% of it

Before — 33 items, ~3,922 tokens, tool results 46% of it.

/compact micro clearing old tool results: about 949 tokens reclaimed at no API cost

/compact microcleared old tool results (~949 tokens, no API cost). This layer is local string surgery, so the session cost on the status bar does not move.

/compact summarising 33 items down to 2, reclaiming about 1,448 tokens for a summary cost of $0.00086

/compactCompacted 33 items → 2 (~1,448 tokens reclaimed · summary cost $0.00086). The summarising layer pays the cheap model and reports both sides of the trade.

/context after compaction: two items totalling about 1,348 tokens

After — two items, ~1,348 tokens: one user item holding the summary, plus one assistant acknowledgement.

Every API call — orchestrator and subagents — reports its token usage to a single tracker, so /cost and /settings show the cache-hit / cache-miss / output split per model and price it from the SPEC §3.2 table.

Web search (native)

Web search is DeepSeek's own, not a search API wired in behind it. There is no provider chain, no key to supply, no rate limit to nurse, and no /websearch command — because there is nothing on this side to configure.

The entire integration is one line in tools/schemas.py:

WEB_SEARCH_TOOL= {"type": "web_search"}

That marker is appended to the tool list. No name, no parameters, no executor. The server takes it from there.

The search happens inside the model's turn, on DeepSeek's infrastructure, before the response comes back. g023 never sees a tool call for it and never returns a result for it. What arrives instead is a record of what the server did, which shows up in the trace like any other tool:

 ● searching the web…
✓ web_search deepseek responses api web_search tool, deepseek v4 pricing
✓ web_search read platform.deepseek.com/docs/…

A few consequences are worth stating plainly:

  • It never asks permission. There is no moment at which g023 could interpose a prompt — the search is over by the time the response exists. So web_search has no entry in /tools. Giving the model the tool is the decision; each individual search is not. Everything that does leave your machine under g023's own control — FetchUrl — still asks every time.
  • It can search several times per turn, following up on what it finds.
  • Findings persist. The server's web_search_call items are carried in history like everything else, so what it learned survives across tool round-trips, across turns, and across /compact.
  • Slow searches are visible. The server can be out on the web for a while without emitting anything, so g023 draws a live searching the web… line rather than letting it read as a hang.

Just ask — what changed in the DeepSeek Responses API this month? — and it searches when it decides it needs to.

Vision (via Ollama)

DeepSeek V4 Flash is text-only, so image analysis is delegated to an Ollama vision model. Vision is disabled by default — enable it once and the choice persists.

/vision # interactive: lists installed models, pick one or disable
/vision qwen3.5:2b # enable a specific model directly
/vision off # disable (the default)
/vision status # show the current setting

/vision with no argument, opening a numbered picker of the vision models the daemon actually reports

  • Only models Ollama reports as vision-capable are listed (if none are, every model is listed so you can force a choice). Sizes are shown so you can stay inside your VRAM budget — on a 12 GB GPU anything up to ~8 GB is comfortable.
  • While vision is disabled the AnalyzeImage tool is not even offered to the orchestrator, so it never proposes a call it cannot fulfil.
  • Images are downscaled to 1024 px on the longest edge (needs pillow; skipped if not installed) and answers are cached by image hash + question, so re-asking the same question about the same image is a local SQLite lookup rather than another inference. A different question about the same image is a new inference — the question is part of the key.

Once enabled, just ask: analyze screenshot.png — what's the error?

Running the model on another machine

The daemon does not have to be local — the usual reason to move it is that the GPU is somewhere else.

/ollama host 192.168.1.50 # bare IP: :11434 is appended for you
/ollama host gpu-box:11434
/ollama host https://ollama.example.com # behind a TLS proxy, port left alone
/ollama host default # back to OLLAMA_HOST, then localhost

Addresses are forgiving: a missing scheme becomes http://, a missing port becomes :11434. The host is tested before it is saved, and if it does not answer you are told why — connection refused reads differently from a timeout — and asked whether to save it anyway.

Precedence, highest first: the vision_host setting (what /ollama host writes) → the OLLAMA_HOST environment variable → http://localhost:11434. /ollama always shows which one is in effect.

Three commands make a remote setup diagnosable rather than a guessing game:

CommandAnswers
/ollamaIs anything listening, what version, how far away (latency), how many models
/ollama modelsWhat that machine actually has — names, sizes, quantisation, vision capability
/ollama testA real inference round-trip: g023 sends a generated image and checks the answer. This proves the whole path — encoding, transport, and the model genuinely being image-capable — rather than just that the port is open.

The /ollama panel: which daemon vision is pointed at, whether it answers, its version and latency

Warning

For a remote daemon to accept connections it must be bound to the network (OLLAMA_HOST=0.0.0.0:11434 ollama serve) and the port must be open. Ollama has no authentication. Anyone who can reach that port can use the GPU and read whatever is sent to it — keep it on a trusted network, behind an SSH tunnel (ssh -L 11434:localhost:11434 gpu-box, then leave the host at default), or behind an authenticating reverse proxy.

Fetching web pages

Distinct from web search: that runs on DeepSeek's side and you never see the request. FetchUrl is g023 reaching out from your machine, to a URL you or the model named — so it is held to a different standard.

FetchUrl reads a page and hands back readable text rather than raw HTML, in keeping with the context budget. Two things make it different from a plain requests.get.

It asks first, every time. A fetch leaves your machine and touches someone else's server, so it is never on the allow list. When a cached copy exists the prompt is a three-way choice rather than yes/no:

Permission required to fetch https://example.com/pricing
engine: curl_cffi · requested mode: auto
Cached copy available — fetched 12m ago, HTTP 200, 48,210 chars
Use cached copy, fetch fresh, or deny? [c/f/d] (c):

Your answer overrides whatever the model asked for. The model can also request a mode itself — auto (cache if younger than max_age), fresh, or cache (never touch the network) — and cache mode is the cheap way to re-read a page at a larger max_chars without a second request.

It presents itself as an ordinary browser. Requests carry a real browser's headers in the order that browser sends them, negotiate HTTP/2, and keep cookies per domain in .g023/cookies.json so a second visit looks like a returning user rather than a stranger. Requests to the same host are spaced out instead of fired back-to-back. Nothing identifies g023.

The weak point is TLS: Python's handshake has a recognisable fingerprint, and a UA claiming Chrome over a Python TLS stack is a contradiction that fingerprint checks notice. Installing curl_cffi fixes it properly — it reproduces Chrome's actual handshake:

pip install curl_cffi # then: /fetch status
headersHTTP/2TLS (JA4)
curl_cffiChrome orderyest13d1516h2… — matches Chrome
httpx + h2Chrome orderyesgeneric Python
httpx aloneChrome ordernogeneric Python

/fetch status always tells you which of these you are on, and the JA4 column reports what curl_cffi impersonates rather than a measurement — point /fetch https://tls.peet.ws/api/all at it to see your own fingerprint on the wire.

What is not claimed: that this gets you past any particular bot defence. Those systems weigh behaviour, IP reputation and history alongside the fingerprint, and they change. A handshake that matches Chrome removes one obvious tell; it is not a guarantee about any site, and any site may block you anyway.

Note

There is no JavaScript engine. Pages that build their body client-side return the shell. Use a server-side check like tls.peet.ws/api/all to see what your requests actually look like on the wire.

Design principles

  1. Context is currency — keep raw files and search dumps out of the orchestrator unless they were asked for by line range.
  2. Subagents are the treasury — heavy I/O happens in isolated, minimal contexts, and the escalation path back to verbatim source stays open.
  3. Cache everything cacheable — file hashes, vision Q&A, fetched pages.
  4. Do it off-API where it is free — search the filesystem locally, see with a local GPU, and spend tokens only on reasoning.
  5. Thinking mode is a dial, not always-on max.
  6. Say what happened, not what was sent — and always say what it cost.
  7. Claim only what is checked. Numbers in this documentation are either asserted by a test, quoted from a published price sheet with attribution, or labelled as an estimate. Where something is not measured, it says so.

Tests

pip install pytest && python3 -m pytest tests/ -q

No plugins beyond pytest — async tests run through a small hook in tests/conftest.py, and every test points G023_PROJECT_ROOT at a temporary directory, so a run never reads your real cache and never touches the network.

FileWhat it holds to account
test_call_accounting.pyThe API-call table above, counted against a stub client
test_file_reader.pySymbol ranges are exact and in-bounds; truncation is declared; local AST facts outrank the model's
test_drift_signals.pyEach signal fires on the shape that matters and stays quiet on ordinary turns; hit-rate history survives restarts
test_history_integrity.pyTool-call pairing survives rollback and repair; output items are echoed back byte-identical
test_usage_accounting.pyCost arithmetic, both usage spellings, worst-case assumption when the split is unreported, command/handler contract

What these do not cover: anything requiring the real API, end-to-end session cost, or whether a summary was good enough for the question asked.

Extending

  • New slash command — add a Command(...) to COMMANDS in commands.py and a matching cmd_* method on CLI. Help text, tab completion, and "did you mean" pick it up automatically; check_handlers() fails at startup if the method is missing, so the two halves cannot drift apart.
  • New tool — add a schema in tools/schemas.py plus an executor or a subagent route. Give it an entry in ui.TOOL_STYLES and a case in ui.describe_call / describe_result so its trace reads like the others.
  • Vision backendsollama_client.py and subagents/vision.py; the router dispatches AnalyzeImage there.
  • Permissionspermission_default (allow | ask | block) sets the starting level for the tools that act: Bash, WriteFile, Agent, AnalyzeImage. Read-only tools stay allow, and FetchUrl still asks unless the default is block. Per-tool overrides live in tools/registry.py and /tools.

Environment overrides:

VariableEffect
G023_HOMEInstallation folder — where K.dat and config.json live
G023_PROJECT_ROOTThe project being worked on
OLLAMA_HOSTVision daemon, when vision_host is unset
G023_ASCII=1Force plain-ASCII output on terminals that mangle box drawing
G023_READFILE_RAW=1ReadFile returns raw content instead of delegating — the A/B baseline described in What delegation costs

License

MIT — built for the g023 workflow.

About

Pure-Python AI coding agent powered by DeepSeek V4 - Subagent-First architecture · Context is Currency · Terminal-native - Can connect Ollama models for Vision support - Uses the native web search built into DS v4 API and is built around the DeepSeek caching mechanism

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages