Skip to content

gg ​

CI

English | 简体中文

gg is a terminal AI coding agent written in Go. Point it at a repository, and it streams an OpenAI-compatible model that can read, search, and edit the project from your shell.

bash
go install github.com/hszjj221/gg/cmd/gg@latest

Requires Go 1.26 or newer (go 1.26 in go.mod). No GitHub release or tag is published, so @latest installs the newest commit on the default branch. From a checkout: go run ./cmd/gg --help.

Demo ​

Placeholder — GIF not recorded. Recording a typical session needs a live model API key, which is not available in this environment. The re-recordable script is docs/demo.tape (Charmbracelet VHS). From the repo root, with gg on PATH and OPENAI_API_KEY set:

bash
vhs docs/demo.tape

That writes docs/demo.gif. The tape starts gg, asks it to explain cmd/gg/main.go, then asks it to run go test and approve the bash prompt.

Architecture ​

The CLI runs the agent in-process. Web and Electron are separate clients of the same application runtime: they share one React UI and talk to the ggd daemon over HTTP. ggd speaks newline-delimited JSON-RPC on stdio unless --http is set; Electron starts it with --http.

internal/agent is the tool loop. internal/provider/openai is the only provider in v1. internal/tools is the built-in tool set (read, list, grep, bash, edit, write, plus optional capabilities). internal/app owns sessions, turns, approvals, and run lifecycle. internal/session stores an append-only JSONL tree. Dependency direction is clients and transports to internal/app, and internal/app to the agent and the session store.

What was hard ​

Security model ​

Tools run as the user. A nil approver inside the agent runner means allow, so every caller that can run tools passes an approver on purpose. The CLI policy lives in internal/cliapp.

ModeIn a terminalNo terminalDangerous bash
auto (default)promptdeny, and say how to pass --yolo or --approval on-requestprompt in a terminal, otherwise deny
on-requestpromptthe process exits: this mode requires a terminalprompt
yolo (--yolo or --approval yolo)allow with no promptallow with no promptprompt in a terminal, otherwise deny
neverallow with no promptallow with no promptdeny, because this mode never asks

"In a terminal" means stdin and stderr are both terminals. tools.ClassifyCommand labels each bash command before that policy runs. Clearly read-only commands (ls, cat, grep, git status / diff / log, and a short allowlist) skip the prompt in every mode. Dangerous commands — rm -rf of / or a home directory, a download piped into a shell, sudo, disk and format tools, writes that resolve outside the working directory — still need an interactive approval. --yolo does not waive them, and a scheduled job's --allow-all does not either: nothing is there to answer, so those commands are denied.

--sandbox is off by default. When it is set, bash tries bubblewrap on Linux or sandbox-exec on macOS, with the working directory writable and the rest of the filesystem read-only. If the helper is missing or cannot start, the command still runs and the output says the sandbox was unavailable. The sandbox does not confine the network.

Full rules, the classifier's gaps, and the sandbox limits: security model.

Streaming tool calls ​

internal/provider/openai always requests stream: true. Tool calls arrive as indexed fragments (id, name, argument bytes) and are assembled when the stream ends. A call with an empty id, an empty name, or arguments that are not a JSON object is an error. The runner then drops ToolCalls on that message so a partial call is never stored as something to execute. Network errors, HTTP 429, and 5xx responses retry before a stream is accepted (two retries, exponential backoff with jitter). An interrupted stream, a length limit, or incomplete tool arguments is returned as an error. Text that already streamed is kept.

Interruption and resume ​

cmd/gg cancels on Ctrl+C. The runner records the partial assistant text, clears unfinished tool calls, and will not start a tool whose context is already canceled. Sessions are append-only JSONL. Opening a file whose last line is torn truncates back to the last valid bytes. A saved tool call with no result is left unreplayed, because the side effect may already have happened. Resume writes a tool result that the execution was interrupted and the result is unavailable, and the coding instructions tell the model to inspect the workspace before retrying. A second process that advanced the same file makes the stale writer fail with a conflict instead of interleaving two branches.

Steering ​

The runner and the UI share agent.MessageQueue. Steering text is drained into the same run at model boundaries: before each provider request, and again when a reply finished with no further tool calls. Messages marked as follow-ups stay on that queue and are not injected into the current run. Web and Electron send both through JSON-RPC run.steer. Resolve and approval still happen in call order before any tool in that batch executes, so approval prompts stay in call order.

Concurrency and lifecycle ​

One session has one active run. Different sessions may run at the same time. ggd admits 16 turns across workspaces by default and bounds a turn at 30 minutes (--max-active-runs, --run-timeout). Inside a turn, only tools that opt in may overlap — today read and grep — and at most four at once. Every other tool is a barrier before and after. A panicking tool becomes an error result. A panicking turn is recorded as failed so the daemon keeps serving. Shutdown cancels active turns, waits for workers, and closes browser and MCP subprocesses before it releases the daemon instance lock.

Run ​

bash
export OPENAI_API_KEY=sk-your-key
gg -p "List the files in this project"

gg with no prompt starts a line-oriented session (Ctrl+D to exit). Replies stream as they arrive. In a terminal, bash, edit, and write ask before they run.

Sessions, provider config, AGENTS.md, skills, memory, and the context budget are in the usage reference.

Extensions ​

These sit behind the terminal agent. Each one is real code in this repo; the linked page is the reference.

Web and ElectronShared React UI. Electron launches ggd as a loopback sidecar.Usage
SchedulerCron and one-shot jobs while ggd is running.Usage
ConnectorsGoogle OAuth for Gmail and Calendar.Connectors
MediaImage generation, TTS, and STT on OpenAI-compatible endpoints.Media
BrowserHeadless Chromium over CDP: navigate, read, screenshot.Browser
Telegramggd answers allowlisted chats, including voice.Telegram
Knowledge baseLocal RAG index and a kb_search tool.RAG
Artifacts and libraryVersioned documents and a personal file library.Usage
MCPExternal tools declared in ~/.gg/mcp.json.Architecture
Computer toolsOS info, processes, clipboard, and notifications on Linux and macOS.Usage
DiagnosticsJSON logs on stderr and how to follow a run.Diagnostics

Package boundaries and the JSON-RPC surface: architecture.

Security ​

Run gg only in workspaces where you are willing to grant the model those tools. ~/.gg/config.json may contain API keys. When memory is enabled, profile and memory files are sent to the selected provider, and memory_add writes without an approval prompt.

Report vulnerabilities privately. See SECURITY.md.

License ​

MIT. See LICENSE. Development setup is in CONTRIBUTING.md.

Generated from README and docs/ in the gg repository at build time.