S1 — Kickoff

Agentic Programming

Florian Oswald

13 October, 2026

Before we start

Assumption

You’ve done 00-prework.qmd: terminal works, git installed, an API key for a free-tier LLM provider, opencode installed and talking to that provider.

If any of that is broken, say so now — we fix it in the first five minutes, not during the lab.

Today’s objectives

  • Understand four words that get used loosely: model, harness, tool, skill — and why “programming” increasingly means supervising one of these systems rather than typing every line by hand.
  • Get comfortable enough in a shell to move around, make/edit/delete files, and chain commands.
  • Initialize a git repo, commit a trivial script, and run one full OpenCode session end to end — reviewing what it did before accepting it.

Part 1 — What are you actually talking to when working with an Agent?

Why bother with definitions

  • “AI” and “the model” and “ChatGPT” and “Copilot” get used interchangeably in casual conversation.
  • That’s fine at a dinner party. It is not fine when you’re debugging why an agent did something weird, or deciding what to pay for.
  • Four layers, each doing a different job:

The stack

model → harness → tool → skill

We’ll meet each one today at a “what is it” level. How each one works internally is later sessions (S4: models, S5: harnesses/tools).

1. The model

  • A large language model: weights trained to predict the next token, given everything before it.
  • That’s it — a model, on its own, is a function: text in, text out. It has no memory between calls, can’t read your disk, can’t run anything.
  • Examples you’re using right now: Gemini, GLM, Qwen, DeepSeek (whichever free-tier one you picked in S0).
  • Different models = different quality/speed/cost tradeoffs, same basic job.

Not yet

Why next-token prediction produces coherent answers — and why it sometimes confidently produces wrong ones — is S4.

curl : asking a server for replies

  • curl sends an HTTP request from your terminal to a URL and prints the response.
  • Pieces of the command:
    • https://openrouter.ai/api/v1/chat/completions — the endpoint we’re POSTing to.
    • -H "Authorization: Bearer $OPENROUTER_API_KEY" — a header proving who’s paying (your API key, read from an environment variable).
    • -H "Content-Type: application/json" — tells the server the body is JSON.
    • -d '{...}' — the request body

A bare model call - no harness

Pull OPENROUTER_API_KEY back out:

export OPENROUTER_API_KEY=$(jq -r .openrouter.key ~/.local/share/opencode/auth.json)

Now curl to openrouter:

curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-31b-it:free",
    "messages": [{"role": "user", "content": "Say hi in 3 words."}]
  }'

A bare model call - no harness

  • It can’t read your disk, can’t run ls, can’t edit a file. It replies with text, and that’s the entire interaction.
  • Everything from here — reading files, running commands, looping — is the harness’s job, not the model’s.

Try another model

Same request, just swap the "model" field — everything else is identical:

curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nvidia/nemotron-3.5-lightning:free",
    "messages": [{"role": "user", "content": "Say hi in 3 words."}]
  }'
  • Gemma gave back something like "content": "Hi there friend!" — short, no extra fields.
  • Nemotron gives back something very different.

What actually comes back

Raw JSON — no harness has prettified anything yet (full reply, scroll to see it all):

{
    "id": "gen-1791877333-LPEh3Gr6fZlW2MfHTCJN",
    "object": "chat.completion",
    "created": 1791877333,
    "model": "nvidia/nemotron-3.5-lightning:free",
    "provider": "Nvidia",
    "choices": [
        {
            "index": 0,
            "finish_reason": "stop",
            "message": {
                "role": "assistant",
                "content": "Hello there friend",
                "reasoning": "Here's a thinking process:\n\n1.  **Analyze User Input:** User says \"Say hi in 3 words.\"\n2.  **Identify Core Task:** Greet the user, but constrained to exactly 3 words.\n3.  **Determine Response Format:** A 3-word greeting.\n4.  **Draft Options:** \n   - \"Hello there, friend!\" (3 words? \"Hello there friend\" = 3, but exclamation might count or not. Better keep it simple.)\n   - \"Hi! How are you?\" (3 words? \"Hi how are\" no, \"Hi how are you\" = 4)\n   - \"Hello! Nice to meet.\" (3 words: \"Hello nice meet\" no)\n   - \"Hi there, howdy?\" (3 words? \"Hi there howdy\" = 3, but punctuation)\n   - \"Greetings! Welcome aboard.\" (3 words: \"Greetings welcome aboard\" = 3)\n   - \"Hi! I'm doing well.\" (too long)\n   - Simple: \"Hello! How are?\" no.\n   - Let's count carefully: \n     - \"Hello there friend\" = 3 words\n     - \"Hi friend how are\" no\n     - \"Greetings and salutations\" = 3 words? maybe too formal\n     - \"Hi there! Welcome\" = 3 words if we count punctuation separately\n     - \"Hello! Nice to meet\" = 3 words? \"Hello nice meet\" = 3\n     - Best approach: Just output exactly 3 words that form a valid greeting.\n   - Options: \n     - \"Hello there friend\"\n     - \"Hi there buddy\"\n     - \"Greetings welcome friend\"\n     - \"Hello! Have fun\" - no, weird.\n     - \"Hi how's it going\" - 4 words.\n     - \"Hey there pal\" - 3 words.\n     - \"Welcome! Come in\" - 3 words.\n   - I'll just do \"Hello there friend\" or \"Hi there buddy\".\n   - Check word count: \"Hello\" (1) \"there\" (2) \"friend\" (3). Perfect.\n   - I'll output exactly that. No extra text. Just the 3 words.\n   - Final: \"Hello there friend\". I'll go with that.\n   - Output: \"Hello there friend\" ✅\n   - Let's double-check: 3 words, greets the user. Perfect.\n   - I'll output exactly that. ✅"
            }
        }
    ],
    "usage": {
        "prompt_tokens": 23,
        "completion_tokens": 718,
        "total_tokens": 741,
        "cost": 0,
        "completion_tokens_details": {
            "reasoning_tokens": 713,
            "image_tokens": 0,
            "audio_tokens": 0
        }
    }
}

What actually comes back

  • The answer you wanted — "Hello there friend" — is one field. reasoning is a separate field: the model talking to itself before answering.
  • 23 tokens in, 718 tokens out, almost all of it scratch work you never asked for, to produce a 3-word reply.
  • This is a reasoning model: it generates a hidden chain of thought before committing to an answer. More tokens, more latency, more cost — for better accuracy on hard problems. Whether that trade is worth it is S5.

2. The harness

  • A model alone can’t do anything to your computer. Something has to:
    • collect context (your prompt, files, previous turns) and send it to the model,
    • parse the model’s reply,
    • actually execute what the model asked for (read a file, run a command, edit code),
    • show you the result, ask permission where configured to.
  • That “something” is the harness. In this course: OpenCode.

2. The harness

  • Claude Code, Cursor, GitHub Copilot Chat, Cline — all harnesses, wrapping different (or the same) models.

Rule of thumb

Same model, different harness → different experience, different capability, sometimes different price.

3. The tool

  • A tool is a specific action the harness lets the model invoke: “read this file,” “write this file,” “run this shell command,” “search the web.”
  • The model doesn’t do these things itself — it emits a structured request (“call read_file with path X”), and the harness executes it and returns the result.
  • This is why an agent can “see” your repo and “run” your tests: not magic, just tool calls the harness carries out on request.

4. The skill

  • A skill is a packaged, reusable set of instructions you (or someone else) hand to the agent so it doesn’t have to reinvent a procedure every time — e.g. “here’s how our team writes commit messages,” “here’s how to structure a test file in this repo.”
  • Concretely, in OpenCode/Claude-Code-style harnesses: a rules/instructions file the harness reads automatically (AGENTS.md, CLAUDE.md), or a short standalone procedure you point the agent at.
  • We’ll write one of these for real in S3 (project config). Today, just recognize the concept: skills are how you encode your own judgment so the agent doesn’t have to guess it fresh each time.

Putting it together

One sentence

You type a prompt → the harness builds context and sends it to the model → the model replies, possibly asking to invoke a tool → the harness runs the tool and loops back → optionally, a skill shaped how the model approached the whole thing.

  • “Programming with AI” in 2026 mostly means: you supervise this loop. You still need judgment about what’s correct — you just spend less time typing syntax and more time reviewing diffs.
  • That’s the whole course, really: build the habit of checking what the agent did before you trust it.

Part 2 — The Shell. And fast.

Why a shell at all

  • Agents run in a terminal. If you can’t navigate a filesystem or read a command’s output yourself, you can’t tell whether the agent did the right thing.
  • We’re not doing a full Software Carpentry pass today — a condensed version. Full reference material (if you want more drills) is linked at the end.
  • Remember the tradeoff between a GUI and a CLI.

The filesystem: root, folders, paths

  • Everything on your machine lives in one tree, starting at the root: / on Mac/Linux, C:\ on Windows.
  • A folder (= directory) is a node in that tree — it holds files and/or other folders.
  • Your home folder (~) is where your own stuff lives: /Users/you (Mac), /home/you (Linux).
  • A path is the address of a file/folder in that tree: list of folders from some starting point, separated by /.

My filesystem

> tree -L 1 /
/
├── Applications
├── bin
├── cores
├── dev
├── etc -> private/etc
├── home -> /System/Volumes/Data/home
├── Library
├── Network -> /System/Volumes/Data/Network
├── opt
├── private
├── sbin
├── System
├── tmp -> private/tmp
├── Users
├── usr
├── var -> private/var
└── Volumes
  • Mac: brew install tree
  • WSL2/Linux: sudo apt install tree

Where am I, what’s here

$ pwd          # print working directory — where am I?
$ ls           # list what's in the current directory
$ ls -l        # long form: sizes, dates
$ ls -a        # show hidden files too (dotfiles)
  • pwd and ls are your two most-used commands. Use them constantly, especially right before you let an agent touch anything.

Moving around

$ cd some-directory     # go into a subdirectory
$ cd ..                 # go up one level
$ cd ~                  # go home
$ cd -                  # go back to wherever you just were

Absolute vs relative paths

data/raw.csv is relative to where you are. /Users/you/project/data/raw.csv is absolute — works from anywhere. Agents often produce absolute paths; know the difference so a “file not found” makes sense.

Making and removing things

$ mkdir my-project          # make a directory
$ mkdir -p a/b/c             # make nested directories at once
$ touch script.py            # create an empty file
$ mv old-name.py new-name.py # rename (or move) a file
$ cp script.py backup.py     # copy a file
$ rm backup.py               # delete — no trash bin, this is permanent
$ nano script.py             # edit the file's contents (Ctrl+X to exit, saving)

Editing files

touch creates an empty file; it does not let you type into it. nano is a simple terminal text editor — good enough for today. You’ll likely move to a fuller editor (e.g. VS Code) later, but the shell/agent workflow doesn’t require one.

rm has no undo

There is no recycle bin in the shell. Double-check the path before you hit enter — and definitely before you let an agent run rm on your behalf.

Looking inside files

$ cat script.py       # print the whole file to screen
$ head -n 5 file.csv  # first 5 lines
$ tail -n 5 file.csv  # last 5 lines
$ less file.csv       # scroll interactively (q to quit)

How does an agent “search” your code?

  • When you ask an agent “where is greet defined?” or “find all TODOs,” it doesn’t magically know. It calls a tool (remember Part 1) that searches text.
  • Under the hood, that tool is almost always some flavor of grep — “global regular expression print.” Harnesses wrap it, but the primitive is this.
  • So: knowing grep yourself means you can (a) do the same search faster by hand, and (b) tell whether the agent’s “I searched and found nothing” claim is even plausible.

Try it: make a file, then search it

$ cat > notes.txt << 'EOF'
TODO: write greet() in hello.py
TODO: add a test for greet()
done: initial commit
GREETING logic lives in hello.py
EOF

Now, before looking ahead — guess what each of these prints, then run it:

$ grep "TODO" notes.txt
$ grep -i "greeting" notes.txt
$ grep -n "done" notes.txt

grep: searching inside files

$ grep "greet" hello.py            # lines in hello.py containing "greet"
$ grep -i "greet" hello.py         # case-insensitive
$ grep -n "greet" hello.py         # show line numbers
$ grep -r "TODO" .                 # recursive: search every file under .
$ grep -rl "TODO" .                # recursive, just filenames (not lines)

Why this matters for agents

“Find every place this function is called” is a grep -rn under the hood. If an agent tells you it checked the whole repo, you can run the same grep yourself in two seconds and confirm it isn’t lying or missing a file.

Combining commands: redirection and pipes

$ ls > filelist.txt      # redirect output into a file (overwrite)
$ ls >> filelist.txt     # append instead of overwrite
$ ls -l | head -n 3      # pipe: feed one command's output into the next

Why this matters for agents

Agents live and die by chaining exactly these primitives — reading a file, filtering it, writing a result. If a diff an agent proposes runs a pipeline that looks like line noise, you should now be able to read it left to right.

Want more?

The old, longer walk-throughs — Shell Intro, Files and Directories, Working with Files and Directories, Pipes and Filters — are still in the repo as optional reference material with more worked examples and challenges.

Part 3 — Lab: repo, script, one agent session

The goal

By the end of this lab you will have:

  1. A git repository on your machine.
  2. A trivial script inside it, committed.
  3. One complete OpenCode session where you asked the agent to do something, reviewed what it changed, and then committed that too.

Step 1 — make a repo

$ mkdir s1-kickoff && cd s1-kickoff
$ git init
  • git init turns the current folder into a git repository — a .git/ folder now tracks history here.
  • We are not doing a git deep-dive today (that’s S3) — just enough to commit something.

First time using git on this machine?

Git refuses to commit until it knows who you are. If git commit later complains “Please tell me who you are,” run this once (only needed the first time ever):

$ git config --global user.name "Your Name"
$ git config --global user.email "you@example.com"

Step 2 — write a trivial script, commit it

Pick one language — today, use whichever you’re most comfortable reading, Python is a reasonable default.

$ cat > hello.py << 'EOF'
print("hello from s1-kickoff")
EOF
$ python3 hello.py

Then commit it:

$ git add hello.py
$ git commit -m "initial commit: trivial hello script"

Step 3 — one full OpenCode session

$ opencode

Give it a small, checkable task, for example:

add a function `greet(name)` to hello.py that prints
"hello, <name>, from s1-kickoff" and call it once with your own name

Let it work. Do not accept blindly.

Step 4 — review before you trust it

Before committing what the agent produced:

$ git diff

Ask yourself

  • Does the diff do only what I asked, nothing more?
  • Would this code actually run? (run it: python3 hello.py)
  • Is there anything here I don’t understand? If so, ask the agent to explain the specific line — don’t just accept it.

If it looks right:

$ git add hello.py
$ git commit -m "agent: add greet() via opencode, reviewed"

If it doesn’t look right, tell the agent what’s wrong and iterate — that back-and-forth is the skill this course is teaching.

Checklist — you’re done when

Next

S2: the same “agent drafts, student verifies” habit, applied to loops/conditionals/functions in both R and Python.