CS357: Foundations of Artificial Intelligence - Overview (100 Points)
Contents
Purpose, Task, and Criteria
Purpose: To get your local AI stack and your coding agent working before the labs depend on them, capture a baseline snapshot of your thinking about AI that you will revisit at the end of the semester, and launch your team.
Task: Install and verify a working local AI environment and coding agent, and write a short baseline reflection on AI agency and trust.
Criteria: I grade this on a complete setup-verification transcript and a specific, personal reflection in equal measure. Please read the rubric below for the details.
Assignment Goals
The goals of this assignment are:
- To install and verify a working local AI environment including Ollama, a pulled model, a Python API call, and a coding agent answering from that same local model
- To demonstrate baseline command-line, git, and Python-environment fluency by navigating a shell, cloning and committing to a repository, and creating a reproducible environment with uv
- To articulate personal baseline beliefs about AI agency, trust, and delegation with specific examples
Background Reading and References
Please refer to the following readings and examples offering templates to help get you started:
- Welcome Activity
- Setup (Route A): the Your AI Workbench activity, which we build together in class - Host Ollama, the Course Container, Git, GitHub, and your first coding agent; its Step 0 is the shell in ten minutes and the read-before-you-run habit every later lab assumes
- The Shell, in Full: pipes, redirection, background jobs, and PATH, if the shell is new to you
The Assignment
In this warmup you’ll install your local AI stack and your coding agent, and write a short baseline reflection on your experiences with AI. I have kept the stakes low here on purpose. It exists to make sure your tools work before the labs depend on them, and to capture a snapshot of your thinking that you’ll come back to at the end of the semester.
There are no wrong answers in the reflection. This is a starting point, and I am not evaluating what you know.
Two administrative notes. Your team charter is not part of this assignment; it is handed out separately once teams are announced (see the Project Thread). And see the course schedule for this onboarding assignment’s due date; it is assessed within the Class Activities and Participation category.
Before You Start
This is the first thing you install for this course. I have put it early on purpose, so that a broken setup costs you this assignment rather than a lab.
Pace yourself: most of this is downloading. The model pull alone is about 2 GB, and the container image is larger. Start the downloads on good wifi and write the reflection while they run. Do not leave this for the night before; the downloads will not go any faster because you are in a hurry, and I can’t help you at 11 PM.
| You need | Why |
|---|---|
| A laptop you can install software on | Ollama, and on Route A Docker Desktop, are installed on your own machine |
| A GitHub account | Part 1.5 pushes a repository there, and Route A keeps your cs357-work repository there all semester |
| About 10 GB of free disk | The model is about 2 GB and the course container image is larger |
If any of those is a problem, say so this week rather than in week four. There is a lab-machine route, and it takes some scheduling.
Taking Route A? Everything you need is on this page. The Optional Route A Setup section in Part 1 installs Docker and builds the course container, with direct links to the container files, and every later step says what Route A lets you skip. The Your AI Workbench class session walks the same steps with you.
How to use this page. First, pick your route in Which route should I take? at the top of Part 1. Then follow your route’s path below, top to bottom. Every step opens with an at a glance box that says where you type, what each route does there, and what you paste, and every step ends with a Next line that tells you where to go. If you only read the boxes and the Next lines, you will still land in the right place.
| Route A path (recommended) | Route B path |
|---|---|
| Optional Route A Setup, A1 through A6 | Skip the optional section |
| Steps 1 and 2, on your host | Steps 1 and 2 |
| Step 3 on your host, plus its Route A bridge check from inside the container | Step 3 |
| Step 4 inside the container; nothing to install | Step 4, after installing Python, pip, and requests |
| Step 5 from 5b; opencode is already installed | Step 5, after installing opencode |
Part 1.5, Steps 1 through 3; gh is already in the image, and Step 2 pushes from the container with your A6 credential |
Part 1.5, Steps 1 through 3 |
| Part 2, the reflection | Part 2, the reflection |
The setup map
This assignment is nine stages plus two optional ones. Each stage ends with one command whose output you paste into your submission, so you can always tell whether a stage is done. Work down the table in order, and use the last column to find the steps.
| Stage | What you do | The command that proves it | What you paste | Where the steps are |
|---|---|---|---|---|
| 0 | Open a terminal, learn to move around and save a file, tell your host prompt from a container prompt, note your operating system, and, on Route B, install Python and pip | pwd, and on Route B python3 --version |
Your operating system name and version, your route (A or B), and on Route B the Python and pip version lines | Part 1, Opening a terminal, Where am I typing?, and Installing Python and pip |
| A | Optional, Route A only: install Docker, create cs357-work, add the container files, build, enter, and verify |
docker compose run --rm cs357, then the checks in A5 |
The container prompt and the A5 output | Part 1, Optional Route A Setup |
| 1 | Install Ollama and pull a model | ollama list |
The output of ollama --version and ollama list |
Part 1, Step 1 |
| 2 | Chat with the model once | ollama run llama3.2 "..." |
The model’s reply | Part 1, Step 2 |
| 3 | Confirm the REST API answers | curl http://localhost:11434/api/tags |
The JSON | Part 1, Step 3 |
| 4 | Call the model from Python | python3 ollama_check.py |
The printed JSON, including a "content" field |
Part 1, Step 4 |
| W | Optional, either route: install OpenWebUI, the chat interface over Ollama | Browse to http://localhost:3000 (Docker) or http://localhost:8080 (pip) |
Nothing required; a screenshot of the model dropdown is welcome | Part 1, Optional: Install OpenWebUI |
| 5 | Confirm the coding agent talks to that model | opencode --version |
The version string and one answered prompt | Part 1, Step 5 |
| 6 | Navigate a shell and search a file | grep -n "localhost" notes.txt |
The commands and their output | Part 1.5, Step 1 |
| 7 | Install and authenticate gh, then commit and push |
gh auth status, then git log --oneline |
The gh auth status block and the log |
Part 1.5, Step 2 |
| 8 | Create a reproducible Python environment | uv run python -c "import requests; print(requests.__version__)" |
The printed version | Part 1.5, Step 3 |
| 9 | Write the baseline reflection | none | Four labeled sections | Part 2 |
Do the stages in this order. Start Stage 1 first, because the download runs in the background. On Route A, start the Stage A image build at the same time; both are downloads. While they download, write the reflection (Stage 9); it needs no tools. Then finish Stages 2 through 5, and do Stages 6 through 8 last, since they use what you just installed.
If a stage fails, document it and move on. A documented failure earns full credit for that stage: quote the error verbatim, state your hypothesis about the cause, and say what you tried. “It worked eventually” earns nothing. Work down the Troubleshooting table at the end of this page before you post in the course channel.
You’ve succeeded when the four boxes in the Part 1A checklist and the three in the Part 1B checklist are checked, all four in the Part 1.5 checklist are checked, and your reflection has four labeled sections. On Route A, the Route A checklist is checked as well.
What a Strong Submission Looks Like
A strong submission has these qualities:
- The transcript is complete and honest. It shows the actual terminal output (version numbers, model names, the API response), copied faithfully. If something broke, quote the error verbatim and tell me what you tried. I won’t give credit for a fabricated or paraphrased transcript.
- The reflection is personal and specific. It names a real AI tool you used, describes a real moment of surprise or confusion, and takes an actual position on agency and trust. I am not looking for a dictionary definition or a summary of the syllabus. A strong reflection reads like a journal entry from someone thinking carefully.
A weak submission has a transcript that says “it worked” without showing output and a reflection that restates prompts without answering them.
Part 1: Tool Setup
Part 1 has two halves, and you must do both:
| What you stand up | Steps | What the transcript shows | |
|---|---|---|---|
| Part 1A | The local model stack: Ollama, a pulled model, the REST API, and a Python call against it | Steps 1-4 | Four pieces of terminal output |
| Part 1B | The coding agent: opencode, talking to that same local model | Step 5 | A version string and one answered prompt |
Part 1A must work before Part 1B can: the coding agent in Part 1B is pointed at the model you pull in Part 1A, so do them in order. Before either half comes a decision (which route) and, on Route A, an optional setup section that builds the course container.
Which route should I take?
A route decides where Steps 1 through 5 run. The commands are the same on both routes, and neither route is the “real” one.
| Route A (recommended): host Ollama plus the course container | Route B: native install | |
|---|---|---|
| Who it is for | Anyone whose laptop can run Docker Desktop | Anyone whose laptop cannot run Docker, or anyone using a lab machine |
| Where Ollama runs | On your host, natively | On your host, natively |
| Where Steps 1-3 run | Your host terminal | Your host terminal |
| Where Steps 4-5 run | Inside the course container | Your host terminal |
| What you install | Ollama, Docker Desktop, and the course container, by following Optional Route A Setup below (the same steps the Development Environment activity walks through in class) | Ollama, Python 3 with pip (see Installing Python and pip below), the Python requests library, and opencode from opencode.ai |
Deciding your route. You do not have to decide yet, but decide early:
- Steps 1 through 3 are identical on both routes, so start Step 1’s download now.
- If Docker Desktop installs and
docker run hello-worldsucceeds (that is A1, the first thing the optional section asks), take Route A: do the Optional Route A Setup section, start its image build while the Step 1 model pull runs, and then Steps 3, 4, and 5 and Part 1.5 Step 2 each say what Route A lets you skip.- If Docker will not run on your machine, take Route B, skip the optional section, and say so in your transcript.
I recommend Route A because every later lab assumes it and it is what we build together in the Your AI Workbench session. Route B is complete and supported too. On Route A, the A5 checks and the Step 3 bridge check belong in your transcript as well.
Before Step 1: Orientation (read once, come back as needed)
Four short references before the steps begin. Every at a glance box below says “your host terminal” or “the container prompt”; the first two sections are where those words are defined, and every step that says “open a terminal,” “cd,” or “save it” means the first of them. The third installs Python and pip, which Route B needs before Step 4. The fourth makes a GitHub token and puts it somewhere your tools can find it; A6 is the first place you need it, and the MCP session and several labs send you back here.
Opening a terminal, moving around, and saving a file
Every step on this page happens at a terminal, and several ask you to save a file. Here is how, on every system this course supports. Come back to this section whenever a step says “open a terminal,” “cd,” or “save it.”
1. Open a terminal.
| System | How to open it | What the prompt looks like |
|---|---|---|
| macOS | Press Cmd+Space, type Terminal, press Enter (Terminal also lives in Applications > Utilities) |
you@laptop ~ % |
| Windows, PowerShell | Open the Start menu, type PowerShell, press Enter (not “Command Prompt”). On Windows 11, right-clicking the Start button and choosing Terminal opens PowerShell too |
PS C:\Users\you> |
| Windows, WSL2 Ubuntu | Open the Start menu, type Ubuntu, press Enter (A1 of the optional Route A setup installs it) |
you@laptop:~$ |
| Linux | Press Ctrl+Alt+T, or open Terminal from the applications menu | you@laptop:~$ |
| VS Code, on any system | Press Ctrl+` (backtick), or View > Terminal. It opens in the folder you have open | one of the above |
2. Find where you are, and move. The terminal always has a current folder, and every relative path is measured from it. These commands work the same in every shell above, including PowerShell:
pwd # print the folder you are in
ls # list what is here (ls -la also shows hidden files; in PowerShell, plain ls)
cd ~ # go to your home folder
mkdir -p ~/cs357 # make a folder for this course (in PowerShell: mkdir ~/cs357)
cd ~/cs357 # go into it
cd .. # go up one level
What
~means on each system.~is your home folder:
/Users/youon macOSC:\Users\youin PowerShell/home/youin WSL2 Ubuntu; from WSL2, your Windows files are under/mnt/c/Users/youDo all of this course’s work in
~/cs357, or in thecs357-workclone from A2 on Route A, so that everycd ~/cs357on this page lands in the same place. Press Tab to complete a name you have started typing, and the up arrow to recall the previous command.
3. Save a file. When a step says “save this as ollama_check.py,” first cd into the folder the file belongs in, then use one of these:
| Editor | Where it works | How to save ollama_check.py |
|---|---|---|
| nano | macOS, Linux, WSL2, and inside the course container | Run nano ollama_check.py, paste the contents (Cmd+V on macOS; right-click or Ctrl+Shift+V in Ubuntu), press Ctrl+O then Enter to write the file, then Ctrl+X to exit |
| vim | Every Unix system | Run vim ollama_check.py, press i to enter insert mode, paste, press Esc, then type :wq and press Enter to write and quit. If you get stuck, press Esc, type :q!, and press Enter to leave without saving |
| VS Code | Any system | From the folder, run code . to open it (or File > Open Folder), then File > New File, paste, and press Ctrl+S (Cmd+S on macOS) to save under the name the step gives. Type the name with its extension, ollama_check.py, and check that the editor did not add .txt |
| Notepad | PowerShell without nano | Run notepad ollama_check.py, click Yes to create the file, paste, save, and close Notepad |
For a one-line file in PowerShell, Set-Content notes.txt "model: llama3.2" writes it directly.
4. Confirm it landed. Run ls and see the file’s name; run cat ollama_check.py and see its contents. If ls does not show it, you saved into a different folder than the one you are in, and pwd tells you which one that is.
Where am I typing?
Most setup failures on this page come from running a command in the wrong place. Before every command, read your prompt.
| Your prompt looks like | You are on | What localhost means there |
|---|---|---|
you@laptop ~ %, you@laptop:~$, or PS C:\Users\you> |
Your host: macOS, Linux, WSL2 Ubuntu, or PowerShell | Your laptop, where Ollama listens |
student@a1b2c3d4e5f6:/workspace$ |
Inside the course container | The container itself, where nothing listens |
The address rule. Every command on this page that names Ollama’s address is written as
localhost:11434. On your host, leave it as written. Inside the container, replacelocalhostwithhost.docker.internal, becauselocalhostinside a container means the container. That is the only substitution on this page, and it applies only inside the container. Running Ollama itself as a Docker container does not change the rule: with its port published (-p 11434:11434),localhost:11434on your host still reaches it.
Windows. Use PowerShell (Windows 10 and 11 include it; open it from the Start menu) or WSL2 Ubuntu, not the old Command Prompt. Every command on this page and in the labs is written for PowerShell or a Unix shell. In PowerShell,
python3is spelledpython. When a transcript from inside the container is what you have, include the container prompt in your copy-paste so it is visible where each command ran.
Installing Python and pip
Step 4 runs a Python script, Part 1.5 installs uv, and every lab assumes a working Python somewhere. Whether you need to install one on your host depends on your route:
| Route A | Route B | |
|---|---|---|
| Do you need Python on your host? | Not for this page: the container carries Python 3.11 and every course library, and uv in Part 1.5, Step 3 brings its own Python. Install one anyway if you want to run scripts outside the container, or if you land on the python -m venv fallback there |
Yes, before Step 4: Python 3 and its package installer, pip |
| Which version | Whatever the table below gives you | 3.11 or later, to match the container’s Python; an older Python 3 runs this page’s script, but the labs’ libraries are tested against 3.11 |
Do. First check what you already have. Open a terminal (Opening a terminal above) and run both commands:
python3 --version
python3 -m pip --version
In PowerShell, spell them python --version and python -m pip --version. If both print a version, and the first is 3.11 or later, you are done; skip to Where am I typing?. Otherwise, install Python for your system from the row below; every row brings pip with it.
| System | How to install Python (pip comes with it) | Then |
|---|---|---|
| macOS, with Homebrew | brew install python@3.12. If you do not have Homebrew, install it first with the one-line command on its home page; it is also the easiest way to install uv in Part 1.5 |
Open a new terminal window |
| macOS, without Homebrew | Download the macOS installer from python.org/downloads and run it | Open a new terminal window |
| Windows, PowerShell | Download the Windows installer from python.org/downloads, run it, and on its first screen check the box that adds Python to PATH before you click Install Now. Or, in PowerShell, winget install --id Python.Python.3.12 -e |
Open a new PowerShell window, so the updated PATH loads |
| WSL2 Ubuntu, or Linux | sudo apt update && sudo apt install python3 python3-pip python3-venv. Ubuntu ships Python 3 but not pip or venv, which is why all three packages are named |
Nothing; the commands work in the same window |
What pip is, and why every pip command on this page starts with
python3 -m.pip is Python’s package installer, the tool that fetches libraries such as
requestsfrom the Python Package Index and puts them whereimportcan find them. It has shipped inside Python since version 3.4, so installing Python installs pip.A machine can hold several Pythons, though. Apple’s developer tools add one, Homebrew adds another, and each
uvenvironment has its own. A barepip installmay therefore target a different Python than thepython3you run scripts with. Writingpython3 -m pip install requestsruns pip from inside the Python you name, so the library lands where that Python will look for it.That one detail is the whole cause of the
ModuleNotFoundErrorin Step 4’s troubleshooting, and the reason this page writes every pip command the long way.Natively, pip also belongs inside a virtual environment, a per-project copy of the library folder. Route B: installing
requestsin Step 4 shows how to make and activate one, and every nativepip installon this page assumes you have.
What you should see. In a new terminal, python3 --version prints Python 3.12.x (or whatever you installed), and python3 -m pip --version prints a pip version followed by the path of the Python it belongs to. Check that the path points at the Python you just installed.
Paste. On Route B, both version lines, next to your operating system name in Stage 0.
Troubleshooting.
python3: command not found, or'python' is not recognized, right after installing means the terminal predates the install. Open a new one.- On Windows, typing
pythonopens the Microsoft Store when Python is not installed yet. Close the Store and run the installer above. If the Store keeps opening afterwards, open Manage app execution aliases from the Start menu and turn off thepython.exeandpython3.exeentries.No module named pipmeans pip was skipped.python3 -m ensurepip --upgraderestores it on macOS and Windows;sudo apt install python3-pipdoes on Ubuntu.- On a fresh Mac, typing
python3may offer to install Apple’s Command Line Tools instead. That copy of Python 3 runs Step 4, but it is several versions old, so install a current one from the table anyway.- If Homebrew says your macOS version is unsupported or a pre-release, run
brew updatefirst and retry.error: externally-managed-environmenton Ubuntu or Debian is handled under Step 4.See the Stage 0 rows in Troubleshooting.
Note.
uv, which Part 1.5 installs, can also fetch a Python of its own (uv python install 3.12) and run scripts with it (uv run python ollama_check.py). Install a system Python first anyway, so thatpython3works in every terminal and every editor, and so that thepython -m venvfallback has something to run.
Making a token, and putting it in your environment
A6 needs a GitHub token, and so does every tool you point at GitHub afterwards: gh inside the container, the GitHub tool server in the MCP session, and any agent you let file an issue on your behalf. None of them should ever read that token out of a file you commit. This section is where the token comes from and where it lives; come back whenever a page says “set GH_TOKEN” or writes something like {env:GITHUB_PAT} in a configuration file.
Do, the token. Go to github.com/settings/personal-access-tokens/new, which is the direct link for the menu path A6 spells out, and make a fine-grained token:
| Field | What to choose | Why |
|---|---|---|
| Repository access | Only select repositories, then the one repository you mean | A token that reaches one repository can damage one repository |
| Repository permissions | The narrowest set that does the job. Contents: Read and write pushes code; add Issues and Pull requests, read and write, only if an agent will open them for you | Permissions you did not grant are failures you cannot cause |
| Expiration | At or beyond the end of the semester | An expired token in week ten is a lab you cannot finish |
Watch out! The token is displayed once. Copy it before you leave that page, because the only repair is to delete it and make another.
Do, the variable. A configuration file should name a variable; your shell should hold the value. Set it for this terminal, and then set it for good so a new window still has it:
| System | For this terminal | To make it stick |
|---|---|---|
| macOS (zsh), Linux, WSL2 Ubuntu | export GITHUB_PAT=github_pat_yourtokenhere |
Add that same line to ~/.zshrc on macOS, or ~/.bashrc on Linux and WSL2, then open a new terminal |
| Windows, PowerShell | $env:GITHUB_PAT = "github_pat_yourtokenhere" |
[Environment]::SetEnvironmentVariable("GITHUB_PAT", "github_pat_yourtokenhere", "User"), then open a new PowerShell window so it loads |
| The course container, compose route | Nothing extra: set it on your host | The environment: block in docker-compose.yml passes it through by name on every docker compose run |
| The course container, VS Code route | export GITHUB_PAT=github_pat_yourtokenhere at the container prompt |
VS Code does not read the compose file, so this one lasts the session unless you add a remoteEnv entry to devcontainer.json |
What you should see. Nothing, which is the point. Check that the variable is set without printing what is in it:
[ -n "$GITHUB_PAT" ] && echo set
In PowerShell, if ($env:GITHUB_PAT) { "set" }.
Why not just
echoit? Because a token you print is a token in your scrollback, your shell history, and any screenshot you paste into the course channel. This is the same instinct as Part 1.5, where you printid_ed25519.puband neverid_ed25519.
And never paste one into an LLM. Not into a chat window, not into an agent prompt, not into a file you then ask an agent to read. Text you send to a hosted model rests in that provider’s logs, and it may be retained, reviewed, or trained on; deleting the conversation does not reach any of that. Scrollback you can clear. This you cannot.
This course makes the mistake easy, which is the reason to say it here rather than later. You will have a terminal with a live token in its environment and an agent in the next pane, and the natural move when something breaks is to paste the config and ask why. Paste the variable name, never its value, and redact before you send.
If it happens anyway, it is a two-minute problem and not a crisis: revoke the token at github.com/settings/personal-access-tokens, issue a new one, update the variable. Treat “I pasted it” as “it is compromised,” because from the outside those are the same thing. MCP, REST, and OAuth 2.0 Together shows how a token sitting in a context window gets extracted by an injected instruction, and the Coding Agents session builds a hook that rejects such a message before the model ever sees it.
Which name to use. Different tools read different variables, and that is worth one table rather than one guess:
| What reads it | Variable |
|---|---|
gh, the GitHub CLI |
GH_TOKEN, or GITHUB_TOKEN |
An opencode.json or .mcp.json configuration |
Whatever name you wrote inside the {env:...} reference |
| A server you write yourself | Whatever name your own os.environ call asks for |
Give a token the name of the thing it can reach, so GITHUB_SCRATCH_PAT for one scoped to a throwaway scratchpad repository and GITHUB_PAT for the one that reaches cs357-work. Two tokens under two names is the correct end state, not a mess to tidy up: share one name between them and the wider token will quietly satisfy the narrower use, which is exactly the blast-radius argument A6 makes about the container credential, arriving one layer down as a naming convention.
If an environment variable is awkward. opencode can also read the value out of a file, which survives a new terminal and a reboot without editing a shell profile:
"headers": { "Authorization": "Bearer {file:~/.secrets/github-scratch-pat}" }
Make it with mkdir -p ~/.secrets, put the token in ~/.secrets/github-scratch-pat, and run chmod 600 ~/.secrets/github-scratch-pat so only you can read it. The configuration still holds a reference rather than a value, so the rule survives intact.
If you would rather do neither. Pasting the token straight into opencode.json works, and it is the right call at midnight when nothing else does. Two conditions. Put the file in .gitignore before the token goes in, not after, because a commit that contains a token has leaked it whether or not the next commit removes it. And expect GitHub’s push protection to reject the push anyway, which is that lesson arriving from the other direction. For work you hand in, use the variable or the file.
Where the configuration file goes. opencode reads opencode.json from the project directory as well as from ~/.config/opencode/, and the choice is about scope rather than convenience. A configuration that belongs to one repository belongs in that repository, where its tools load when you are working there and nowhere else; a configuration you want everywhere goes in the home-directory copy. Step 5b comes back to this, and the MCP session leans on it hard.
Optional Route A Setup: Docker and the Course Container
This section is optional. It is Route A. If you are on Route B, skip to Step 1. If you are on Route A, do A1 through A6 once, on your host, and every lab this semester runs in what you build here: one Docker container with the whole course toolchain preinstalled, bind-mounted onto a directory that is a git repository with a GitHub remote, so everything you write inside the container is versioned and pushed like normal work.
| Runs inside the container | Stays on your host |
|---|---|
| Python 3.11 with the course libraries: retrieval, classical ML, NLP, and explainability | Ollama, the model server, installed natively for speed |
| Node.js with promptfoo, the evaluation lab’s harness | Your SSH key for GitHub (Part 1.5) |
| opencode, the coding agent, and herdr, the agent multiplexer | Docker Desktop itself, and your cs357-work clone on disk |
git, curl, and zip, so you commit, probe, and package from inside |
Your browser, your other courses, and everything else |
Your containerized code reaches Ollama at http://host.docker.internal:11434. Two ideas carry the whole design:
| Design idea | What it means for you |
|---|---|
| The image is the environment | One course Dockerfile, built once, gives everyone a byte-for-byte identical lab environment. “Works on my machine” stops being a sentence anyone says |
| The mount is the only door | The container can see exactly one directory of your machine, the workspace you mount into it. For a course where you run agent code that takes actions, that boundary is the blast-radius principle, enforced by architecture |
The Development Environment activity is the in-class version of these same six steps, and Docker from Zero explains the concepts from first principles whenever a step below feels like magic.
Budget about an hour, most of it downloads. Start the A4 image build as soon as you reach it and do Step 1 while it runs.
A1. Install Docker Desktop
| A1 at a glance | |
|---|---|
| Where you type | Your host terminal (PowerShell as Administrator for the Windows step) |
| Route A | Do it |
| Route B | Skip this whole section and go to Step 1 |
| You paste | The Hello from Docker! line |
If Docker is already on your machine from another course or project, skip to the verification at the end of A1. If Docker cannot be installed on your machine at all (unsupported hardware, an administrator lock, or too little disk), take Route B now; nothing is wasted.
Disk note. Docker Desktop plus the course image (the ML libraries are hefty) needs roughly 8-10 GB free, on top of Ollama’s models. Clear space now, not mid-download.
Windows only: install Ubuntu on WSL2 first. Docker Desktop on Windows does not run containers on Windows itself; it runs them inside WSL2, the Windows Subsystem for Linux. Installing the Linux side first prevents most of the Windows trouble on this page. Open PowerShell as Administrator and run:
wsl --install -d UbuntuReboot if it asks. Then launch Ubuntu from the Start menu and set the UNIX username and password it prompts for; these are new, and separate from your Windows account. If
wsl --installis not recognized, your Windows is too old for the one-liner: update Windows, or follow Microsoft’s manual WSL2 install steps. Do the rest of this page from the Ubuntu terminal:~means what it says, paths are ordinary Linux paths, and a repository kept in your WSL2 home directory bind-mounts far faster than one on the Windows side.
Do. Install Docker Desktop (macOS/Windows) or Docker Engine (Linux), and start it.
Windows only, after Docker Desktop is installed: check two settings. Open Docker Desktop’s Settings (the gear icon) and verify both rows of this table:
| Docker Desktop setting | Where to find it | Must be |
|---|---|---|
| Use the WSL 2 based engine | Settings -> General | Checked |
| WSL Integration for Ubuntu | Settings -> Resources -> WSL Integration | The Ubuntu toggle switched on, then Apply & Restart |
Watch out! The second setting is the one students most often miss, and its symptom is confusing: Docker Desktop looks perfectly healthy in its own window, but
dockeris not a command inside Ubuntu.
Verify. From a terminal (the Ubuntu terminal on Windows):
docker run hello-world
What you should see:
Hello from Docker! This message shows that your installation appears to be working correctly.A working Docker is your evidence that Route A is available to you.
Paste. The Hello from Docker! line.
Troubleshooting:
Cannot connect to the Docker daemonmeans Docker Desktop is installed but not running; start the application and wait for the whale icon to settle.docker: command not foundinside Ubuntu, while Docker Desktop is plainly running, is the WSL Integration setting in the table above. See the Stage A rows in Troubleshooting.
Next: A2.
A2. Create and clone your cs357-work repository
| A2 at a glance | |
|---|---|
| Where you type | Your host terminal |
| Route A | Do it |
| Route B | Skip |
| You paste | The two lines of git remote -v |
Your lab work lives in a private GitHub repository named cs357-work, the directory you will mount into the container and push to all semester.
Do.
- On github.com: New repository -> name
cs357-work-> Private -> check Add a README file. - Clone it, from the Ubuntu terminal on Windows or the Terminal on macOS and Linux:
cd ~
git clone https://github.com/YOURUSERNAME/cs357-work.git
cd cs357-work
git remote -v
Watch out! Keep the clone under your home folder. Docker Desktop shares that location with containers by default, and a clone on a second drive or a network share is the most common cause of an empty bind mount later.
What you should see:
origin https://github.com/YOURUSERNAME/cs357-work.git (fetch) origin https://github.com/YOURUSERNAME/cs357-work.git (push)The clone is a git repository that already knows its GitHub remote, the versioned half of the environment. The address is HTTPS on purpose: inside the container you will authenticate with a repository-scoped token (A6), and that token works over HTTPS. The SSH key you make in Part 1.5 stays on your host, where it belongs.
Paste. The two git remote -v lines.
Troubleshooting:
git: command not foundmeans git is not installed on your host yet; on macOS, runninggitonce offers to install the developer tools, on Ubuntusudo apt install git, and on Windows the Ubuntu terminal already has it. A prompt for a username and password means the repository is private and your host has no GitHub credential yet; Part 1.5 Step 2 sets one up, so for now make the repository, come back to this clone after Step 2, or use the GitHub Desktop alternative there.
Next: A3.
A3. Add the course container files
| A3 at a glance | |
|---|---|
| Where you type | Your host terminal, inside the cs357-work clone |
| Route A | Do it |
| Route B | Skip |
| You paste | The ls -la .devcontainer listing and the git commit line |
The course container is defined by three small files, which you keep in a .devcontainer/ folder inside your clone. Read them; each one is commented line by line.
| File | What it does |
|---|---|
| Dockerfile | The recipe for the course image; every package is commented with the lab that uses it |
| docker-compose.yml | One-command build/run, the workspace bind mount, and the Linux host.docker.internal fix |
| devcontainer.json | VS Code Dev Containers configuration |
| README.md (optional) | The quickstart version of this section |
Do. The commands below fetch all three into the right place. Run them from your clone:
cd ~/cs357-work
mkdir -p .devcontainer
cd .devcontainer
curl -fsSL -o Dockerfile https://raw.githubusercontent.com/BillJr99/Ursinus-CS357-Fall2026/gh-pages/files/devcontainer/Dockerfile
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/BillJr99/Ursinus-CS357-Fall2026/gh-pages/files/devcontainer/docker-compose.yml
curl -fsSL -o devcontainer.json https://raw.githubusercontent.com/BillJr99/Ursinus-CS357-Fall2026/gh-pages/files/devcontainer/devcontainer.json
cd ..
ls -la .devcontainer
What you should see: the three files, under exactly those names, so that your repository looks like this:
cs357-work/ .devcontainer/ Dockerfile docker-compose.yml devcontainer.json README.md
Watch out! If you saved the files from a browser instead, check the names, because browsers sometimes save
DockerfileasDockerfile.txt, and Docker will not find it under that name.
Open the Dockerfile and read it; it is exactly the anatomy from Docker from Zero Section 5 (FROM, ARG, RUN, ENV, WORKDIR, CMD), and every pip line names its lab. Then commit the files; they are part of your work:
git add .devcontainer
git commit -m "Add course dev container configuration"
Paste. The ls -la .devcontainer listing and the [main ...] Add course dev container configuration line.
Troubleshooting:
curl: command not foundon native Windows means you are in Command Prompt; use PowerShell or the Ubuntu terminal. Ifgit commitasks who you are, rungit config user.name "Your Name"andgit config user.email "you@example.com"in this folder and commit again; A6 says why per-repository is the right scope.
Next: A4.
A4. Build and enter the container
| A4 at a glance | |
|---|---|
| Where you type | Your host terminal to build; the container prompt once you are in |
| Route A | Do it |
| Route B | Skip |
| You paste | The container prompt, student@...:/workspace$ |
Build and enter by either front door. Both use the same Dockerfile, and you can switch anytime.
| Front door | Pick it if | How you enter |
|---|---|---|
| Option A: VS Code Dev Containers | You already use VS Code | Install the Dev Containers extension, open the cs357-work folder, and run Dev Containers: Reopen in Container from the command palette. Terminals you open in VS Code are now inside the container |
| Option B: plain Docker Compose | You use any other editor, or want to see every step | Run the three commands below from the .devcontainer/ folder |
Do, for Option B:
cd ~/cs357-work/.devcontainer
docker compose build
docker compose run --rm cs357
Budget note. The first build downloads the ML libraries and takes a while; start it and go do Step 1 in another terminal. Rebuilds are nearly instant thanks to layer caching.
What you should see: when the build finishes, a prompt like
student@a1b2c3d4e5f6:/workspace$You are the non-root user
student, in/workspace, which is yourcs357-workclone (ls -lashows.git,.devcontainer, andREADME.md). Exit anytime withexitor Ctrl-D;--rmdeletes the container but never your files, which live in the mounted repo on your disk.
Paste. The container prompt. Include it in every container transcript on this page, so it is visible where each command ran.
Troubleshooting: a build that cannot find the Dockerfile is the
Dockerfile.txtproblem from A3, or you randocker composefrom somewhere other than.devcontainer/. An empty/workspaceon Windows means the clone lives somewhere Docker Desktop does not share; see the Stage A rows in Troubleshooting. A build that dies with a network error resumes from the failed step when you rerundocker compose build.
Next: A5.
A5. Verify the tools inside the container
| A5 at a glance | |
|---|---|
| Where you type | The container prompt |
| Route A | Do it |
| Route B | Skip |
| You paste | The output of the three checks (and herdr --version if you include it) |
Three commands, each proving one tool is in the image. The fourth thing the container must do, reach Ollama on your host, is Step 3’s Route A subsection, because it needs the Ollama you install in Step 1.
| Check | Command | What you should see | What it proves |
|---|---|---|---|
| 1 | promptfoo --version |
0.x.x |
Node.js and promptfoo are wired correctly (the evaluation lab’s harness) |
| 2 | python3 -c "import spacy; nlp = spacy.load('en_core_web_sm'); print('spacy OK:', nlp('Agents plan and act.')[0].pos_)" |
spacy OK: NOUN |
The NLP model the explainability directions use is loaded |
| 3 | opencode --version |
opencode x.x.x |
The coding agent is baked into the image, so Step 5 has nothing left to install |
| optional | node --version |
v24.21.0 |
The image pins an exact Node release, because promptfoo requires 22.22 or newer; worth running first whenever promptfoo misbehaves. Not graded |
| optional | herdr --version |
a version string | herdr, an agent-aware terminal multiplexer a later lab uses, is in the image too; this one is not graded |
| optional | gh --version |
gh version 2.x.x |
gh, the GitHub CLI the Coding Agents session runs its whole loop through, is in the image as well, so Route A has nothing to install. Part 1.5, Step 2 is where it is graded, not here |
Do. At the container prompt, the three graded checks, ready to paste:
promptfoo --version
python3 -c "import spacy; nlp = spacy.load('en_core_web_sm'); print('spacy OK:', nlp('Agents plan and act.')[0].pos_)"
opencode --version
Paste. All three outputs, with the container prompt visible.
Troubleshooting.
ExperimentalWarning: DecompressInterceptor is experimentalabove the promptfoo version is harmless noise from promptfoo’s own dependencies, not a problem with your setup. The version string underneath it is what counts.- If promptfoo refuses to start and says your Node.js is too old, your image predates the Node 24 update. Appendix: Updating Your Container has the fix.
command not foundfor any of the three means an older build of the course image. Rerundocker compose buildfrom your.devcontainer/folder.permission deniedwriting files in/workspaceon a Linux host means the container’sstudentuser does not match your host UID. Run the container withdocker compose run --rm --user "$(id -u):$(id -g)" cs357.
Next: A6.
A6. Git identity and a push credential inside the container
| A6 at a glance | |
|---|---|
| Where you type | The container prompt, in /workspace |
| Route A | Do it |
| Route B | Skip |
| You paste | Nothing yet; the push in Part 1.5 Step 2 is the evidence |
The container ships with git but knows nothing about you, and it cannot push without a credential.
Why this matters. This is the one place in the course where your own machine and the container are set up differently on purpose: on your host you will use an SSH key (Part 1.5 Step 2); inside the container the default is a token scoped to one repository, because from Step 5 onward this container runs agent code that acts on your files, and a credential you place inside it is a credential that code can use.
Do, identity. Set your identity per repository, so it is stored in /workspace/.git/config, on your disk, inside the mount, and survives container teardown:
cd /workspace
git config user.name "Your Name"
git config user.email "you@example.com"
Note. The VS Code route copies your host
~/.gitconfiginto the container automatically, so Option A students often find this already done.
Do, credential. Create a fine-grained personal access token (PAT):
- GitHub -> Settings -> Developer settings -> Personal access tokens -> Fine-grained tokens -> Generate new token.
- Scope it tightly: Only select repositories ->
cs357-work; Repository permissions -> Contents: Read and write; expiration at or beyond the end of the semester. If you already know you will let an agent open issues or pull requests for you, which the Coding Agents session has you do two days from now, add Issues: Read and write and Pull requests: Read and write while you are here. Adding them later meansgh auth refresh -s <scope>or a new token, and the symptom that sends you looking is a bare 403. - Copy the token (shown once). When
git pushprompts for a password, paste the token. - Cache it for a work session so you are not retyping:
git config credential.helper 'cache --timeout=7200'
The token you just made is now something you can leak. Keep it out of any message you send to a model, including the agent you are about to run in this container. Making a token, and putting it in your environment, in the Part 1 orientation, says what to do if it gets out.
Do, gh. The image also carries gh, and it knows even less about you than git does. Give it the token you just made, as an environment variable, from the host shell you start the container in:
export GH_TOKEN=github_pat_yourtokenhere # on your host, before docker compose run
The environment: block in docker-compose.yml passes GH_TOKEN through by name, so the value stays in your shell and never enters a file. Making a token, and putting it in your environment, back in the Part 1 orientation, is where the variable and its platform spellings live. Confirm inside the container with gh auth status.
Do not run
gh auth loginin the container, and do not mount~/.config/ghinto it. Both work, and both are the wrong shape here.gh auth loginstores a credential with whatever reach your account has, inside a filesystem that is about to run agent code; mounting your hostghconfiguration hands that same code the token that can push to every repository you own. The variable above carries exactly one repository-scoped token and disappears when the container does, which is the A6 argument in one more place.
Note.
gh auth setup-gitmakesgit pushover HTTPS use that same token, which replaces the credential-cache line above. It is a fine trade on your host. Inside the container, either works.
The two credentials you could use inside the container, side by side:
| Scoped token (recommended) | Mounted SSH key | |
|---|---|---|
| Blast radius | One repository, cs357-work, and nothing else |
Every repository your key can reach |
| How it authenticates | Pasted at the HTTPS password prompt, cached for the session | Your host’s ~/.ssh mounted read-only, with the git@github.com:... remote form |
| When to choose it | By default, and whenever you are not sure | Only if you already use SSH keys with GitHub and accept the trade-off below |
Alternative, only if you already use SSH keys with GitHub. Add one line to the
volumes:list indocker-compose.yml:volumes: - "..:/workspace" - "~/.ssh:/home/student/.ssh:ro"
:romakes the mount read-only (the container can use the keys, not modify them), and you would use thegit@github.com:...remote form. Mounting~/.sshdeliberately widens the container’s view of your machine: you are handing everything that runs inside (including, later, agent code) a credential that can push to every repository your key reaches. Step 6 of the Development Environment activity says more about why the two environments differ.
What you should see:
git config user.nameprints your name. The credential proves itself at the first push, in Part 1.5 Step 2.
Troubleshooting:
fatal: detected dubious ownership in repository at '/workspace'is git noticing that the repository’s owner (your host account) is not the user running the command (student). Rungit config --global --add safe.directory /workspaceinside the container and rerun the command that failed. The container’s~/.gitconfigis recreated on everydocker compose run --rm, so expect to run this once per session; the VS Code route keeps one long-lived container, so once is usually enough.
Next: the Route A checklist, then Step 1.
You’ve succeeded when every box below is checked. Then go to Step 1; on Route A, Steps 1 through 3 run on your host, not in the container.
Route A Checklist
docker run hello-worldprintedHello from Docker!git remote -vshows yourcs357-workrepositoryDockerfile,docker-compose.yml, anddevcontainer.jsonare in.devcontainer/and committed- The
student@...:/workspace$prompt appears promptfoo --version, the spacy check, andopencode --versionall print output
Step 1. Install Ollama, pull a small model, and confirm both
| Step 1 at a glance | |
|---|---|
| Where you type | Your host terminal (on Route A too: Ollama stays on the host) |
| Route A | Do it |
| Route B | Do it |
| You paste | The ollama --version line and the ollama list table |
Do. Install Ollama for your operating system. Then open a new terminal and run:
ollama --version
ollama pull llama3.2
ollama list
What you should see: a version string, a download that ends in
success, and a table fromollama listwith allama3.2:latestrow. The pull is about 2 GB and is the longest wait on this page. On Route A, the A4 image build can run at the same time.
Paste. The ollama --version line and the ollama list table.
Troubleshooting:
command not foundornot recognizedmeans your terminal predates the install; open a new one. See the Stage 1 rows in Troubleshooting.
Next: Step 2.
Step 2. Run a CLI sanity check
| Step 2 at a glance | |
|---|---|
| Where you type | Your host terminal |
| Route A | Do it |
| Route B | Do it |
| You paste | The command and the model’s reply |
Do.
ollama run llama3.2 "Say hello in five words."
What you should see: one short line of text from the model. The exact words vary. On CPU-only hardware the first reply can take a minute, which is normal.
Paste. The command and the reply.
Troubleshooting: a connection error means the Ollama server is not running, which is separate from Ollama being installed. See the Stage 3 rows in Troubleshooting.
Next: Step 3.
Step 3. Verify the REST API responds
| Step 3 at a glance | |
|---|---|
| Where you type | Your host terminal; on Route A, then the container prompt for the bridge check |
| Route A | Do it on the host, then also run the bridge check below from inside the container |
| Route B | Do it |
| You paste | The JSON (and, on Route A, the bridge check’s output) |
Do.
curl http://localhost:11434/api/tags
What you should see: a JSON object that begins
{"models":[{"name":"llama3.2:latest", ...
Paste. The JSON. Trim it to the first few lines if it is long.
Troubleshooting:
Connection refusedmeans the server is not running. Start the Ollama desktop app, or runollama servein a second terminal and leave that terminal open.
Route A: also check the bridge from inside the container
Do. Keep Ollama running on the host, enter the container (A4), and send one line of Python straight through the container wall to the model server on your host:
python3 -c "import requests; print(requests.get('http://host.docker.internal:11434/api/tags').json())"
What you should see: yours will show your models and digests:
{'models': [{'name': 'llama3.2:latest', 'model': 'llama3.2:latest', 'modified_at': '...', 'size': 2019393189, 'digest': '...', 'details': {...}}]}If you see a
modelslist containingllama3.2, the whole architecture works: containerized Python ->host.docker.internal-> native Ollama.
Why this matters. Try
curl http://localhost:11434/api/tagsfrom the same container prompt and watch it fail;localhostinside the container is the container, not your machine, and that failure is correct behavior. The image also setsOLLAMA_HOST=http://host.docker.internal:11434, so tools that read that variable find the host server automatically; in your own code, use thehost.docker.internalURL whenever a lab handout sayslocalhost:11434.
Paste. The one-liner’s output, with the container prompt visible.
Troubleshooting: a connection error here has two usual suspects: Ollama is not actually running on the host right now, or, on Linux, the container was started without the course compose file, whose
extra_hostsmapping (host.docker.internal:host-gateway) makes the hostname resolve at all. See the Stage 4 rows in Troubleshooting.
Next: Step 4.
Step 4. Call the model from Python
| Step 4 at a glance | |
|---|---|
| Where you type | The container prompt on Route A; your host terminal on Route B |
| Route A | Skip the install; run the script inside the container with host.docker.internal |
| Route B | Install requests, then run the script |
| You paste | The printed JSON, including a "content" field |
This is the first step where the route matters. Read your column, then do the part both routes share.
| Route A | Route B | |
|---|---|---|
| Before you start | Finish A1 through A5 if you have not, then enter the container (A4) | Nothing |
| Install | Nothing; the container already has the requests library |
Python 3 with pip, from Installing Python and pip in the orientation, then requests by one of the three ways in Route B: installing requests just below |
Where to save ollama_check.py |
/workspace, which is your cs357-work clone |
~/cs357 |
| Address in the script | Replace localhost with host.docker.internal |
Leave localhost as written |
Route B: installing requests
The script imports requests, a library that is not part of Python. Three tools can install Python packages, and this is the first place on the page where you choose one. The table names what each is for, because the choice matters more than the command:
| Tool | The command | What it does, and when to use it |
|---|---|---|
| pip, inside a virtual environment | macOS, Linux, or WSL2: cd ~/cs357, python3 -m venv .venv, source .venv/bin/activate, then python3 -m pip install requests. PowerShell: cd ~/cs357, python -m venv .venv, .\.venv\Scripts\Activate.ps1, then python -m pip install requests |
A virtual environment is a private copy of Python’s library folder for one project, kept in .venv, so that the library lands there rather than in the system Python. Activating it (the prompt gains a (.venv) prefix) makes python3 and pip mean that copy for the rest of the terminal session, so activate it again in every new terminal. Installing without one works on macOS and Windows but is a habit to unlearn: it stops with error: externally-managed-environment on Ubuntu, Debian, and a fresh WSL2 Ubuntu, and it mixes every project’s libraries together. If PowerShell refuses to run the activation script, run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser once, then retry |
| uv | cd ~/cs357, uv init, uv add requests, then run the script with uv run python ollama_check.py |
Creates a project with its own environment in ~/cs357/.venv, records requests in pyproject.toml, and runs the script inside that environment. This is exactly what Part 1.5, Step 3 does, so doing it here means Step 3 is already done; install uv from the table in that step first |
| pipx | pipx install requests |
Stops with No apps associated with package requests, and that is the right answer: pipx installs programs (each in its own private environment, with the command on your PATH), not libraries for your scripts to import. It is the right tool for pipx install uv, and the wrong one for requests |
Whichever tool you choose, run the script from the same terminal, with the environment still activated on the pip route, so that the Python that runs it is the one that received the library. Both pip’s .venv and uv’s .venv are ordinary folders in ~/cs357; if that folder becomes a git repository in Part 1.5, add .venv to its .gitignore, because an environment is rebuilt from pyproject.toml rather than committed.
Both routes: save and run the script
Do. Save the script below as ollama_check.py in the folder from your column. Python runs files, so put the lines in a file rather than typing them at a prompt. Opening a terminal, moving around, and saving a file above walks through nano, vim, VS Code, and Notepad; any of them does the job.
import requests, json
response = requests.post(
"http://localhost:11434/api/chat",
json={
"model": "llama3.2",
"messages": [{"role": "user", "content": "What is 2 + 2?"}],
"stream": False
}
)
print(json.dumps(response.json(), indent=2))
Run it from the directory the file lives in:
cd ~/cs357
python3 ollama_check.py
Note. On Windows in PowerShell the command is
python ollama_check.py. VS Code’s Run button (the triangle in the top right, with the Python extension installed) runs the same command in its integrated terminal, and either transcript is fine for your submission.
What you should see: a JSON object with a
"message"block whose"content"field holds the model’s answer.
Paste. The printed JSON.
Troubleshooting.
ModuleNotFoundError: No module named 'requests'means the install landed in a different Python than the one you ran. Usepython3 -m pip install requests, orpython -m pipon Windows, from the same terminal, then rerun.error: externally-managed-environmenton Ubuntu, Debian, and therefore a fresh WSL2 Ubuntu, is the operating system refusing to let pip change its own Python. It means you skipped the virtual environment in the pip row above. Create and activate it, so the prompt gains a(.venv)prefix, then rerun thepip installand the script from that same terminal. Or jump ahead to Part 1.5, Step 3, and letuvmanage it.Activate.ps1 cannot be loaded because running scripts is disabledin PowerShell is the execution policy. The pip row has the one-time fix.- A
ConnectionErrorinside the container means the address rule was not applied.can't open file ... No such file or directorymeans you are in a different directory than the one you saved into.ls, ordiron Windows, shows which.
You’ve succeeded when the four boxes below are checked. That is Part 1A.
Part 1A Checklist
ollama --versionreturns a version stringollama listshows at least one downloaded model- The
curlcommand to/api/tagsreturns JSON (not a connection error) - Your Python script prints a response that includes a
"content"field
Only if you installed Ollama as a Docker container. The course expects Ollama installed natively, as in Step 1. If you installed it as a Docker container instead, the
ollamacommand does not exist on your host, and everyollama ...command on this page runs inside that container. Reach it withdocker exec, naming the container. The conventional name, and the onedocker run --name ollamagives you, isollama;docker psshows what yours is actually called. The word appears twice for a reason: the first is the container, the second is the program inside it.docker exec ollama ollama --version docker exec ollama ollama pull llama3.2 docker exec ollama ollama listAdd
-itwhen the command is interactive, as the chat in Step 2 is:docker exec -it ollama ollama run llama3.2 "Say hello in five words.". Steps 3 and 4 are unaffected as long as you published the port with-p 11434:11434, becauselocalhost:11434on your host then reaches the server inside the container. A transcript from this route is fully accepted; leave thedocker execprefix visible in what you paste, so I can see where the command ran.
Next: Step 5, or the optional OpenWebUI install first.
Optional: Install OpenWebUI, the chat interface over Ollama
OpenWebUI is a self-hosted web front end that sits in front of your Ollama server: a private chat window over the model you pulled in Step 1, with tools, knowledge bases, and an API of its own. Nothing in this assignment needs it. The Local Agent lab’s no-code path and several later tutorials assume it is installed, so doing it now, while the model is fresh, saves you the first fifteen minutes of that lab.
| Optional OpenWebUI at a glance | |
|---|---|
| Where you type | Your host terminal, on both routes. On Route A, OpenWebUI is its own container beside the course container, not inside it |
| What you install | OpenWebUI by one of two routes: Docker (recommended if Docker Desktop is already running) or pip (no Docker required) |
| You paste | Nothing is required. A screenshot of the model dropdown showing llama3.2 is welcome |
Do. Confirm Ollama is running (ollama list shows llama3.2), then install by one route.
Route 1, Docker. Each flag does one job: -p 3000:8080 publishes the container’s port 8080 on your host’s port 3000; --add-host=host.docker.internal:host-gateway lets the container reach Ollama on your host (required on Linux, harmless elsewhere); -v open-webui:/app/backend/data keeps your account, models, and chat history in a named volume that survives restarts.
docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway \
-v open-webui:/app/backend/data --name open-webui --restart always \
ghcr.io/open-webui/open-webui:main
Route 2, pip. The first command downloads a large dependency set; expect several minutes. The second starts the server in your terminal; leave that terminal open. When the startup banner shows Uvicorn running on http://0.0.0.0:8080, the server is up.
pip install open-webui
open-webui serve
Then, in the browser (http://localhost:3000 for Docker, http://localhost:8080 for pip):
- Create an admin account (name, email, password). It exists only in your local OpenWebUI database; the email is a local username.
- In the model selector at the top left, confirm
llama3.2:latestappears. OpenWebUI finds a local Ollama server on its own. - Send one message (“Say hello in one sentence.”) and read the reply.
- Create an API key: click your initials (bottom left), then Settings, Account, API Keys, Create new key. Keep it somewhere you will find again. It authenticates you to a server on your own machine; it is not a payment credential. The Local AI session and the Local Agent lab both use it.
Troubleshooting.
- An empty model dropdown means OpenWebUI cannot reach Ollama. On the Docker route, confirm the
--add-hostflag was present, then open Admin Panel, Settings, Connections and set the Ollama URL tohttp://host.docker.internal:11434. Notlocalhost, which inside the container means the container itself; see The address rule above. On the pip route the URL ishttp://localhost:11434, andollama servemust be running.- If
http://localhost:3000refuses to connect on the Docker route, rundocker ps. When the container is missing,docker logs open-webuisays why. A port conflict means something else owns 3000, so re-run with-p 3001:8080and browse to 3001.- If
pip install open-webuifails with a resolver or build error, it is almost always a Python version issue. Create a 3.11 environment withpython3.11 -m venv owui && source owui/bin/activateand install inside it.
Next: Step 5.
Step 5. Confirm your coding agent is installed and talking to that same local model
| Step 5 at a glance | |
|---|---|
| Where you type | The container prompt on Route A; your host terminal on Route B |
| Route A | Skip 5a (opencode is in the image); do 5b and 5c |
| Route B | Install opencode in 5a, then do 5b and 5c |
| You paste | The version string, and one question with the agent’s answer |
The agent is opencode, and every install route lives at opencode.ai. The Week 2 lab depends on this step, so it is here rather than discovered later.
5a. Install it
| Route A | Route B | |
|---|---|---|
| Install | Nothing. opencode is already in the course image, and A5 printed its version. Skip to 5b | One of the three commands in the rows below, for your system |
| macOS, Linux, or WSL | curl -fsSL https://opencode.ai/install \| bash |
|
Already have Node.js (20 or newer; check with node --version) |
npm i -g opencode-ai |
|
| Native Windows, in PowerShell | choco install opencode or scoop install opencode |
Note. That page also offers a desktop app, in beta for macOS, Windows, and Linux, if you would rather work in a window than a terminal. It drives the same agent, but install the command-line version even if you try the desktop one, because this assignment and every lab ask for terminal output.
5b. Point it at your model (both routes)
opencode reads one configuration file, and the file name matters: it is opencode.json, never config.json. opencode silently ignores a file with the wrong name.
Where you put it decides which projects it applies to. A copy in a project directory configures that repository and nothing else; a copy in your home directory configures everything you open. Pick the row for what you want, and note that the address also changes with it, because localhost means something different inside a container:
| Where the file goes | What it configures | The baseURL inside it |
|---|---|---|
/workspace/opencode.json, the root of your cs357-work repository (Route A) |
That repository. It is also the copy that survives docker compose run --rm, since the container’s home directory does not |
http://host.docker.internal:11434/v1 |
Any other project directory, as opencode.json at its root |
That project | Match it to where opencode runs |
~/.config/opencode/opencode.json on macOS, Linux, or WSL |
Every project you open | http://localhost:11434/v1 |
%USERPROFILE%\.config\opencode\opencode.json on Windows |
Every project you open | http://localhost:11434/v1 |
For today either choice works, since the only thing being configured is a model provider you want everywhere. The distinction starts to matter in the MCP session, where a configuration can attach a whole tool server to a project: a server that belongs to one repository costs you context in every other one if you put it in the home-directory copy.
Do. The smallest working file registers Ollama and nothing else. Create the folder if it does not exist, save this as opencode.json in the location from the table, and use the baseURL from the table:
{
"provider": {
"ollama": {
"npm": "@ai-sdk/openai-compatible",
"options": { "baseURL": "http://localhost:11434/v1" },
"models": { "llama3.2": { "name": "llama3.2 (raw Ollama)" } }
}
}
}
Note. Step 8 of the Development Environment activity walks through the same file in more detail, including an optional second provider you can add later.
5c. Verify it (both routes)
Do.
opencode --version
Then start opencode, type /model, and confirm your Ollama provider is listed. Ask it one question (“what files are in this directory?” is enough).
What you should see: a version string, a
/modellist that names your Ollama provider, and an answer to your question. A small local model answers slowly and sometimes oddly; that is the model, not your setup.
Paste. The version string, and the question with the agent’s answer.
Troubleshooting: an empty provider list means the file name or location is wrong. Check the name first, then the location from the table, then that the JSON parses with
python3 -m json.tool opencode.json. See the Stage 5 rows in Troubleshooting.
You’ve succeeded when the three boxes below are checked. That is Part 1B.
Part 1B Checklist
opencode --versionreturns a version string/modellists your Ollama provider- The agent answered one prompt from your local model
Optional: install herdr while you are here
This is not graded and is not on the checklist above. It is here because installing it now costs one command, and a later lab assumes it is on your bench.
herdr is an agent-aware terminal multiplexer. It keeps agents running after you close your laptop or drop an SSH connection, and it shows you which agent is blocked, working, or done, so you are not cycling through terminal panes to find the one waiting on you.
| Where | How to install |
|---|---|
| Route A | Already in the course image, alongside opencode. Confirm with herdr --version |
| Route B: macOS, Linux, WSL | curl -fsSL https://herdr.dev/install.sh \| sh |
| Route B: macOS with Homebrew | brew install herdr |
| Route B: native Windows | powershell -ExecutionPolicy Bypass -c "irm https://herdr.dev/install.ps1 \| iex" |
The installer downloads one binary, checks its SHA-256, and puts it in ~/.local/bin. If you include herdr --version in your transcript, I will read it, but its absence costs you nothing.
Next: Capturing Part 1, then Part 1.5.
Capturing Part 1
Copy-paste or screenshot the output of all five steps, including the output of ollama --version and your operating system name and version. On Route A, also include the container prompt and the A5 output, and say which route you took.
If any step fails: document the error message verbatim, state your hypothesis about the cause, and describe what you tried. A well-documented failure with a follow-up plan earns full credit for that step. Do not delete error output or write “it eventually worked” without showing what changed.
Part 1.5: Command-Line and Git Checkpoint
Every lab this semester runs from a terminal, lives in a git repository, and depends on a reproducible Python environment. This checkpoint makes sure those tools work before the labs depend on them, the same philosophy as the Ollama setup above. You do not need to be a shell wizard; you need to be able to move around, version your work, and stand up an environment without guesswork. If any command below is unfamiliar, the Command-Line Survival resources at the end of this section will get you there.
Complete each step and capture the terminal output.
Step 1. Navigate and search
| Part 1.5, Step 1 at a glance | |
|---|---|
| Where you type | Your host terminal; on Route A, the container prompt in /workspace works too |
| Route A | Do it |
| Route B | Do it |
| You paste | Every command with its output |
Do. Create a working directory for this course, enter it, and list its contents:
mkdir -p ~/cs357 && cd ~/cs357 && pwd && ls -la
The directory is empty when you make it, so create a file for the search to find. Redirecting a couple of lines into a file is the quickest way; typing them into an editor (nano notes.txt, or any of the ways in Opening a terminal, moving around, and saving a file) or copying in a file you already have works just as well:
printf 'model: llama3.2\nhost: http://localhost:11434\nagent: opencode\n' > notes.txt
cat notes.txt
Watch out!
touch notes.txtcreates the file but leaves it empty, and a search over an empty file matches nothing, so put a line or two inside it. In native PowerShell, the equivalent isSet-Content notes.txt "model: llama3.2", sinceprintfandgrepare Unix shell tools, andSelect-Stringis the PowerShell search command. Running these from WSL2 or Git Bash keeps the commands as written.
Then search that file with grep (or ripgrep, the rg command, if installed):
grep -n "localhost" notes.txt
What you should see:
pwdprints a path ending incs357,ls -lalists.and..,catprints your three lines, andgrepprints2:host: http://localhost:11434
Paste. Every command above with its output.
Next: Step 2.
Step 2. Authenticate to GitHub with gh, then commit and push
| Part 1.5, Step 2 at a glance | |
|---|---|
| Where you type | Your host terminal for 2a through 2c (the credential lives on your host); your host terminal, or on Route A the container prompt, for 2d and 2e |
| Route A | Do 2a through 2c on your host; for 2d and 2e, use cs357-work from A2 and push from the container with your A6 token |
| Route B | Do 2a through 2e |
| You paste | The gh auth status output and the git log --oneline output |
If you built the course container (Route A).
You already have a repository:
cs357-workfrom A2, cloned over HTTPS, with a scoped token from A6. So for 2d, skip creating or cloning anything. For 2e, make your commit and push from the container prompt in/workspace; the token authenticates the push, andgit log --onelinethere is your transcript.Still do 2a through 2c on your host. The labs drive git and
ghfrom a host terminal too, and that is where the credential belongs.If you built the container in class, the practice loop in Step 7 of the Development Environment activity, which creates
hello_agent.py, runs it against host Ollama, commits, and pushes, is exactly this checkpoint. Its transcript satisfies it.
You will push to GitHub every week this semester, so set authentication up once, now. GitHub stopped accepting your account password over HTTPS years ago, which leaves two credentials that work: an SSH key, and a token. The fastest way to get either one is gh, the GitHub CLI, which creates the key, uploads it to your account, and hands git a credential, in one sign-in.
Install it even if you already have a working key, and install it even if you prefer clicking. Two days from now the Coding Agents session runs its entire loop through gh, an issue becomes the task and a pull request becomes the attempt, and the Agents That Talk session later uses the same commands as a message bus between two agents. This is the step where you get it.
| Sub-step | What you do | Command | Paste it? |
|---|---|---|---|
| 2a | Install the GitHub CLI | gh --version |
No |
| 2b | Sign in, letting gh make and upload an SSH key |
gh auth login |
No |
| 2c | Confirm | gh auth status, then ssh -T git@github.com |
Yes, the status block |
| 2d | Get the repository onto your machine | gh repo clone <your-username>/<your-repo> |
No |
| 2e | Commit and push | git add, git commit, git push -u origin main, then git log --oneline |
Yes, the log |
2a. Install the GitHub CLI. One row for your system:
| System | How to install gh |
|---|---|
| macOS, with Homebrew | brew install gh |
| Windows, PowerShell | winget install --id GitHub.cli, then open a new PowerShell window so PATH picks it up |
| WSL2 Ubuntu, Debian, Ubuntu | Follow the four-line apt recipe on cli.github.com, which adds GitHub’s package repository. Do not use plain apt install gh alone: the version Ubuntu ships lags far enough behind to be missing commands the labs use |
| Other Linux | Your package manager (dnf install gh, pacman -S github-cli), or the release archive from cli.github.com |
| The course container (Route A) | Nothing. gh is in the image, and gh --version at the container prompt proves it |
Confirm with gh --version.
2b. Sign in. Before you generate anything, look for a key you already trust, because gh will offer to use it rather than making a second one:
ls -al ~/.ssh
Then:
gh auth login
Answer the prompts: GitHub.com, then SSH as the protocol, then let it generate a new SSH key if ls showed none (give the key a passphrase; that passphrase is what keeps the key useless to somebody who copies the file), then Login with a web browser, which prints a one-time code to paste into the page it opens.
That single exchange replaces three manual steps: ssh-keygen, printing the public half, and pasting it into GitHub’s settings. It also uploads the key under a name you can revoke later, which is the part people forget to do by hand.
Note. If you would rather git push over HTTPS than over SSH, run
gh auth setup-gitafterwards. It installsghas git’s credential helper, sogit pushstops asking who you are. You do not need it on the SSH path.
2c. Confirm it.
gh auth status
ssh -T git@github.com
What you should see:
gh auth statusnames github.com, your account, the protocol, and the token’s scopes.ssh -Tasks you to accept GitHub’s host fingerprint the first time, which you answeryes, and then greets you by username. It does not open a shell, and the line saying GitHub does not provide shell access is the success case, not an error. Paste thegh auth statusblock.
Watch out! Tokens expire and sessions lapse. When a
ghcommand fails later in the semester with a 401, a 403, or a flat refusal to authenticate,gh auth statustells you which,gh auth loginsigns you back in, andgh auth refresh -s <scope>adds a permission you did not originally grant. You will want that last one in the Coding Agents session.
2d. Get the repository onto your machine. Use your course GitHub Classroom repository, the cs357-work repository from A2 (Route A students already have it; skip to 2e), or a throwaway repository. If it exists on GitHub already:
cd ~/cs357
gh repo clone <your-username>/<your-repo>
cd <your-repo>
git remote -v
What you should see: cloning creates a new folder named after the repository inside whatever directory you ran it from, which is why you
cdinto it on the next line.git remote -vprints an address twice, once for fetch and once for push, and that is your evidence the working copy is wired to GitHub.gh repo clonepicks the protocol you authorized in 2b, so you never paste a URL. A repository with no commits clones with a warning that it is empty; the commit below is about to fix that.
If nothing exists on GitHub yet, make it and clone it in one command:
gh repo create <your-repo> --private --clone
cd <your-repo>
2e. Commit and push. Git versions files, so the repository needs a file before there is anything to commit. Make one the way you made notes.txt:
printf '# CS357 scratch repository\n' > README.md
git add README.md
git commit -m "first commit"
git push -u origin main
What you should see:
git commitprints a line beginning[mainwith your message, andgit pushends withmain -> main. Then rungit log --onelineand paste its output, which shows your commit.
Troubleshooting.
git commitwithout-mdrops you into an editor. If that editor turns out to bevim,:q!leaves it.git pushcomplains if your default branch is not namedmain.git branch -M mainfixes it.- A repository that still has an HTTPS remote from an earlier clone can be switched in place rather than recloned:
git remote set-url origin git@github.com:<your-username>/<your-repo>.git, then check withgit remote -v.- On native Windows, PowerShell ships OpenSSH, so these commands work as written. If
ssh-addreports that the agent is not running, start it once from an elevated PowerShell withSet-Service -Name ssh-agent -StartupType Manualfollowed byStart-Service ssh-agent.See the Stage 7 rows in Troubleshooting.
2f. Or ask the agent to do it.
Everything in 2d and 2e is a command line, and you now have a program sitting in the next terminal whose job is composing command lines. Try it once, here, where you already know what the right answer looks like:
“Create a private GitHub repository called
cs357-scratchusinggh, clone it into this folder, add a README that says what it is, commit, and push. Show megit log --onelinewhen you are done.”
“Run
gh auth statusand tell me what it says. If I am not signed in, stop and say so rather than trying to fix it yourself.”
The permission gate asks before each shell command, so you see exactly what the agent composed before it runs, which is the whole reason to do this the first time on a throwaway repository. Neither prompt is graded. They are here because the command line is how you learn what an operation is, and the prompt is how you will actually work once you know, and this course would rather you met both in week one.
Alternative: do it by hand, with ssh-keygen
gh auth login automates a three-step exchange that is worth seeing once, and if gh will not install on your machine you need it anyway. Nothing below is required if 2b succeeded.
Check for a key you already have.
ls -al ~/.ssh
If id_ed25519.pub (or id_rsa.pub) is listed and you know its passphrase, skip to registering it.
Create one. Use the email address tied to your GitHub account:
ssh-keygen -t ed25519 -C "you@example.com"
Press Enter to accept the default location, and set a passphrase rather than leaving it empty. Then load it into the agent so you type that passphrase once per session rather than once per push:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
Register the public half with GitHub. Print the .pub file, which is the public one:
cat ~/.ssh/id_ed25519.pub
Watch out! Never paste or send the file without the extension, which is the private key. Only the
.publine goes to GitHub.
On GitHub, go to Settings -> SSH and GPG keys -> New SSH key (the direct link is github.com/settings/keys). Title it after the machine it lives on, so you can revoke exactly one laptop later; leave the key type as Authentication Key; paste the whole line, and save. Then test with ssh -T git@github.com and continue from 2d, using git clone git@github.com:<your-username>/<your-repo>.git in place of gh repo clone.
Other alternatives for 2d and 2e (choose at most one)
| Tool | Replaces | Best for | Verify with |
|---|---|---|---|
| VS Code | The clone in 2d | Anyone already working in VS Code | git log --oneline in the integrated terminal |
| GitHub Desktop | 2d and 2e | Anyone who would rather not type git commands yet | git log --oneline from Repository > Open in terminal |
VS Code, for the clone in 2d. It drives the same git underneath, so the result is identical.
With no folder open, the Source Control view (Ctrl+Shift+G, or Cmd+Shift+G on macOS) offers a Clone Repository button. From anywhere, the Command Palette (Ctrl+Shift+P, or Cmd+Shift+P) runs Git: Clone. Either one asks for the repository address, where you paste the same SSH URL, then asks which local folder to put it in, ~/cs357 here, and offers to open the clone when it finishes.
Say yes. The integrated terminal (Ctrl+`) then opens already inside the repository, which is where you run git log --oneline for your transcript.
The palette also offers Clone from GitHub, which lets you pick from a list of your repositories instead of pasting a URL. Note that it signs you in to GitHub inside VS Code and authenticates as that account rather than with your key.
GitHub Desktop, for 2d and 2e. If you would rather not type git commands yet, GitHub Desktop is a supported option and handles authentication for you: install it, sign in, use File > New repository (or Add local repository) on your cs357 folder, commit from the Changes tab, and Publish repository to push. Then paste the output of git log --oneline from Repository > Open in terminal, which is the same transcript the command-line route produces.
Watch out! GitHub Desktop is not a substitute for 2a. It bundles Git for Windows, not
gh, and signing in to Desktop does not sign ingh. Do 2a and 2b anyway, because the Coding Agents session and every agent you run afterwards drive GitHub from a terminal.
The two GitHub downloads are easy to confuse on native Windows without WSL2:
GitHub CLI (gh) |
GitHub Desktop | |
|---|---|---|
| What it is | The command-line tool, a separate install | The graphical client |
| What it bundles | Nothing else | Git for Windows, which is where ssh-keygen and Git Bash come from |
| How it authenticates | gh auth login, over SSH or HTTPS |
Its own sign-in, handled for you |
| Does it give you the other one? | No, and it does not need to | No. Desktop cannot run gh issue create |
| Installing both | Common, and they coexist happily |
The Ubuntu or WSL2 route gives you the standard Unix tooling instead, and every command on this page then works as written.
Route A and credentials inside the container. The credential you just made lives on your host, which is where it belongs. Inside the course container, A6 recommends a fine-grained personal access token scoped to
cs357-workinstead, because that container will soon be running agent code, and a credential placed inside it is a credential that code can use. That is why A2 clonescs357-workover HTTPS: the token authenticates HTTPS pushes. Step 6 of the Development Environment activity goes deeper, and A6 shows the read-only~/.sshmount if you would rather use your key there.
Next: Step 3.
Step 3. Reproducible Python with uv
| Part 1.5, Step 3 at a glance | |
|---|---|
| Where you type | Your host terminal, on both routes |
| Route A | Do it on the host; the container already bundles the course packages, and uv is your tool for everything outside it |
| Route B | Do it |
| You paste | The output of all four commands |
Do. Install uv, the fast, modern Python environment manager we standardize on this term, using the row for your system:
| System | Install uv | Then |
|---|---|---|
| macOS, with Homebrew | brew install uv |
Nothing; Homebrew’s directory is already on your PATH |
| macOS, Linux, or WSL2 Ubuntu | The standalone installer: the one-line curl command in the first block below the table |
Open a new terminal, or run source $HOME/.local/bin/env, so that ~/.local/bin is on your PATH |
| Windows, PowerShell | The standalone installer: the one-line powershell command in the second block below the table, or winget install --id=astral-sh.uv -e |
Open a new PowerShell window |
| Any system, through pipx | pipx install uv. pipx itself comes from brew install pipx on a Mac, sudo apt install pipx on Ubuntu or WSL2, or python -m pip install --user pipx on Windows, each followed by pipx ensurepath |
Open a new terminal, so that pipx’s ~/.local/bin is on your PATH |
| Any system, through pip | python3 -m pip install uv (PowerShell: python -m pip install uv) |
Nothing, though on Ubuntu this meets the same externally-managed-environment refusal as Step 4, so use the curl or pipx row there |
The two standalone installer lines, which do not fit in a table cell:
# macOS, Linux, or WSL2 Ubuntu
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows, in PowerShell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Confirm the install with uv --version.
pip, pipx, or uv? All three install packages from the Python Package Index, and they divide the work cleanly. pip installs a library into one particular Python, for that Python’s scripts to import. pipx installs a program written in Python (uv,
ruff,black,jupyter) into a private environment of its own and puts its command on yourPATH, so tools never fight over versions. uv does both jobs per project, and pins what it installed inpyproject.tomlso a teammate can rebuild the same environment; that reproducibility is why the course standardizes on it.
Then, in your ~/cs357 directory, create a project, an environment, and the one dependency the labs start with:
cd ~/cs357
uv init
uv venv
uv add requests
uv run python -c "import requests; print(requests.__version__)"
Note.
uv initwrites apyproject.toml, which is whatuv addrecords the dependency in; without it,uv addstops with a message about a missing project.
What you should see:
uv addprintsInstalledlines that includerequests, and the last command prints a version number such as2.32.3.
Paste. The output of all four commands.
Troubleshooting:
uv: command not foundmeans the installer’s directory is not on your PATH yet; restart the terminal, or runsource $HOME/.local/bin/env. On a Mac, ifbrew install uvcomplains that your macOS version is unsupported, runbrew updateand retry. If you cannot install uv, fall back topython3 -m venv .venv,source .venv/bin/activate, andpython3 -m pip install requests(PowerShell:python -m venv .venv,.\.venv\Scripts\Activate.ps1,python -m pip install requests), and note in your submission that you used the fallback. See the Stage 8 rows in Troubleshooting.
Next: the Part 1.5 checklist, then Part 2. The reference list below is there when you need it, not required reading.
Command-Line Survival: reference (use as needed, not required reading cover-to-cover)
| Resource | What it is for |
|---|---|
| The Shell, in Full | The course’s own shell tutorial, from the first prompt through pipes, PATH, and processes |
| tldr pages | Plain-language, example-first cheat sheets for any command (tldr tar) |
| explainshell | Paste any command line and see each flag explained |
| ShellCheck | Catches bugs in shell scripts before they bite |
curl and HTTPie plus jq |
You will hit JSON APIs (Ollama, MCP) all semester; curl ... \| jq is your friend |
You’ve succeeded when the four boxes below are checked. That is Part 1.5.
Part 1.5 Checklist
- A shell transcript showing directory creation, navigation, and a
grep/rgsearch - A
gh auth statustranscript showing you are signed in to GitHub - A
git log --onelinetranscript showing at least one commit pushed to a remote (command line or GitHub Desktop) - A
uv(or documented fallback) transcript importingrequests
Part 2: Baseline Reflection
Write approximately one page addressing all four prompts below. This is captured now so you can compare it to your thinking at the end of the semester. There are no wrong answers.
Reflection Template (use these as section headings; write a paragraph under each):
My AI Experience So Far
Describe which AI tools you use, for what purposes, and how often. Then describe one specific moment when an AI output surprised you, either because it was better than you expected, or because it failed in an unexpected way. Name the tool, describe the task, and describe the surprise.
What “Agent” Means to Me Right Now
Write your own definition of what makes a system an “agent” rather than just a program or a tool. You do not need to match any textbook definition; write what you actually think. After the semester, we will return to this and see how your thinking changed.
What I Would and Would Not Delegate
Name one task you would happily hand to an AI agent and one you would not. For each, write one or two sentences explaining the specific reason: what is it about that task that makes delegation feel appropriate or inappropriate? The difference between your two examples is more interesting than either example alone.
What I Want to Build
Describe one thing you hope to be able to build or do by the end of the semester that you cannot do today. Be as concrete as you can: what would it do, who would use it, and what would “working” look like?
Troubleshooting
Work down this table before you post in the course channel. The Stage column matches the setup map at the top of the page. If none of it helps, post the exact command you ran and its full output.
| Stage | Symptom | Likely cause | Fix |
|---|---|---|---|
| 0 | On Windows, 'ollama' is not recognized, or curl prints something odd |
You are in Command Prompt or an old PowerShell window from before the install | Open a fresh PowerShell window so the updated PATH loads, and use PowerShell for every command on this page |
| 0 | python3: command not found on Windows |
Windows Python installs as python |
Use python wherever this page says python3 |
| 0 | python3: command not found on macOS or Linux, or python opens the Microsoft Store on Windows |
Python is not installed, or the terminal window predates the install | Install it from Installing Python and pip in Part 1’s orientation, then open a new terminal. On Windows, if the Store keeps opening afterwards, turn off the python.exe and python3.exe entries under Manage app execution aliases |
| 0 | No module named pip |
Python was installed without its package installer | python3 -m ensurepip --upgrade on macOS and Windows; sudo apt install python3-pip on Ubuntu and WSL2 |
| 0 | python3 -m pip --version names a different Python than the one you just installed |
Several Pythons on the machine, and PATH finds an older one first |
which python3 (PowerShell: Get-Command python) shows which one wins; open a new terminal after the install, and always write python3 -m pip rather than a bare pip |
| 0 | On a Mac, Homebrew says your macOS version is unsupported or a pre-release, and brew install fails |
Your copy of Homebrew predates your macOS upgrade | brew update, then rerun the install. Homebrew needs macOS Sonoma (14) or later |
| A | Cannot connect to the Docker daemon |
Docker Desktop is installed but not running | Start the application. On Linux, sudo systemctl start docker, and confirm your user is in the docker group |
| A | Docker Desktop is running, but docker is not a command inside WSL2 Ubuntu |
Docker’s WSL integration is off for that distribution | In Docker Desktop, Settings -> Resources -> WSL Integration, switch the Ubuntu toggle on, Apply & Restart, then open a new Ubuntu terminal |
| A | docker compose build cannot find the Dockerfile |
A browser saved it as Dockerfile.txt, or you are not in the .devcontainer/ folder |
ls -la .devcontainer and rename the file if needed; run docker compose from inside .devcontainer/, because the .. in the compose file is relative to it |
| A | Inside the container, /workspace is empty |
On Windows, the clone lives on a drive or share Docker Desktop has not been granted, or docker compose ran from the wrong folder |
Keep cs357-work under your user profile or, better, inside your WSL2 home directory; run docker compose from .devcontainer/ |
| A | fatal: detected dubious ownership in repository at '/workspace' |
The repository’s owner (your host account) is not the user running git in the container (student) |
git config --global --add safe.directory /workspace inside the container, then rerun the command; once per session on the compose route |
| A | permission denied writing files in /workspace (Linux hosts) |
The container’s student UID does not match your host UID |
docker compose run --rm --user "$(id -u):$(id -g)" cs357 |
| A | The build fails partway with a network error | A flaky connection during the large download layers | Rerun docker compose build; completed layers are cached, so it resumes from the failed step |
| A | promptfoo refuses to start and says Node.js must be 22.22.0 or newer |
An image built before the course container pinned Node 24; Debian’s own Node package is older than promptfoo now allows | Re-fetch the current Dockerfile and rebuild, as Appendix: Updating Your Container describes. Fetching matters: the Dockerfile lives in your repository, so rebuilding without it just rebuilds the same image |
| 1 | ollama: command not found after installing |
The installer put the binary somewhere not on your PATH |
Restart your terminal. If it persists, find the binary (ls /usr/local/bin/ollama) and add its directory to PATH. This is the PATH idea from Step 0 of the Workbench session |
| 1 | ollama: command not found, and you installed Ollama with Docker |
There is no host binary on this route; the program lives inside the container | Prefix the command: docker exec ollama ollama list (docker ps confirms the container name), and add -it for the interactive ollama run |
| 1 | The model download stalls or fails partway | Network interruption on a 2 GB transfer | Rerun ollama pull llama3.2; it resumes rather than restarting |
| 1 | Out of disk space partway through the build | The course image plus models is roughly 8 to 10 GB | Clear space and rerun docker compose build; completed layers are cached and the build resumes |
| 2 | Responses are very slow | A small model on CPU-only hardware | Expected. llama3.2 is the right choice for that machine. Note the speed in your transcript; it is a real observation, not a failure |
| 3 | The curl to /api/tags says connection refused |
The Ollama server is not running, which is separate from Ollama being installed | Start the desktop app, or run ollama serve in its own terminal and leave it open |
| 4 | Inside the container, localhost:11434 refuses the connection |
Correct behavior: localhost inside a container means the container |
Use http://host.docker.internal:11434, but only for commands run inside a container; from your host, localhost stays correct. On Linux, start via the course compose file so that hostname resolves |
| 4 | ModuleNotFoundError: No module named 'requests' |
The library is not installed in the Python you are running | python -m pip install requests, then rerun the script from the same terminal |
| 4 | error: externally-managed-environment from pip install |
Ubuntu and Debian protect the system Python from pip, and you installed outside a virtual environment | python3 -m venv ~/cs357/.venv, source ~/cs357/.venv/bin/activate, then rerun the install and the script in that terminal; or use uv from Part 1.5, Step 3 |
| 4 | PowerShell: Activate.ps1 cannot be loaded because running scripts is disabled on this system |
The default execution policy blocks the activation script | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser once, then rerun .\.venv\Scripts\Activate.ps1 |
| 5 | opencode reports no provider or no models |
Almost always the config file name: it must be opencode.json, not config.json |
Fix the name, then check the location (/workspace/opencode.json in the container, ~/.config/opencode/opencode.json natively), then check that the JSON parses with python3 -m json.tool |
| 5 | opencode: command not found inside the container |
An older build of the course image, from before the agent was added | Rerun docker compose build from your .devcontainer/ folder; cached layers make it quick |
| 5 | opencode: command not found on Route B, right after the installer succeeded |
The installer put the binary in ~/.local/bin, which is not on your PATH yet |
export PATH="$HOME/.local/bin:$PATH" for this session, and add the same line to ~/.bashrc to make it stick |
| 7 | gh: command not found, or 'gh' is not recognized |
Not installed, or the terminal predates the install | Install it from the 2a table, then open a new terminal. Inside the course container it is already there; if it is not, your image predates this change and Appendix: Updating Your Container has the rebuild |
| 7 | gh auth status says you are not logged in, or a gh command returns 401 |
The sign-in lapsed or the token expired | gh auth login signs you back in. Tokens do expire, so expect this once or twice a semester rather than never |
| 7 | gh issue create or gh pr create returns 403 while gh auth status looks fine |
The token authenticates but lacks that permission; A6’s Contents-only scope does not cover issues or pull requests | gh auth refresh -s <scope> adds it, or edit the fine-grained token to include Issues and Pull requests, read and write. A 403 is a permissions answer, not a sign-in problem, which is why the status looks healthy |
| 7 | You ran gh auth login at the container prompt |
It works, and it puts a credential inside the filesystem that runs your agent code | Log out with gh auth logout, and pass GH_TOKEN in from your host shell instead, as A6 describes |
| 7 | ssh-keygen is not recognized on Windows |
OpenSSH is not installed or the window predates it | Open a new PowerShell window; if it persists, install the GitHub CLI (Step 2a) and let gh auth login make the key, or install GitHub Desktop, which bundles Git for Windows |
| 7 | git push rejected, “authentication failed” |
GitHub no longer accepts account passwords over HTTPS | gh auth login and then gh auth setup-git gives git a credential over HTTPS. Or move the remote to SSH: git remote set-url origin git@github.com:<user>/<repo>.git. A fine-grained token scoped to that one repository, with Contents: read and write, is the fallback if you must stay on HTTPS, and is the default inside the container (A6) |
| 7 | git@github.com: Permission denied (publickey) |
The key is not loaded in the agent, or its public half was never added to GitHub | gh auth status tells you what gh thinks it has. By hand: ssh-add -l lists loaded keys and ssh-add ~/.ssh/id_ed25519 loads yours; confirm the contents of id_ed25519.pub appear under Settings -> SSH and GPG keys; then retest with ssh -T git@github.com |
| 7 | GitHub Desktop says authentication failed, or cannot push | Not signed in, or the repository exists on GitHub but was never published from Desktop | File > Options > Accounts, sign in with the browser, then Publish repository; if the repository already exists online, use Add local repository and set the remote under Repository > Repository settings |
| 8 | uv: command not found |
Not installed, or not on PATH yet |
Install it from the table in Part 1.5, Step 3 (brew install uv on a Mac with Homebrew, the curl or PowerShell installer elsewhere), then open a new terminal or run source $HOME/.local/bin/env; if it still fails, use the documented python -m venv fallback and say so |
| 8 | brew install uv says your macOS version is unsupported |
Homebrew is older than your macOS | brew update, then rerun brew install uv |
| 8 | uv add complains that no pyproject.toml was found |
You skipped uv init |
Run uv init in the same directory, then rerun uv add requests |
Self-Check Before You Submit
Hold your submission against the rubric’s proficient column:
- One file, PDF or Markdown, with each component clearly labeled.
- Setup transcript covers all five steps, including the coding-agent check, and states your OS and version numbers. On Route A, it also shows the container prompt and the A5 output.
- Transcript output is copied verbatim, not retyped or paraphrased.
- Any failure is quoted exactly, with a hypothesis and what you tried.
- Part 1.5: shell navigation and a search, a
gh auth statusblock, agit log --onelineshowing a pushed commit, and the uv (or documented fallback) output. - Reflection has four labeled sections and is about a page.
- The reflection says what you actually think, not what you expect the course to want.
- Which route you took (A or B) is stated, and a Route A transcript shows the container prompt.
- Collaboration, AI-disclosure, and hours questions answered at the end.
Deliverables
Submit a single PDF or markdown file containing:
- Your tool setup transcript (all five steps, including
opencode --versionand one answered prompt, plus version and OS info; on Route A, the container prompt and the A5 output as well) - Your command-line and git checkpoint transcript (Part 1.5: navigation,
gh auth status, git commit/push, uv environment) - Your baseline reflection (one page, four sections)
Frequently Asked Questions
Q: I don’t have a machine that can run Ollama. What should I do? A: Use a lab machine or contact the instructor before the due date. Do not wait until the night before; lab access may require scheduling. Document which machine you used in your transcript.
Q: My Python API call returns an error or the model responds very slowly. Is that okay? A: Slow is okay for a small model on older hardware. An error is okay as long as you document it fully: copy the full error message, describe what you tried, and state whether it was eventually resolved. A partial success with complete documentation earns full credit for that step.
Q: The reflection prompts ask about “agency” and “trust”; do I need to use the textbook definitions? A: No. This is a baseline, and not a knowledge test. Write what you actually think before the course shapes your view. The textbook will be there later; this snapshot of your prior thinking is valuable precisely because it is unfiltered.
Appendix: Updating Your Container
One of the quieter advantages of working in a container is that your environment is a file rather than a machine. When a tool the course depends on changes underneath us, and sooner or later one always does, nobody has to reinstall anything by hand or compare laptops to work out whose setup drifted. The Dockerfile in your repository is the environment, so a corrected Dockerfile plus a rebuild puts the whole class back on an identical, working stack. When an update is announced, fetch the current file and rebuild:
cd ~/cs357-work/.devcontainer
curl -fsSL -o Dockerfile https://raw.githubusercontent.com/BillJr99/Ursinus-CS357-Fall2026/gh-pages/files/devcontainer/Dockerfile
git add Dockerfile && git commit -m "Update course container"
docker compose build
docker compose run --rm cs357
That first curl matters more than it looks. The Dockerfile lives in your repository, not in a registry somewhere, so rebuilding without fetching the new one just rebuilds the image you already have.
Expect the rebuild to take roughly as long as your original build did. A change near the top of the file invalidates every layer beneath it, so the large library downloads run again.
If you work through VS Code Dev Containers, run Dev Containers: Rebuild Container from the command palette. Plain Reopen in Container reuses the old image and will not pick up the change.
Once you are back at the container prompt, rerun the A5 checks to confirm the update landed, then run docker image prune to reclaim the disk the replaced image was still holding.
Submission
In your submission, please include answers to any questions asked on the assignment page, as well as the questions listed below, in your README file.
If you wrote code as part of this assignment, please describe your design, approach, and implementation in a separate document prepared using a word processor or typesetting program such as LaTeX. This document should include specific instructions on how to build and run your code, and a description of each code module or function that you created suitable for re-use by a colleague.
In your README, please include answers to the following questions:
- Describe what you did, how you did it, what challenges you encountered, and how you solved them.
- Please answer any questions found throughout the narrative of this assignment.
- If collaboration with a buddy was permitted, did you work with a buddy on this assignment? If so, who? If not, do you certify that this submission represents your own original work?
- Please identify any and all portions of your submission that were not originally written by you (for example, code originally written by your buddy, or anything taken or adapted from a non-classroom resource). It is always OK to use your textbook and instructor notes; however, you are certifying that any portions not designated as coming from an outside person or source are your own original work.
- Approximately how many hours it took you to finish this assignment (I will not judge you for this at all...I am simply using it to gauge if the assignments are too easy or hard)?
- Your overall impression of the assignment. Did you love it, hate it, or were you neutral? One word answers are fine, but if you have any suggestions for the future let me know.
- Using the grading specifications on this page, discuss briefly the grade you would give yourself and why. Discuss each item in the grading specification.
- Any other concerns that you have. For instance, if you have a bug that you were unable to solve but you made progress, write that here. The more you articulate the problem the more partial credit you will receive (it is fine to leave this blank).
- Please describe any use of outside resources you may have engaged in the completion of this assignment, including the use of generative Artificial Intelligence.
Assignment Rubric
| Description | Pre-Emerging (< 50%) | Beginning (50%) | Progressing (85%) | Proficient (100%) |
|---|---|---|---|---|
| Environment Setup and Verification (40%) | Little or no evidence that the environment was attempted | Some components installed, but the verification transcript is missing or incomplete | Ollama installed and verified with a transcript, with a minor omission such as a missing model listing, missing version information, or a missing coding-agent check, or the command-line and git checkpoint is incomplete | The transcript shows all four Ollama steps completed with verbatim terminal output, the output of ollama --version, ollama list showing at least one model, the curl /api/tags JSON response, and the Python script output including a non-empty "content" field, plus the fifth step, the output of opencode --version and one answered prompt from your local model, plus the operating system name and version; the command-line and git checkpoint (Part 1.5) is also complete, showing the shell-navigation commands, the output of gh auth status, a git commit/push transcript (command line or GitHub Desktop), and the uv environment creation; any failed step includes the verbatim error message, a stated hypothesis, and what was tried |
| Reflection Essay (40%) | The reflection is missing or does not address the prompts | The reflection addresses some prompts superficially without naming specific tools or moments | The reflection addresses all four sections with specific examples, but the connection between the two delegation examples is not analyzed or the "What I Want to Build" section is vague | All four sections are present and addressed with concrete specifics, a named AI tool and a described moment of surprise in "My AI Experience," a personal definition of agency distinct from any course reading in "What Agent Means to Me," a pair of delegation examples where the contrast between the two is explicitly analyzed, and a "What I Want to Build" description naming what the system would do, who would use it, and what working would look like |
| Submission (20%) | An incomplete submission is provided | The submission is provided but is disorganized, the transcript and the reflection are hard to tell apart, or one is missing | All required components are present in a single file, with a minor omission such as an unlabeled transcript section or missing OS information | A single well-organized PDF or Markdown file with each component clearly labeled, the five-step setup transcript with version and OS details, the Part 1.5 command-line and git checkpoint, and the four-section reflection, with the collaboration and time questions answered at the end |
Please refer to the Style Guide for code quality examples and guidelines.