Coding agent
A coding harness reads a repository, plans a change, edits files, runs commands and checks its own work. It leans on three parts: tools (file and shell access with permission gates), a workspace (the checkout, or a sandbox when the code is not yours to trust) and context (repository guides, a task list, and compaction for long sessions). Everything except the file and shell tools themselves is already in Glove.
- read_file, write_file, edit_file, grep, bash (or reuse examples/coding-agent/tools.ts)
- The system prompt: workflow rules for your codebase
- The surface: a terminal or a web client that renders permission and plan slots
How it fits together
A request such as “make the retry test pass” arrives from the surface and is saved to the store. Before the first model call, Glove assembles context: the system prompt, the saved history, and any runtime context you registered. The model reads the skill listing that useReadSkill folded in and pulls the testing guide it needs. It calls glove_update_tasks to lay out a plan, then explores with read-only tools.
When it calls edit_file or bash, the executor checks the permission gate. The store has no decision for that tool and input yet, so the executor pushes a permission_request slot onto the display stack and the tool waits. Your surface renders it, the person approves, and display.resolve(slotId, true) lets the call through. The decision is stored, so the same input does not ask again. Results are saved, the loop continues, and subscribers stream text and tool events to the surface the whole time. If the session grows past the compaction limit, Glove summarises it and the model carries on from the summary; the store keeps the full history for your UI.
Skeleton
This is the core of examples/coding-agent/server.ts, reduced to the parts that matter. The tool implementations are yours; the example ships a complete set.
import {
Displaymanager,
Glove,
MemoryStore,
createAdapter,
useReadSkill,
type SubscriberAdapter,
} from "glove-core";
import { loadContentSkillsFromFs } from "glove-core/content-skills-fs";
import { z } from "zod";
// Your code: read_file, write_file, edit_file, grep, bash, git_* as glove-core Tools.
// examples/coding-agent/tools.ts has a complete set to start from.
import { codingTools, readOnlyTools } from "./tools.js";
const DESTRUCTIVE = new Set(["write_file", "edit_file", "bash"]);
export async function createCodingAgent(cwd: string, surface: SubscriberAdapter) {
const display = new Displaymanager();
const store = new MemoryStore(`coding:${cwd}`); // tasks + permissions + subagent stores built in
const agent = new Glove({
store,
model: createAdapter({ provider: "anthropic", model: "claude-sonnet-5-5", stream: true }),
displayManager: display,
systemPrompt: `You are a coding agent working in ${cwd}.
Explore before you change anything. Track multi-step work with glove_update_tasks.
Run the tests before you say you are done.`,
compaction_config: {
max_turns: 50,
compaction_instructions:
"Summarise the work. Keep: files changed, task state, failing tests, decisions made.",
},
});
for (const tool of codingTools) {
agent.fold<any>({
name: tool.name,
description: tool.description,
inputSchema: tool.input_schema,
// Ask before anything that writes or executes. A function form can
// exempt read-only shell commands: (input) => !isReadOnly(input.command)
requiresPermission: DESTRUCTIVE.has(tool.name),
do: (input) => tool.run(input),
});
}
// A plan the person approves before the agent edits anything.
agent.fold({
name: "propose_plan",
description: "Show a step-by-step plan and wait for the user to approve or change it.",
inputSchema: z.object({ title: z.string(), steps: z.array(z.string()) }),
async do(input, display) {
const decision = await display.pushAndWait({ renderer: "plan_approval", input });
return { status: "success", data: decision };
},
});
// A read-only explorer the main agent can hand questions to.
agent.defineSubAgent({
name: "explorer",
description: "Answer a question about the codebase by reading files. Never edits.",
factory: async ({ parentStore, parentControls }) => {
const child = new Glove({
store: (await parentStore.createSubAgentStore?.("explorer", false)) ?? new MemoryStore("explorer"),
model: parentControls.glove.model,
displayManager: parentControls.displayManager,
systemPrompt: "Find the answer in the code. Quote paths and line numbers.",
compaction_config: { compaction_instructions: "Keep the findings." },
});
for (const tool of readOnlyTools) {
child.fold<any>({ name: tool.name, description: tool.description, inputSchema: tool.input_schema, do: (input) => tool.run(input) });
}
return child.build();
},
});
agent.addSubscriber(surface); // text_delta, tool_use, tool_use_result, compaction, tokens
const built = agent.build();
// Repository guides (.claude/skills/<name>/SKILL.md) as skills the agent reads on demand.
useReadSkill(built, await loadContentSkillsFromFs(`${cwd}/.claude/skills`));
return { agent: built, display };
}The surface answers the slots the tools push. In a terminal it can be this small:
const { agent, display } = await createCodingAgent(process.cwd(), terminalSubscriber);
const answered = new Set<string>();
display.subscribe(async (stack) => {
for (const slot of stack) {
if (answered.has(slot.id)) continue;
answered.add(slot.id);
// Don't await inside the listener: pushAndWait registers its resolver only
// after every listener returns. Ask on the next tick, then resolve.
setImmediate(async () => {
if (slot.renderer === "permission_request") {
const { toolName, toolInput } = slot.input as { toolName: string; toolInput: unknown };
display.resolve(slot.id, await confirm(`Allow ${toolName}? ${JSON.stringify(toolInput)}`)); // your prompt
}
if (slot.renderer === "plan_approval") {
display.resolve(slot.id, await reviewPlan(slot.input)); // your prompt
}
});
}
});
const controller = new AbortController();
process.on("SIGINT", () => controller.abort()); // Ctrl-C stops the loop; unAbortable tools finish first
await agent.processRequest("Make the retry test pass", controller.signal);When the code is not yours: a sandbox
Host tools are fine for your own checkout on your own machine. For anyone else's code, or a hosted agent, run commands in a container instead. mountSandbox from glove-execution adds execute_sandbox: the model writes one script that creates a sandbox, writes files, runs commands and reads the output, and the scope tracks which sandboxes this run owns. In Foundry, the managed Station can host the container provider and the agent mounts it in configure, exactly as examples/foundry-operator does:
import { defineAgent } from "glove-foundry";
import { mountSandbox } from "glove-execution";
import { stationSandbox } from "glove-execution/station";
import { StationClient } from "station-client";
export default defineAgent({
description: "Writes and runs code in a persistent container",
// model, systemPrompt, store …
async configure(agent, context) {
// Your code: the connection the application saved from stationDaemon's onReady,
// and the sandbox ids this instance was granted on earlier runs.
const connection = await loadStationConnection();
const adapter = await stationSandbox({
client: new StationClient({ url: connection.url, token: connection.token }),
stationId: connection.stationId,
sandboxIds: await loadSandboxGrants(context.agentId),
cleanup: "retain", // keep files and services between runs
});
let close = () => adapter.close();
context.onCleanup(() => close()); // register before anything else can fail
const sandbox = mountSandbox(agent, { adapter });
close = () => sandbox.close();
},
});To let the agent edit a real checkout but review the change before it lands, glove-vfs's hostDirectory(dir) keeps every write in memory until you call commit().
What not to build
- A confirm-before-write wrapper in each tool. Permission gates (
requiresPermission) ask through the display stack and remember the decision per tool and input. - A todo tool. Any store with tasks gets
glove_update_tasks, and your UI can watch it throughtool_use_resultevents. - Pasting every contributing guide into the prompt. Content skills list the guides and let the model read the one it needs, by section.
- History trimming. Compaction summarises the session at the limit and keeps tool calls paired with their results.
- A second agent you call by hand for exploration. Subagents are a tool the model routes to, with their own store and tools, and their events bracketed in the parent's stream.
- docker exec wrappers. mountSandbox gives the model a script surface over sandboxes with ownership, bounded output and cleanup.
Deploy on Kuiper
A local coding harness needs no deployment. A hosted one, where people send it tasks and it works in its own containers, runs as a Foundry app:
- Put the agent in
agents/coder/agent.tsand host the container provider on the managed Station withstationDaemoninfoundry.application.ts(see Browser & sandbox agent). - Commit a
kuiper.json(start fromkuiper manifest init --foundry) and add"capabilities": { "sandbox": { "maxConcurrent": 2 } }. The unit then gets a Docker socket that Kuiper serves and polices; every container runs under gVisor within the unit's limits. Kuiper's sandbox images, such askuiper/coding, run by name. - Keep Foundry's data, transcripts and sandbox grants under
/data, the unit's persistent disk. - Point the model at the gateway so provider keys never reach the unit:
kuiper env set -u coder 'MODELS_API_KEY=${{models.API_KEY}}' 'ANTHROPIC_BASE_URL=${{models.ANTHROPIC_BASE_URL}}'and pass both tocreateAdapter. kuiper deploy --source . -u coder --wait, then open the unit's Inspect card to watch runs.
Sandboxes can also come from Kuiper's own sandbox API instead of the unit's Docker socket; see Kuiper sandboxes. The full walkthrough is Deploy on Kuiper.
Go further
- examples/coding-agent: the full harness with a WebSocket server, a React client, plan approval and permission prompts. The walkthrough explains every file.
- Build the parts one at a time: a loop and a tool, show your work, control what it sees, give it a workspace.
- Reference: Server-side agents, Hooks, skills & subagents, Browsers & sandboxes, Code execution.