EVERYTHING AIAI engineering, made visual
0/12 complete
LESSON 08 · SETUP & TOOLING × AI · BUILD

Configure it once.
Then let it work.

Your editor is your co-pilot: eight extensions, five settings, one terminal layout, and a tunnel to the GPU box. Twenty minutes of setup against twenty minutes a day — and a language server that underlines the wrong keyword argument before the expensive run starts.

20 MIN · 7 CHAPTERS + CHECKPREREQ · PHASE 0 · LESSON 01
FIG. 08 / ONE KEYSTROKE, ONE ROUND TRIP · LIVE CYCLE
keystroke out completions back diagnostic
LESSON 08TYPE · BUILD~20 MINPREREQ · PHASE 0 · LESSON 01ORIGINAL LESSON ↗
THE 60-SECOND VERSIONSee the arithmetic ↓
01 / THE ARITHMETIC OF SETUP

Twenty minutes once. Twenty minutes a day.

A misconfigured editor charges rent: no autocomplete, no type hints, no inline errors, manual formatting. The source's trade is stark — the right setup takes about 20 minutes, skipping it costs about 20 minutes every day. Twenty minutes × 250 working days is 5,000 minutes: 83.3 hours, roughly two working weeks a year, and the setup is paid back on day one.

20 × 250 = 5,000 min = 83.3 h ≈ 10.4 working days · payback: day 1
02 / FIVE LAYERS, EIGHT EXTENSIONS

The editor is a stack you climb once.

Base editor (VS Code) → extensions (Python, Pylance, Jupyter, GitLens, Remote SSH, Debugpy, Black Formatter, Ruff) → AI-specific settings → terminal integration → remote development. Each layer assumes the one below, and Remote SSH — Secure Shell, the protocol for logging into another machine — is the layer that reaches the GPU box: files, interpreter, debugger and the run stay remote while the window stays on your laptop.

code --install-extension … × 8 · then the settings × 5
03 / FIVE SETTINGS THAT EARN THEIR KEEP

Configure the failures AI code actually has.

typeCheckingMode: basic flags the wrong keyword argument (epoch=3 where the signature says epochs) before the run; formatOnSave lets Black rewrite a 102-character line into the wrapped block at 88 columns; rulers [88, 120] make line length visible; notebook.output.scrolling keeps the last 500 of 12,480 training lines; files.autoSave stops a stale file from starting a run.

"python.analysis.typeCheckingMode": "basic" · "editor.rulers": [88, 120]
MENTAL MODEL IN ONE SENTENCE

An AI editor setup is a stack of five layers — base editor, extensions, settings, terminal, remote — and the whole job is to configure it once so the tool stops being the bottleneck: type checking for the mistakes that would otherwise wait for runtime, formatting and auto-save so the file on disk is always the file you meant, scrolling and scrollback for the output that training runs produce, and a tunnel for the hardware that lives somewhere else.

By the end you will be able to install VS Code and the eight extensions from one paste and verify them with code --list-extensions; explain what each extension buys and which layer it belongs to; paste the five settings and say what each one prevents — the epoch=3 squiggle, the 102-character line, the invisible column-120 overflow, the 12,480-line output cell, the stale file; set up the integrated terminal with a 10,000-line scrollback and split panes for watch -n1 nvidia-smi; reach a GPU box with ssh-keygen -t ed25519, ssh-copy-id and a ~/.ssh/config entry, then open it with Remote-SSH: Connect to Host; and place Cursor, Windsurf and Neovim against this setup without losing the twenty minutes you just spent.

THE 20-MINUTE TRADE

Twenty minutes once.
Twenty minutes every day.

The source states the whole lesson in two sentences: you will spend thousands of hours inside an editor, and a misconfigured one turns every session into friction — no autocomplete, no type hints, no inline errors, manual formatting, a clunky terminal. The right setup takes 20 minutes; skipping it costs 20 minutes a day.

That sentence is a trade, so let us do the trade arithmetic. A working year, counted as five days a week for fifty weeks, is 250 days. Twenty minutes lost each of those days is 20 × 250 = 5,000 minutes, which is 83.3 hours — about 10.4 eight-hour working days, or two working weeks a year, spent inside a tool that could have been configured before lunch.

setup cost 20 minutes, once daily friction 20 minutes × 250 working days = 5,000 minutes 5,000 ÷ 60 = 83.3 hours 83.3 ÷ 8 = 10.4 working days ≈ two working weeks payback 20 ÷ 20 = 1 day at 5 min/day 20.8 h a year · paid back in 4 days at 10 min/day 41.7 h a year · paid back in 2 days at 20 min/day 83.3 h a year · paid back in 1 day at 30 min/day 125.0 h a year · paid back the same afternoon

Where do twenty minutes a day actually go? The source names four frictions: no autocomplete, no type hints, no inline errors, manual formatting. Here is one plausible split of a single day — illustrative, not measured, but every line of it is the kind of minute you will recognise:

4 min looking a function signature up in the docs because the editor cannot show it 6 min a name or shape mistake found at runtime instead of at the keystroke 3 min hand-fixing line length and spacing 4 min the wrong interpreter, or a venv the editor never detected 3 min terminal friction: no split panes, copying paths ───── 20 min one day, and the same twenty tomorrow

Notice what that arithmetic does not include: the expensive version of the same mistakes. A stale file because you forgot to save starts a run with yesterday’s code. A typo in a keyword argument survives until the data has loaded and the first batch is on the GPU. At an invented example rate of $2.34 per GPU-hour, one three-hour run that dies at minute 90 wastes about 1.5 × 2.34 ≈ $3.51; avoid fifty of those in a year and the squiggle in the margin is worth roughly $175. The price is a label for the arithmetic, not a quote — the point is that editor mistakes and GPU mistakes are the same mistakes, priced differently.

Here is the machinery behind all four frictions. The editor does not know Python; it knows how to talk to something that does. That something is a language server — for Python in VS Code, Pylance — and the conversation follows a standard called the Language Server Protocol (LSP): the editor asks for completions, type information or diagnostics, the server answers, and neither side needs to know how the other is built. That split is why a Python language server (Pylance where it runs; pyright or pylsp in Neovim) can serve VS Code, Cursor and Neovim, and why the lab below is a two-panel conversation rather than a single box.

The LSP round trip, in miniature

Type a name and watch the editor ask a language server for completions and type hints. Then switch the checker off: the same wrong call keeps its squiggle-free life until the script runs. This is a simulation of the protocol’s shape — the symbol table and the 42 ms latency are teaching stand-ins, not a real Pylance session.

typed emb completions embeddings, embedding_dim, embed_batch hover no symbol under the cursor diagnostics 0 editor line 6 model.fit(x_train, y_train, epochs=3) with the checker on the server compares the call against fit(x, y, epochs: int = 1) and publishes the mismatch as a squiggle before the run

One keystroke, one round trip: the editor owns the window and the keystrokes, the server owns the analysis. That split is why a Python language server (Pylance where it runs; pyright or pylsp in Neovim) can serve VS Code, Cursor and Neovim.

The 20-minute trade, worked out

The source’s claim is one sentence: the right setup takes 20 minutes, and skipping it costs 20 minutes every day. Here is the arithmetic behind that sentence — move the slider and watch the year change. The daily figure is an illustrative split of the four frictions the source names; the 250-day working year is a convention, not a measurement.

83 hper year at 20 min/day · 5,000 minutes · 10.4 working days (8 h each)
ONE-OFF SETUP20 minutes, once — the source’s estimate. That is 0.18% of the year’s friction at 45 min/day.
A YEAR WITHOUT IT83 h at 20 min/day — paid for in 1.0 day.
THE SAME ARITHMETIC AT SIX DAILY FIGURES
0 min/day0.0 h · 0.0 d
5 min/day21 h · 2.6 d
10 min/day42 h · 5.2 d
20 min/day83 h · 10.4 d
30 min/day125 h · 15.6 d
45 min/day188 h · 23.4 d
input 20 minutes lost per day working year 250 days setup 20 minutes, once minutes per year 5,000 min hours per year 83 h working days 10.4 days of 8 hours payback 20 ÷ 20 = 1.0 day to break even the four frictions the source names 1 no autocomplete — retype names and signatures from docs 2 no type hints — shapes and parameter names guessed 3 no inline errors — mistakes found when the script runs 4 manual formatting — style dealt with by hand, per file what the year means at 20 min/day 83 h ≈ 10.4 working days ≈ two working weeks the setup is paid back on day 1

The useful part is not the exact number — it is the shape: a fixed cost of minutes against a recurring cost of minutes. Anything recurring wins the comparison, which is why the source says to spend the 20 minutes now.

Quick check

The source says skipping the setup costs 20 minutes every day. What is that 20 minutes actually made of?

FIVE LAYERS, BOTTOM-UP

An AI editor setup
is a stack, not a download.

The source draws the whole setup as one graph: remote development sits on terminal integration, which sits on settings, which sit on extensions, which sit on the base editor. Each layer assumes the one below it — which is exactly why installing extensions onto an unconfigured editor leaves most of the value on the table.

5. Remote developmentSSH into GPU boxes and cloud VMs — the project folder lives on the serverms-vscode-remote.remote-ssh4. Terminal integrationrun scripts, debug, monitor the GPU — shell inside the editorCtrl+` · split panes · 10,000 lines of scrollback3. AI-specific settingsauto-format, type checking, rulers at 88 and 120the five keys in settings.json2. ExtensionsPython, Pylance, Jupyter, GitLens, Remote SSH, Debugpy, Black, Ruff8 extensions, one paste to install1. Base editorVS Code — free, extensible, universal, first-class Jupyter supportcode.visualstudio.com · verifies with code --versionEACH LAYER ASSUMES THE ONE BELOW
The source’s mermaid graph, redrawn as five stacked plates. The order is a dependency order, not a preference: settings only mean something once the extensions they belong to are installed, and remote development over SSH (Secure Shell) only helps once there is a terminal worth running on the other side.

Layer 1 — the base editor. The source recommends VS Code, and gives four reasons: it is free, it runs on every OS, it has first-class Jupyter notebook support, and its extension ecosystem covers everything an AI workflow needs. Nothing in this lesson is about taste; it is about the ecosystem and the notebook support being where the rest of the course already lives.

Layer 2 — the extensions. Eight of them, and the source’s table maps each to a job: Python (language support, virtual-env detection, run/debug), Pylance (type checking, autocomplete, import resolution), Jupyter (notebooks and the variable explorer), GitLens (who changed what, inline blame), Remote SSH (a folder on a GPU box as if it were local), Debugpy (step-through debugging), Black Formatter (auto-format on save) and Ruff (fast linting). Without layer 2, layer 3 has nothing to configure.

Layer 3 — the AI-specific settings. Five keys, chosen because the work is Python that runs for hours: type checking on basic, format on save, rulers at 88 and 120, notebook output scrolling, and auto-save. This is the layer where the editor stops being a text box and starts knowing things about your code.

Layer 4 — terminal integration. Training scripts, environment management and GPU monitoring all happen in a shell, so the shell lives inside the editor: a default profile per OS, a readable font size, 10,000 lines of scrollback, and split panes — one for the run, one for watch -n1 nvidia-smi.

Layer 5 — remote development. The layer that matters most for AI work and the one no screenshot can show: over SSH (Secure Shell, the encrypted protocol for logging into another machine) the project folder, the interpreter, the GPU and the debugger live on a remote machine, while the window you type into stays on your laptop.

INSTALL AND EXTEND

Install the base.
Then the eight.

Two downloads and one paste. The editor comes from code.visualstudio.com; the extensions come from eight commands you can run in one go, because VS Code ships a command-line interface (CLI) called code that does the clicking for you.

Step 1 — the editor. Download VS Code from code.visualstudio.com and install it. The source’s reasons are worth repeating because they are why this course standardises on it: free, every OS, first-class Jupyter notebook support, and an extension ecosystem that covers AI work. Then verify that the command-line tool exists — because every subsequent step uses it:

Verify the CLIbash
code --version

# expected shape (your version will differ):
# 1.9x.x
# <commit-hash>
# x64
On macOS, if code is not found: open VS Code, press Cmd+Shift+P, type “Shell Command”, and select “Install 'code' command in PATH”. That is the whole fix, and it is the first thing the command palette is for.

Step 2 — the extensions. Open the integrated terminal inside VS Code with Ctrl+` — the same chord on every platform — and paste the eight commands. This is the moment the setup starts paying: eight marketplace searches, eight Install clicks and eight reload prompts collapse into one paste of code --install-extension lines.

The eight essentials, in one pastebash
code --install-extension ms-python.python
code --install-extension ms-python.vscode-pylance
code --install-extension ms-toolsai.jupyter
code --install-extension eamodio.gitlens
code --install-extension ms-vscode-remote.remote-ssh
code --install-extension ms-python.debugpy
code --install-extension ms-python.black-formatter
code --install-extension charliermarsh.ruff
Straight from the source's Step 2. Each line is one extension id — publisher.name — and each install is a download of a few megabytes; the eight together are minutes, not an afternoon (timings vary with your connection).

What each one buys, in the source’s own framing:

ExtensionWhat it buys youWhy it exists in an AI setup
PythonLanguage support, virtual-env detection, run/debugIt finds the interpreter you built in the environments lesson, so the editor and the terminal agree about which Python you mean
PylanceFast type checking, autocomplete, import resolutionThe language server behind the round trip in chapter 01: it answers completion, hover and diagnostic requests
JupyterRun notebooks inside VS Code, variable explorerNotebook lessons stay in one window, with a variable inspector beside the cells
GitLensSee who changed what, inline git blameA strange line in a training script gets an author, a date and a commit message instead of a shrug
Remote SSHOpen a folder on a remote GPU box as if it were localThe most important extension for AI work — chapter 06 is entirely about it
DebugpyStep-through debugging for PythonBreakpoints and a call stack inside the training loop, instead of print statements you forget to remove
Black FormatterAuto-format on save, consistent styleStyle stops being a topic in review; Black wraps at 88 columns, which is where the first ruler sits
RuffFast linting, catches common mistakesUnused imports, shadowed names and other habits are flagged (and often fixed) as you save

The source ships a longer recommendations file, code/.vscode/extensions.json, and VS Code will offer to install it when you open the project folder. It lists the eight above plus five optional entries: ms-vscode-remote.remote-containers (Docker, which has its own lesson), the Jupyter helpers ms-toolsai.vscode-jupyter-cell-tags and ms-toolsai.vscode-jupyter-slideshow, and redhat.vscode-yaml plus tamasfe.even-better-toml for config files. Optional means optional — nothing in the course is blocked without them.

code/.vscode/extensions.json — the source's full listjsonc
{
    "recommendations": [
        "ms-python.python",
        "ms-python.vscode-pylance",
        "ms-toolsai.jupyter",
        "ms-python.debugpy",
        "ms-python.black-formatter",
        "charliermarsh.ruff",
        "eamodio.gitlens",
        "ms-vscode-remote.remote-ssh",
        "ms-vscode-remote.remote-containers",
        "ms-toolsai.vscode-jupyter-cell-tags",
        "ms-toolsai.vscode-jupyter-slideshow",
        "redhat.vscode-yaml",
        "tamasfe.even-better-toml"
    ]
}
Committed to the project, this file is a standing invitation: anyone who opens the folder gets the same prompt, so a team shares one editor setup without a wiki page.

Extension matcher

Eight jobs an AI-engineering session really has, and the eight extensions from the source. Match them; every correct pair explains what that extension bought, and every wrong pair says what the extension actually does instead.

JOBS TO FILL · CLICK ONE, THEN THE OWNER

Jobs matched: 0 / 8 · wrong picks: 0

matched 0 / 8 attempts 0 selected flag a wrong keyword argument before the script runs Pick a job on the left, then the extension that owns it. why these eight base ms-python.python interpreter, run, debug brain ms-python.vscode-pylance completions, hover, type checks notes ms-toolsai.jupyter notebook cells + variable explorer blame eamodio.gitlens who changed this line door ms-vscode-remote.remote-ssh the GPU box as a folder steps ms-python.debugpy breakpoints in the training loop pen ms-python.black-formatter rewrites on save, 88 columns rules charliermarsh.ruff lint, fast

Four of these are conveniences and four are load-bearing: Python and Pylance are the floor, Black and Ruff are the style pair, Debugpy is what a traceback cannot give you, and Remote SSH is the one that reaches the GPU.

Quick check

Your editor shows a red squiggle for a wrong argument, but saving never rewrites your code. Which of the eight extensions is missing?

THE SETTINGS THAT MATTER

Five keys.
Each one fixes a real annoyance.

Extensions add abilities; settings decide how those abilities behave. The source picks five, and every one of them answers a complaint you would otherwise have at 2 a.m.: unchecked types, hand-made formatting, invisible line length, exploding notebook output, and code that runs from a file you forgot to save.

Settings live in JSON, and there are two scopes. User settings belong to your machine — open the command palette with Cmd+Shift+P (or Ctrl+Shift+P) and choose “Preferences: Open User Settings (JSON)”. Workspace settings live in .vscode/settings.json inside the project and travel with it; that is the file the source ships, and the one the rest of this chapter quotes. The format is JSONC — JSON with comments allowed — so you may leave notes to your future self in the file.

The five settings that matter, verbatim from the sourcejsonc
{
    "python.analysis.typeCheckingMode": "basic",
    "editor.formatOnSave": true,
    "editor.rulers": [88, 120],
    "notebook.output.scrolling": true,
    "files.autoSave": "afterDelay"
}
Five lines. Everything else in the source's settings.json is comfort — whitespace trimming, excluded folders, Git auto-fetch.

1 · python.analysis.typeCheckingMode: “basic” — the highest-value line in the file. Pylance compares what you write against what it knows: the function’s signature, the types on the arguments, the names of the keyword parameters. A call that says epoch=3 where the signature says epochs: int = 1 is flagged at the keystroke, not after the data loader has finished. The setting has three values — off, basic and strict — and the source chooses the middle one on purpose: strict mode on an untyped research script produces so much noise that people turn checking off entirely, which is the one outcome worse than basic.

2 · editor.formatOnSave: true — every save runs the formatter, which the source pins to Black with "black-formatter.args": ["--line-length", "88"]. In this lesson’s playground the long line is 102 characters, so Black rewrites it into a wrapped block the first time you save. The workspace-level benefit is larger than the aesthetic one: when everyone’s file is formatted by the same tool, a diff shows what changed in the code instead of who prefers which spacing.

3 · editor.rulers: [88, 120] — two vertical guides. Black wraps code at 88, so the first ruler is the “this line will be rewritten” marker. The second, at 120, marks the outer limit for the lines Black does not reflow: comments and docstrings. The lesson’s comment line is 125 characters — code under 88, prose past 120, and both facts visible at a glance instead of in review.

4 · notebook.output.scrolling: true — a training loop prints one line per step. Ten epochs of 1,248 steps is 12,480 lines in a single output cell; at roughly 48 bytes per line that is about 585 KiB of text the notebook would otherwise render at once. Scrolling, plus notebook.output.textLineLimit: 500, shows the last 500 of those lines — about 23 KiB, a twenty-fifth of the text — while the rest stays available on the scrollbar.

5 · files.autoSave: “afterDelay” — with files.autoSaveDelay: 1000, the file on disk catches up with the buffer one second after you stop typing. This is the setting that prevents the classic AI-lab failure: you edit the training script, switch to the terminal, launch the run, and discover an hour later that the process read the version from before your edit. Auto-save makes “the file on disk is current” the default instead of a habit.

Settings playground

Five switches from the source’s settings.json. Make an edit, save it, and watch each setting change what the editor does: Black rewrites the 102-column line, the rulers appear at 88 and 120, the checker flags epoch=3, the output panel scrolls, and auto-save writes the file before you remember to.

editor.formatOnSave true editor.rulers [88, 120] python.analysis.typeCheckingMode "basic" notebook.output.scrolling true files.autoSave "afterDelay" buffer matches disk line 6 length 102 columns · ruler at 88 crossed comment length 125 columns · ruler at 120 crossed diagnostics 1 · No parameter named 'epoch' output panel last 500 lines ≈ 23 KiB of ≈ 585 KiB last event settings.json is loaded — all five keys are on, matching the source. numbers the prose quotes 12,480 lines = 10 epochs × 1,248 steps 48 B/line estimate → 585 KiB for the run, 23 KiB kept

The pair to remember is typeCheckingMode: “basic” and formatOnSave: true: one catches the mistake before the expensive run, the other stops the diff from being about whitespace. Everything else here is comfort.

The source's full settings.json, line by line

The five keys above are the argument; this is the whole file, for when you want to paste it as it was written. The extras fall into four groups: Python analysis niceties (auto-import completions, inlay hints for return types), editor hygiene (tab size 4, insert spaces, trim trailing whitespace, insert a final newline), performance and noise control (no minimap, excluded caches such as __pycache__ and .pytest_cache, an explorer that hides .venv and node_modules), and git conveniences (git.autofetch, git.confirmSync, GitLens code lenses off to reduce clutter).

{ "python.analysis.typeCheckingMode": "basic", "python.analysis.autoImportCompletions": true, "python.analysis.inlayHints.functionReturnTypes": true, "python.analysis.inlayHints.variableTypes": false, "[python]": { "editor.defaultFormatter": "ms-python.black-formatter", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": "explicit" } }, "black-formatter.args": ["--line-length", "88"], "ruff.lint.run": "onSave", "editor.rulers": [88, 120], "editor.tabSize": 4, "editor.insertSpaces": true, "editor.renderWhitespace": "trailing", "editor.bracketPairColorization.enabled": true, "editor.stickyScroll.enabled": true, "editor.minimap.enabled": false, "files.autoSave": "afterDelay", "files.autoSaveDelay": 1000, "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, "files.exclude": { "**/__pycache__": true, "**/.ipynb_checkpoints": true, "**/*.pyc": true, "**/.pytest_cache": true, "**/.mypy_cache": true, "**/.ruff_cache": true }, "notebook.output.scrolling": true, "notebook.output.textLineLimit": 500, "notebook.cellToolbarLocation": "right", "terminal.integrated.scrollback": 10000, "terminal.integrated.fontSize": 13, "terminal.integrated.defaultProfile.osx": "zsh", "terminal.integrated.defaultProfile.linux": "bash", "git.autofetch": true, "git.confirmSync": false, "gitlens.codeLens.enabled": false, "search.exclude": { "**/.venv": true, "**/node_modules": true, "**/__pycache__": true, "**/dist": true, "**/*.egg-info": true } }

One detail worth noticing: the format-on-save rule is scoped to [python] and names Black explicitly, so the setting only touches Python files and never fights another language’s formatter. editor.rulers, by contrast, is global — the 88-column guide follows Black’s default line length, but the 120 marker is useful in every language.

Quick check

Rulers are set to [88, 120]. Why two numbers instead of one?

THE TERMINAL IS PART OF THE EDITOR

One pane runs the script.
The other watches the GPU.

Training scripts, uv pip install, environment switches and GPU monitoring all happen in a shell. The source keeps that shell inside the editor and configures three things: the default profile, a readable font size, and ten thousand lines of scrollback.

The integrated terminal opens with Ctrl+` — the same chord on every platform, which is why the source prints it identically in both columns of its shortcut table. What it saves is the context switch: no separate terminal window to alt-tab into, no re-typing the path you just clicked in the explorer, and a shell that inherits the project’s working directory automatically.

Terminal settings from the sourcejsonc
{
    "terminal.integrated.defaultProfile.osx": "zsh",
    "terminal.integrated.defaultProfile.linux": "bash",
    "terminal.integrated.fontSize": 13,
    "terminal.integrated.scrollback": 10000
}
zsh is the default shell on macOS and bash the usual one on Linux, so the editor opens the same shell you already know. Font size 13 is chosen for reading log lines, not for fitting more of them.

Why 10,000 lines of scrollback? Because the line you need is always the one that scrolled away. A training run prints a line per step, an nvidia-smi watcher prints a table every second, and the stack trace you care about appears once, above thousands of loss lines. Ten thousand lines at a rough 80 bytes each is about 780 KiB of terminal history held in memory — an estimate, and a cheap price for being able to scroll back to the first NaN without re-running anything. The default is far smaller, and people discover the limit exactly once: when the line they needed was silently discarded.

Why split panes? A training run occupies its terminal until it finishes; a GPU monitor occupies its own forever. Splitting gives each one a pane, so the run keeps printing while the GPU stays visible — and you catch the moment utilisation collapses instead of finding out from a wall-clock time. The source offers two monitors: watch -n 1 nvidia-smi, which re-runs the command every second, and nvidia-smi -l 1, where the card loops on its own. Either way, the useful columns are the same three: the GPU name, memory used out of total, and utilisation as a percentage of the last second.

The two-pane layout, in practicebash
# pane 1 — the run                       # pane 2 — the hardware
$ python train.py --epochs 10            $ watch -n 1 nvidia-smi

epoch 1/10 · step 0    · loss 2.3104     Every 1.0s
epoch 1/10 · step 100  · loss 1.8821     NVIDIA A100-SXM4-40GB
epoch 1/10 · step 200  · loss 1.4470     MiB / 40960MiB   38211MiB
epoch 1/10 · step 300  · loss 1.1904     GPU-Util  94 %   ▶ the number
...                                      to watch: if it drops to 0 %
                                         while the run is alive, the GPU
                                         is waiting on data, not training
A composed transcript, but the shape is real: the left pane is your job, the right pane is the hardware it is spending. Utilisation near zero with a live process means the bottleneck moved to the data loader.

The other two chords in the source’s table create and split terminals, and the split is the one that differs between keyboards:Cmd+\ on macOS, Ctrl+Shift+5 on Linux and Windows. If you forget, the command palette is the escape hatch — type “split terminal” and it shows the chord you could have pressed.

ActionmacOSLinux / WindowsWhen you reach for it
Toggle terminalCtrl+`Ctrl+`The run finished; you want your editor height back
New terminalCtrl+Shift+`Ctrl+Shift+`A second shell for a different environment
Split terminalCmd+\Ctrl+Shift+5The source’s layout: run on one side, GPU on the other

The shortcut trainer

Six moves that pay for themselves daily. Pick the chord for the keyboard you are on; wrong answers show the right one and why it matters. The two terminal chords that are identical everywhere sit next to the one that is not.

Save the file
Quick-open a file by name
Command palette
Toggle the integrated terminal
New terminal
Split the terminal

0 / 6 correct · 0 answered · macOS layout

The whole board — macOS and Linux/Windows.
ActionmacOSLinux / Windows
Save the fileCmd+SCtrl+S
Quick-open a file by nameCmd+PCtrl+P
Command paletteCmd+Shift+PCtrl+Shift+P
Toggle the integrated terminalCtrl+`Ctrl+`
New terminalCtrl+Shift+`Ctrl+Shift+`
Split the terminalCmd+\Ctrl+Shift+5
layout macOS · Cmd is the primary modifier correct 0 / 6 answered 0 the pattern worth memorising Cmd ↔ Ctrl is the only difference for most chords toggle terminal Ctrl+` the same on both keyboards new terminal Ctrl+Shift+` the same on both keyboards split terminal Cmd+\ vs Ctrl+Shift+5 the one that bites command palette where "Install 'code' command in PATH" lives why the split matters terminal 1: python train.py terminal 2: watch -n1 nvidia-smi the run keeps printing while the GPU stays visible

On a Mac the shortcut symbols are rarely printed in documentation; the command palette is the escape hatch — type the command (“Remote-SSH: Connect to Host”) and it shows the chord you could have pressed.

THE GPU BOX IS A FOLDER

Edit on the laptop.
Run on the machine.

The source calls this the most important extension for AI work, and the reason is arithmetic: training happens where the GPU is — cloud VMs, lab servers, rented boxes — while the person typing sits somewhere else. Remote SSH collapses that distance into a folder name.

Without it, remote work is a loop of copying files: edit locally, upload, run over SSH, paste the traceback back, edit locally again. With it, the remote filesystem appears in the explorer, the remote Python interpreter answers the language server, the formatter runs next to the code it rewrites, the debugger pauses the remote process, and the terminal you type into is a shell on the box. The window, the cursor and your keystrokes stay on your laptop; everything else moved.

Setup, in four steps — the source’s Step 5, unchanged:

  1. Install the Remote SSH extension (it is in the eight above).
  2. Press Cmd+Shift+P — or Ctrl+Shift+P — and type “Remote-SSH: Connect to Host”.
  3. Enter user@your-gpu-box-ip.
  4. VS Code installs its server component on the remote machine automatically on first connect.

That works with a password. For passwordless access — the version you want for a box you connect to every day — the source adds two commands and one config file. The key has two halves: a private key that never leaves your laptop (here ~/.ssh/id_ed25519) and a public key (~/.ssh/id_ed25519.pub) that ssh-copy-id appends to the remote account’s ~/.ssh/authorized_keys. After that, the remote recognises you without a prompt, and Remote SSH connects instantly.

Passwordless access: generate, then copybash
ssh-keygen -t ed25519 -C "you@example.com"
ssh-copy-id user@your-gpu-box-ip
ed25519 is the modern key type the source chooses; -C adds a comment so you can tell keys apart later. ssh-keygen writes the pair into ~/.ssh/, and ssh-copy-id installs the public half on the remote account.
~/.ssh/config — names instead of addressesini
Host gpu-box
    HostName 203.0.113.50
    User ubuntu
    IdentityFile ~/.ssh/id_ed25519
    ForwardAgent yes
The source's example. 203.0.113.50 is not a real host — it sits in 203.0.113.0/24, the address block reserved for documentation by the IETF standard RFC 5737 (TEST-NET-3). With this entry in place, “Remote-SSH: Connect to Host → gpu-box” connects instantly.

The Host gpu-box line is a nickname: it is what you type in the explorer, in ssh gpu-box, and in every other command. ForwardAgent yes forwards your SSH agent so that, once you are on the box, further SSH hops (pulling a private repository, for example) can use the same key without copying it to the remote disk.

Remote SSH, from both ends

This is the source’s most important extension for AI work: a folder on a GPU box opened as if it were local. Pick an action and watch what crosses the tunnel. Then switch the host to this laptop and see the same session without the GPU — the difference the extension buys is hardware, not convenience.

host gpu-box · 203.0.113.50 (documentation address) action connect — key handshake, then the server component installs itself handshake ssh-keygen + ssh-copy-id + ~/.ssh/config Host entry, then Remote-SSH: Connect to Host setup, in order 1 ssh-keygen -t ed25519 -C "you@example.com" 2 ssh-copy-id user@your-gpu-box-ip 3 Host gpu-box · HostName 203.0.113.50 · User ubuntu · IdentityFile ~/.ssh/id_ed25519 4 Cmd+Shift+P → "Remote-SSH: Connect to Host" → gpu-box what runs where local render the window, cursor and keypresses; carry keystrokes and screen updates over SSH remote read and write train.py; index the project for completions; format the file on save; run the training loop; allocate CUDA memory and step the optimizer what "as if it were local" really means the window never moves; the working directory does your laptop can sleep without killing the run the GPU is one connect away, not one upload away

The files never leave the box: opening train.py reads it there, saving writes it there, and the terminal you type into is a shell on the remote machine.

Two things about the remote split are worth internalising. First, extensions run remotely too: Pylance indexes the remote project, Black formats the remote file, the debugger attaches to the remote process. That is why a remote session takes a moment to set up on first connect — the server side has to be provisioned — and why the extensions you installed locally appear on the remote end of the session. Second, the run is not your laptop’s problem: the training process lives on the box, so closing the lid or losing Wi-Fi does not kill it. Reconnect and the terminal shows what happened while you were away.

CURSOR, VIM, OR VS CODE

The editor is a choice.
The setup is not.

The source closes with the alternatives, and the reason they can be covered in a page is that they are mostly the same page: Cursor and Windsurf are VS Code forks, so this lesson’s extensions and settings transfer unchanged. The one genuinely different route — Neovim — comes with an honest warning.

A fork is a copy of a codebase that then evolves on its own. Cursor and Windsurf are VS Code forks with AI generation added, which has two consequences that matter here. The first is compatibility: because they inherit the extension host and the settings schema, the eight extensions install, .vscode/settings.json is read, and Remote SSH works the same way. The second is that the material in this lesson is not a VS Code lesson at all — it is an editor-workflow lesson, and it survives the switch.

RouteWhat it shares with this lessonWhat it changesWho it is for
VS Code
the course default
Everything: the eight extensions, the settings file, Remote SSH, the Jupyter integration.Nothing — this is the reference setup the other two are compared against.Beginners, and anyone who wants the largest pool of documentation and answers.
Cursor
VS Code fork with built-in AI generation
The extension ecosystem and the settings format. Import the same settings.json and extensions.json.Adds inline AI code generation on top of the editor; the AI panel becomes another way to write the same Python.People who already know VS Code and want model-completion in the loop from day one.
Windsurf
another AI-first fork
Same story: same extensions, same settings format, same Remote SSH support.Its own AI-first surface with its own opinions about agentic edits; the underlying editor remains a fork.The same audience as Cursor; the choice between them is taste and tooling, not architecture.

Neovim is the real alternative. If you already live in it, the source says to stay — and lists the minimum for AI Python work. Five pieces, each doing one of the jobs the eight extensions do in VS Code:

Neovim pieceThe job it doesThe VS Code equivalent
pyright or pylspType checking and language intelligencePylance (which is built on Pyright)
nvim-lspconfigWires a language server into the editorThe LSP client inside every modern editor
jupyter-vim or molten-nvimNotebook-like execution of cellsThe Jupyter extension
telescope.nvimFile and symbol searchQuick open and the search panel
none-ls.nvim with black and ruffFormatting and linting on saveBlack Formatter plus Ruff

Notice the shape of that table: the same five jobs, different packaging. Which is the deeper point about LSP — the editor is a client, the language server is a separate process, and the protocol between them is standardised. That is why a Python language server (Pylance where it runs; pyright or pylsp in Neovim) can serve VS Code, Cursor and Neovim with the same answers, and why moving editors does not mean learning a new type checker.

Then the source’s advice, which is worth quoting rather than paraphrasing: “If you do not already use Vim, do not start now. The learning curve will compete with learning AI engineering.” That is not a judgement about Vim. It is the same 20-minute arithmetic from chapter 01 with different numbers: a week spent learning modal editing and plugin configuration is a week not spent learning tensors, and the setup’s whole purpose is to stay out of the way.

Quick check

A friend says you should switch to Cursor because it is a different ecosystem. What is the honest correction?

CHECK YOURSELF

Five questions.
Then the terms worth keeping.

Answer before you look. The LSP question and the Remote SSH question are the two that separate “I installed some extensions” from “I know what each layer of this setup is for” — and the notebook-output question is the one people get wrong until a training loop prints twelve thousand lines into one cell.

0 / 5 answered · 0 correct

01What is a Language Server Protocol (LSP)?

02Why is format-on-save useful for team projects?

03Which VS Code extension enables editing code on a remote GPU machine as if it were local?

04Why should 'notebook.output.scrolling' be enabled in VS Code settings for AI work?

05What does setting 'python.analysis.typeCheckingMode' to 'basic' in VS Code accomplish?

Key terms, demystified

Click a card to swap the lazy description for what it actually means.

Exercises from the lesson

Four problems with exact settings and commands — install everything, paste the settings, prove that the checker and the formatter are working, and set up Remote SSH if you have a machine to reach. Try first; a worked answer is one click away.

  1. Install VS Code and all eight extensions listed in the source's Step 2 (Exercise 1). Then prove the install from the terminal instead of trusting the UI.
    Show one worked answer

    Install VS Code from code.visualstudio.com, open the integrated terminal with Ctrl+` (yes, before anything is configured — the chord is the same on every platform), and run: code --version. Expected shape: a version line, a commit hash and an architecture — this proves the code CLI exists, which is what every following step uses. If the command is not found on macOS, press Cmd+Shift+P, type “Shell Command”, and choose “Install 'code' command in PATH”. Then paste the eight commands from Step 2 and verify with code --list-extensions: the output must contain ms-python.python, ms-python.vscode-pylance, ms-toolsai.jupyter, eamodio.gitlens, ms-vscode-remote.remote-ssh, ms-python.debugpy, ms-python.black-formatter and charliermarsh.ruff — eight lines, one per job in the table. A shortcut worth knowing: opening the project folder makes VS Code offer the same list from .vscode/extensions.json, which is how a teammate gets an identical editor without reading this lesson.

  2. Copy the source's settings.json into your VS Code config (Exercise 2), then explain which of the five load-bearing keys changes the editor's behaviour most, and which one is easiest to forget.
    Show one worked answer

    Open the command palette (Cmd+Shift+P / Ctrl+Shift+P), choose “Preferences: Open User Settings (JSON)” for a personal file, or create .vscode/settings.json in the project for a shared one, and paste the five keys — python.analysis.typeCheckingMode: "basic", editor.formatOnSave: true, editor.rulers: [88, 120], notebook.output.scrolling: true, files.autoSave: "afterDelay" — plus the rest of the source's file if you want the conveniences. Ranking the five is a judgement, but the honest case for the most consequential is typeCheckingMode: it is the only one that prevents a class of mistake (wrong keyword arguments, wrong argument types) rather than recolouring or reflowing one. The easiest to forget is files.autoSave: its failure is silent and delayed — the editor looks fine, the terminal runs an older file, and the symptom arrives an hour later as “the run ignored my change”. The cheapest to appreciate is notebook.output.scrolling: 12,480 lines of training output at roughly 48 bytes per line is about 585 KiB in one cell, versus the last 500 lines at about 23 KiB.

  3. Open a Python file and verify that Pylance shows type hints and Black formats on save (Exercise 3). Make the verification falsifiable: write down what you expect to see, then create the failure on purpose and check the diagnostic returns.
    Show one worked answer

    Write a small file that the checker should object to: a function def fit(x, y, epochs: int = 1) and a call fit(1, 2, epoch=3). Expect a squiggle under epoch and a message naming the missing parameter — Pylance reports it from the signature, not from a run. Then hover the function name: the signature (x, y, epochs: int = 1) must appear; that hover is the LSP round trip from chapter 01, and it is the thing you lose if the language server is not running. For Black, type a line longer than 88 characters, press Cmd+S, and watch the wrapped block appear — if nothing happens, check that editor.formatOnSave is true and that the [python] default formatter is ms-python.black-formatter. To make the checker's value concrete: delete the annotation epochs: int = 1 so the signature becomes fit(x, y, epochs=1); the hover changes, but the epoch=3 squiggle stays (the parameter name still does not exist), which is exactly the class of mistake that survives to runtime when no checker runs. A teaching estimate for that mistake: one three-hour run at an example $2.34 per GPU-hour that dies at minute 90 wastes about 1.5 × 2.34 ≈ $3.51 and an afternoon.

  4. If you have access to a remote machine, set up Remote SSH and open a folder on it (Exercise 4). If you do not, write the four commands and the config entry in order, and say what each one fixes.
    Show one worked answer

    Order matters. (1) ssh-keygen -t ed25519 -C "you@example.com" creates the key pair — a private key at ~/.ssh/id_ed25519 that never leaves your laptop and a public key at ~/.ssh/id_ed25519.pub. (2) ssh-copy-id user@your-gpu-box-ip appends the public key to the remote account's ~/.ssh/authorized_keys, which is what removes the password prompt; without this step every connect still asks. (3) The ~/.ssh/config entry — Host gpu-box, HostName 203.0.113.50, User ubuntu, IdentityFile ~/.ssh/id_ed25519, ForwardAgent yes — turns an address and a username into the one word gpu-box; 203.0.113.50 is the source's example and belongs to the documentation block (TEST-NET-3, RFC 5737), so replace it with the real address. (4) Cmd+Shift+P → “Remote-SSH: Connect to Host” → gpu-box: VS Code installs its server component on the remote and opens the window. What you should see afterwards: the green remote indicator and SSH: gpu-box in the title bar, an explorer showing remote folders, extensions marked as installed remotely, and a terminal whose prompt is the box's. What each step fixes: (1) is the identity, (2) is the trust, (3) is the name, (4) is the session. And once the folder is remote, edit it there — if a local copy of the same filename exists, treat it as the trap it is.

Terms this lesson borrows from later lessons (or outside)

You do not need to master these here. Each one gets a proper treatment in its own lesson; the one-line meaning is enough to keep reading. Orange dotted underlines in the prose point back to this list.

  • SSH and key-based authenticationLogging into a remote machine, and the ed25519 key pair (private here, public in ~/.ssh/authorized_keys there) that removes the password prompt. This lesson uses SSH as the transport for remote editing; Phase 0, Lesson 03 (GPU Setup & Cloud) covers connecting to GPU instances, and Phase 0, Lesson 10 (Terminal & Shell) covers the shell on the other end.
  • Jupyter notebook executionCells, kernels, execution order and the variable explorer. This lesson only installs the Jupyter extension and sets notebook.output.scrolling; Phase 0, Lesson 05 (Jupyter Notebooks) teaches the notebook itself — including the stale-state surprises that execution order creates.
  • Virtual environmentsThe per-project interpreter where packages belong. The Python extension's virtual-env detection is what makes the editor agree with your terminal about which Python you mean; Phase 0, Lesson 06 (Python Environments) builds the environment this lesson only points at.
  • Git blame and historyWhich commit last touched a line, when, and why. GitLens displays that annotation inline in the editor, but the commits, branches and merges underneath are Phase 0, Lesson 02 (Git & Collaboration).
  • Dev containersAn editor session whose “remote” is a Docker container rather than a GPU box — the ms-vscode-remote.remote-containers entry in the source's extensions.json. Phase 0, Lesson 07 (Docker for AI) teaches the images and containers; the editor integration reuses exactly the layer-5 idea from this lesson.
  • Process monitoring and pipelineswatch, nvidia-smi -l 1, pipes, redirection and background jobs — the shell skills behind “one pane runs the script, one watches the GPU”. Phase 0, Lesson 10 (Terminal & Shell) is where those commands get their proper treatment.
KEEP GOING

A picture is a start.
Practice is the rest.

This lesson is a port of an open course. Everything here traces back to it — and the next step is running the code yourself.

Lesson text adapted from AI Engineering from Scratch (Phase 00, Lesson 08), its quiz, and the lesson's code/vscode/settings.json and extensions.json. The six labs — the LSP round-trip animator, the extension matcher, the settings playground, the Remote SSH simulator, the shortcut trainer and the 20-minute payoff calculator — are original to this page, as is the arithmetic they work with: 20 minutes once against 20 × 250 = 5,000 minutes = 83.3 hours ≈ 10.4 eight-hour days a year (the 4 + 6 + 3 + 4 + 3 minute daily split is an explicitly illustrative decomposition of the source's four frictions); the line lengths behind the rulers (the playground line is 102 columns, past Black's 88, and the comment is 125, past the 120 prose marker); the notebook arithmetic (10 epochs × 1,248 steps = 12,480 lines, ≈48 bytes each ≈ 585 KiB rendered at once versus the last 500 lines ≈ 23 KiB); the terminal arithmetic (10,000 lines of scrollback ≈ 780 KiB at ≈80 bytes a line); the GPU example ($2.34 per hour invented for arithmetic, one 90-minute loss ≈ $3.51); the Remote SSH split itemised local versus remote; and the memory hooks. Every extension id, extension count, setting, value, command and quoted line of advice traces back to the source — including the honest warning about Vim, the 203.0.113.50 documentation address (TEST-NET-3, RFC 5737) and the five optional recommendations in the source's extensions.json. All simulations are labelled as teaching models: no real language server, editor, SSH session or GPU was measured here, and no marketplace or version data is asserted.