Goose and OpenHands: Autonomous Agents and Sandboxing
The tools in the previous three lessons wait for you. These two do not. By the end of this lesson you will have Goose pointed at your gateway with its autonomy capped deliberately, you will know how OpenHands is configured for a local model and what its container architecture implies, you will be able to estimate what an unattended run costs in tokens before you start it, and you will be able to read an agent transcript for the specific things that go wrong.
The course pins Goose 1.50.0 · verified 2026-09-08 and OpenHands 1.16.0 · verified 2026-09-08.
Autonomy is a token budget, not a feature
Section titled “Autonomy is a token budget, not a feature”The previous lessons treated turns as things you watch. An autonomous agent’s whole point is that you stop watching, and the arithmetic from the second lesson does not stop applying when you look away.
Goose’s GOOSE_MAX_TURNS has a documented default of 1000. Take the per-turn cost from the second
lesson’s table — new prompt tokens divided by your prompt rate, output tokens divided by your decode
rate, plus your test suite — and multiply by even a fraction of a thousand. On a local model, an
unattended run that is allowed to go that far is not an experiment; it is an afternoon of your
machine’s memory bandwidth, spent on something you did not read.
The two settings that turn autonomy into a decision rather than a default are the turn cap and the approval mode. Set both before the first run.
Apache-2.0, and it has moved twice recently in ways that break bookmarks. Its documentation now
lives at goose-docs.ai; the GitHub Pages address most links point at returns a “goose has moved”
notice. The project site states it is “now governed by the Agentic AI Foundation at the Linux
Foundation”.
Providers
Section titled “Providers”Goose reaches an OpenAI-compatible server through OPENAI_HOST, with OPENAI_BASE_PATH for the
awkward cases — the documentation explains it as “Set this when your endpoint serves the chat
completions API at a different path—most proxies expect v1/chat/completions, but some are mounted
at chat/completions (no v1)”. Ollama is reached through OLLAMA_HOST, and the documentation
notes that “if you don’t provide a host, we set it to localhost:11434”, and that Goose “prepend[s]
http:// if the scheme is not http or https”.
GOOSE_PROVIDER and GOOSE_MODEL select what runs, and the environment-variables page states the
precedence rule that saves an hour of confusion: “environment variables take precedence over
configuration files”.
RunnableAll tracks
# Purpose: the environment Goose reads to reach a local model - either the Part 9 gateway# through the OpenAI-compatible provider, or an Ollama install directly - plus the# two settings that decide how autonomous a session is allowed to be.# Platform: all (spark, strix, mac, nvidia)# Minimum memory: 16 GB for a 30B-class coder behind the alias# Assumes: `cp goose-local-env.txt goose.env`, edit, then `set -a; . ./goose.env; set +a`# before starting Goose. Goose's documentation states that environment variables# take precedence over configuration files, which is why this file exists rather# than a hand-written config.yaml: the variables below are documented by name on# the providers and environment-variables pages read on 2026-09-09, whereas the# exact shape of the on-disk configuration file is best produced by running# `goose configure` rather than typed from memory.
# ------------------------------------------------------------------ which model, and where# GOOSE_PROVIDER selects the provider; GOOSE_MODEL selects the model within it. Through the# Part 9 gateway the provider is the OpenAI-compatible one and the model is your alias.GOOSE_PROVIDER=openaiGOOSE_MODEL=local/coder
# The OpenAI-compatible provider's endpoint. OPENAI_HOST replaces the default host.# OPENAI_BASE_PATH exists because, as the providers page puts it, "most proxies expect# v1/chat/completions, but some are mounted at chat/completions (no v1)". Leave it unset# unless your server is one of the unusual ones.OPENAI_HOST=http://127.0.0.1:4000# OPENAI_BASE_PATH=v1/chat/completions
# The key is required by the provider even when the server behind it is yours. Generate a# virtual key on the gateway and paste it here; this file is a template and the value below# is a placeholder, not a credential.OPENAI_API_KEY=${GATEWAY_KEY}
# ------------------------------------------------------------------ the no-gateway route# Uncomment these two and comment out the four lines above to talk to Ollama directly. The# documentation notes that if no host is given, Goose uses localhost:11434, and that it# prepends http:// when the scheme is neither http nor https.# GOOSE_PROVIDER=ollama# OLLAMA_HOST=http://127.0.0.1:11434
# ------------------------------------------------------------------------- how autonomous# GOOSE_MODE decides whether the agent asks before acting. Documented values are auto,# approve, chat and smart_approve. Start at approve. Move to auto only inside the container# built in this part's sandbox lab.GOOSE_MODE=approve
# GOOSE_MAX_TURNS caps the loop. The documented default is 1000, which is a long way past# the point at which a small model has stopped making progress. Lower it while you are# learning what your model does, and raise it once you have watched a few transcripts.GOOSE_MAX_TURNS=40
# Sampling temperature for the session. Lower values make tool-call formatting more# consistent, which matters more for an agent than variety does.GOOSE_TEMPERATURE=0.2The providers page also carries the sentence that this whole part keeps returning to: “goose extensively uses tool calling, so models without it can only do chat completion.” If your model fails Part 24’s reliability test, Goose will start, connect, converse and do nothing.
Extensions
Section titled “Extensions”Extensions are how Goose gets its hands. The documentation describes them as “add-ons that provide a way to extend the functionality of goose by connecting with applications and tools you already use in your workflow”, built on MCP. The built-in set named on that page includes Developer, which is enabled by default and provides the shell and file tools, along with Computer Controller, Memory, Tutorial and others.
An MCP server from Part 24 is added either interactively — goose configure, then “Add Extension”,
then “Command-line Extension”, then the run command — or on the command line with
--with-extension "{command}" for a local one and --with-builtin "{extension_id}" for a built-in.
A remote server has its own flag, --with-streamable-http-extension "{url}".
Recipes
Section titled “Recipes”A recipe is a task written down so it can be run again, which is exactly what a comparison across
models needs. The documented top-level keys are version, title, description, instructions,
prompt, parameters, extensions, activities, response, retry, settings and
sub_recipes, with title and description required and at least one of instructions or prompt
mandatory.
RunnableAll tracks
# Purpose: a Goose recipe that runs one repeatable task - make a failing test suite pass# without editing the tests - with the extensions it needs declared, so the same# run can be repeated against different models and compared.# Platform: all (spark, strix, mac, nvidia)# Minimum memory: 16 GB for a 30B-class coder behind the alias# Assumes: `goose run --recipe goose-recipe.yaml --params project_dir=/work` with the# environment from goose-local-env.txt already sourced. Keys below are from the# Goose recipe reference read on 2026-09-09: version, title, description,# instructions, prompt, parameters, extensions, activities, response, retry,# settings and sub_recipes are the documented top-level keys, and either# instructions or prompt must be present.
version: 1.0.0title: Make the failing tests passdescription: >- Repair a small Python project until its test suite passes, without editing the tests. Written so that the same task can be run against several models and the transcripts compared side by side.
parameters: - key: project_dir input_type: string requirement: required description: Absolute path to the project directory inside the sandbox.
instructions: | You are working inside a container that can see one project directory and nothing else. Work only inside {{ project_dir }}.
Rules for this task: - Read task-readme.md first. It states what the project is supposed to do. - Run the tests before changing anything, so you know the starting state. - Do not edit task-tests.py. The tests define the requirement; changing them is failing the task, not passing it. - Change one thing at a time and re-run the tests after each change. - Stop when the suite passes, and say in one sentence what was wrong. - If you have not made progress after several attempts, stop and say what you tried and what you would need in order to continue. Do not keep repeating a failing edit.
prompt: | Run the test suite in {{ project_dir }}, read the failures, and repair the application code until every test passes.
extensions: - type: builtin name: developer timeout: 300
activities: - Run the test suite and report which tests fail - Explain the first failure in one sentence - Repair the code and re-run the suiteIt runs with goose run --recipe goose-recipe.yaml, and parameters are passed as
--params key=value. The documented goose run flags also include -i, --instructions <FILE>
(with - for standard input), -t, --text <TEXT>, --no-session to “Run goose commands without
creating or storing a session file”, --max-turns <NUMBER>, --with-extension, --with-builtin
and --debug.
The instructions in that recipe are worth reading as a pattern rather than as content. Three of the rules exist because of failure modes from the second lesson: do not edit the tests (a model that cannot pass a test often deletes it), change one thing at a time (loop discipline), and stop and report rather than repeating a failing edit (the runaway).
OpenHands
Section titled “OpenHands”MIT-licensed, and the tool in this part designed from the start to run unattended in a container.
Note that its documentation moved: docs.all-hands.dev redirects to docs.openhands.dev.
Configuring a local model
Section titled “Configuring a local model”The documented pattern is an openai/-prefixed model name plus a base URL, which is the same
LiteLLM-style routing convention Aider uses. The documentation’s own example writes the custom model
as openai/ followed by the identifier your server exposes, and shows base URLs for the usual local
ports: Ollama on 11434, vLLM and SGLang on 8000, LM Studio on 1234. The environment variables are
LLM_MODEL, LLM_BASE_URL and LLM_API_KEY, with a dummy key where the server needs none,
and a [llm] section in config.toml carrying retry settings such as num_retries,
retry_min_wait, retry_max_wait and retry_multiplier.
Two statements from those pages are the most useful sentences any tool in this part publishes about local models, because they are honest about the requirement rather than the possibility.
On context: “OpenHands requires a large context size to work properly. When using Ollama, set OLLAMA_CONTEXT_LENGTH to at least 22000. The default (4096) is way too small.”
On capability: “even with a large context window, some local models may struggle with reliable tool use”, followed by the diagnostic advice that saves the most time — “If OpenHands behaves like a plain chatbot, refuses to use tools or files, or has constant failed tool calls with a local model, the issue may be with the model itself rather than your setup.”
The LLM page is blunter still about model choice: “OpenHands can connect to any LLM supported by LiteLLM. However, it requires a powerful model to work.” Its local-model page recommends starting with a mixture-of-experts coder model built for agentic work and gives hardware guidance in the range of 24 GB of video memory for quantised variants, or 64 GB of unified memory. That lines up with this part’s own tier table, and it is the reason the lab’s memory floor is 16 GB rather than 8.
A headless run, which is what an unattended task wants, is documented as openhands --headless -t "Your task here", with -f for a task file and --json for machine-readable output.
The container runtime, and the thing to notice in it
Section titled “The container runtime, and the thing to notice in it”OpenHands runs the agent’s work inside a container it manages. The published local-setup command looks like this:
Fragment — not complete on its own
docker run -it --rm --pull=always \ -e LOG_ALL_EVENTS=true \ -v /var/run/docker.sock:/var/run/docker.sock \ -v ~/.openhands:/.openhands \ -p 3000:3000 \ --add-host host.docker.internal:host-gateway \ --name openhands-app \ <the image and tag from the documentation>It is a fragment because the image name and tag are version-specific and belong in the documentation rather than in a course page that will age; read them from the local-setup page for the release you are installing.
The line to look at is -v /var/run/docker.sock:/var/run/docker.sock. That is the Docker socket,
mounted into the container, and it is how OpenHands starts the further containers its work runs in.
It is architecturally necessary for that design and it has a consequence that Docker’s own
documentation states without hedging.
Reading a transcript
Section titled “Reading a transcript”An unattended run leaves a transcript, and reading one properly is a skill with a short checklist.
Goose’s LOG_ALL_EVENTS-style verbosity, OpenHands’ event log and the session files each tool keeps
are all the same artefact for this purpose.
What to look for, in order, when a run went wrong
- The first malformed tool callNot the last. The first one tells you when the model lost the format, and everything after it is consequence rather than cause.
- The first repeated actionThe same edit twice, or the same command with the same failure. This is where loop discipline went, and it is usually within two turns of a compaction.
- The compaction or truncation pointWhere the conversation was summarised or the window overflowed. Constraints given at the top of the session are commonly lost here.
- The first action outside the taskA read outside the project, a network call, a file written somewhere unexpected. This is the sandbox question, and the next lab answers it.
- The claim of successDid the tests actually run, and did the transcript contain their output? A model declaring victory without evidence is the most common false positive in agent work.
- The token totalCompare against your estimate from the second lesson. A run that cost five times the estimate spent it somewhere, and the transcript says where.
Define an episode boundary for autonomous work
Section titled “Define an episode boundary for autonomous work”An autonomous coding run needs a starting repository state, task description, permitted tools, resource budget and independent success test. Without those boundaries, a long transcript is difficult to distinguish from productive progress. Store final files and logs outside the disposable execution environment before cleanup.
Test the failure paths: an unavailable model endpoint, a denied command and a failing test. Check whether the agent stops, retries within limits or continues with an incorrect assumption. Keep retries and operator interventions in the report rather than counting only the final successful attempt.
Review what the execution container can access, including mounts, network routes and credentials. A container with the host’s administrative socket or broad home-directory mounts can have much more authority than its name suggests. Run the sandbox lab’s boundary probes before using real projects. Choose autonomy according to the effects the environment can safely permit and the model’s measured ability to finish tasks, not according to how long it can continue generating actions.
Autonomy is a budget: turns times per-turn cost, in tokens, in memory and in your machine’s time.
Goose is Apache-2.0, now under the Agentic AI Foundation with documentation at a new address,
reaches your gateway through OPENAI_HOST and Ollama through OLLAMA_HOST, caps its loop with
GOOSE_MAX_TURNS whose default of 1000 is not a suggestion you should accept, and gates actions
with GOOSE_MODE. Its extensions are MCP servers and its recipes make a task repeatable across
models, which is what a fair comparison needs. OpenHands is MIT, needs a large context and a capable
model, and says so plainly in its own documentation; it runs work in containers it manages, which
requires the Docker socket, which Docker’s own security documentation explains is equivalent to root
on the host. That is not a reason to avoid OpenHands. It is a reason to know what your sandbox is
actually bounding, which is the next lab.
Check your understanding
Sources for this lesson
10 verified · checked 2026-09-09
- 01Goose — providers§ OpenAI-compatible; Ollama; tool-calling requirementgoose-docs.ai/docs/getting-started/providers2026-09-09
- 02Goose — environment variables§ GOOSE_PROVIDER; GOOSE_MODE; GOOSE_MAX_TURNSgoose-docs.ai/docs/guides/environment-variables2026-09-09
- 03Goose — using extensions§ Built-in extensions; adding an MCP servergoose-docs.ai/docs/getting-started/using-extensions2026-09-09
- 04Goose — recipe reference§ Top-level keys; extensions blockgoose-docs.ai/docs/guides/recipes/recipe-reference2026-09-09
- 05Goose — CLI commands§ goose run; goose session; flagsgoose-docs.ai/docs/guides/goose-cli-commands2026-09-09
- 06OpenHands — local LLMs§ Model prefix; context length; tool-use reliabilitydocs.openhands.dev/usage/llms/local-llms2026-09-09
- 07OpenHands — LLM configuration§ Model requirementsdocs.openhands.dev/usage/llms/llms2026-09-09
- 08OpenHands repository§ Licence; quickstart container commandgithub.com/All-Hands-AI/OpenHands2026-09-09
- 09Docker — post-installation steps for Linux§ docker group privilegesdocs.docker.com/engine/install/linux-postinstall2026-09-09
- 10Docker — security§ Docker daemon attack surfacedocs.docker.com/engine/security2026-09-09
Every technical claim on this page was checked against the official documentation of the tool, vendor or model publisher on the date shown, at the version pinned for the course. Where the course disagrees with folklore, the source is how you can tell which one to trust.