Skip to content
Level 5 · Agentic EngineerLessonPart 25 · page 7 of 1125 min
25Minutes
2Tools
10Sources
Tools used on this page2

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.

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”.

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

goose-local-env.txt
# 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=openai
GOOSE_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.2

Download goose-local-env.txt52 lines

The 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 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}".

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

goose-recipe.yaml
# 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.0
title: Make the failing tests pass
description: >-
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 suite

Download goose-recipe.yaml52 lines

It 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).

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.

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

Terminal window
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.

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
Read in this order rather than from the top. The last few turns of a failed run are almost always noise; the interesting event is earlier and quieter.

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

Question 1. Goose's GOOSE_MAX_TURNS defaults to 1000. Why does this course recommend lowering it before your first local run?
Show the answer and why

Answer: Because each turn costs prompt tokens, output tokens and machine time, and a thousand turns of a local model is a large unattended expenditure of the resources you measured in Part 5

Multiply the second lesson's per-turn arithmetic by the cap and you have the worst case you are authorising. Lowering it while you learn what your model does converts an open-ended run into a bounded experiment.

Question 2. Why is mounting /var/run/docker.sock into a container a notable decision rather than a detail?
Show the answer and why

Answer: A process that can reach the socket can start containers that mount the host filesystem without restriction, which Docker's own documentation describes as equivalent to root on the host

Docker's security page gives the example directly: you can start a container whose /host is the host's root and alter the host filesystem without restriction. The socket is therefore an escape route from the container that holds it, which is exactly the fault the challenge in this part has you find.

Question 3. An OpenHands session with a local model behaves like a chatbot and never touches files. What does the documentation suggest you conclude?
Show the answer and why

Answer: That the issue may be with the model itself rather than the setup, since reliable tool use is a model property that a large context does not guarantee

The local-LLM page says exactly this: constant failed tool calls or a refusal to use tools points at the model. It also warns that even a large context window does not guarantee reliable tool use, which is why the reliability test comes before the configuration.

Question 4. When reading the transcript of a failed autonomous run, why start from the first malformed tool call rather than the end?
Show the answer and why

Answer: Because everything after the first format failure is consequence, so the last turns are noise and the causal event is earlier

Agents degrade rather than crash. Once the format goes, the harness rejects calls, the model retries, the context fills with failures and the tail of the transcript describes a symptom. The interesting turn is the quiet one where the format first slipped.

Question 5. Which of these are documented properties of Goose relevant to a local setup? Select all that apply.
Show the answer and why

Answer: Environment variables take precedence over configuration files, OPENAI_BASE_PATH exists for servers that mount the chat endpoint without a /v1 prefix, Models without tool calling can only do chat completion

The first explains why an environment file is a reliable way to configure it; the second is the fix for a proxy with an unusual path; the third is the requirement that decides whether your model is usable at all. Recipes are plain YAML with documented keys.

Sources for this lesson

10 verified · checked 2026-09-09

  1. 01Goose — providers§ OpenAI-compatible; Ollama; tool-calling requirementgoose-docs.ai/docs/getting-started/providers2026-09-09
  2. 02Goose — environment variables§ GOOSE_PROVIDER; GOOSE_MODE; GOOSE_MAX_TURNSgoose-docs.ai/docs/guides/environment-variables2026-09-09
  3. 03Goose — using extensions§ Built-in extensions; adding an MCP servergoose-docs.ai/docs/getting-started/using-extensions2026-09-09
  4. 04Goose — recipe reference§ Top-level keys; extensions blockgoose-docs.ai/docs/guides/recipes/recipe-reference2026-09-09
  5. 05Goose — CLI commands§ goose run; goose session; flagsgoose-docs.ai/docs/guides/goose-cli-commands2026-09-09
  6. 06OpenHands — local LLMs§ Model prefix; context length; tool-use reliabilitydocs.openhands.dev/usage/llms/local-llms2026-09-09
  7. 07OpenHands — LLM configuration§ Model requirementsdocs.openhands.dev/usage/llms/llms2026-09-09
  8. 08OpenHands repository§ Licence; quickstart container commandgithub.com/All-Hands-AI/OpenHands2026-09-09
  9. 09Docker — post-installation steps for Linux§ docker group privilegesdocs.docker.com/engine/install/linux-postinstall2026-09-09
  10. 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.