<rss version="2.0">
	<channel>
		<title>anoopkc</title>
		<link>https://anoopkc.com</link>
		<description>anoopkc - Blog</description>
		<generator>Zine -- https://zine-ssg.io</generator>
		<language>en-US</language>
		<lastBuildDate>Wed, 14 Oct 2026 21:26:02 +0000</lastBuildDate>
		
			<item>
				<title>zenv: project environments on laptops and HPC clusters</title>
	      <description>&lt;h1&gt;zenv: project environments on laptops and HPC clusters&lt;/h1&gt;&lt;p&gt;&lt;figure&gt;&lt;img class=&quot;full &quot; src=&quot;https://anoopkc.com/blog/zenv/fig-speed.svg&quot; alt=&quot;Horizontal bar chart of mean times on an Apple Silicon Mac and an HPC system.&quot;&gt;
&lt;figcaption&gt;Mean time per command on an Apple Silicon Mac and an HPC system, as hyperfine means with zenv v0.28.1. The HPC system is a shared JURECA login node with 256 CPUs, and the project is on GPFS.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Before a Python project runs on JURECA, one of the clusters at the Jülich Supercomputing Centre, the shell needs three things: the site’s modules for the compiler stack including Python, a virtual environment built on that Python, and the variables the project reads. By hand, that is&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;load&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;Stages/2026&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;StdEnv&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;Python&lt;/span&gt;
&lt;span class=&quot;constant function&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;~/sim/.venv/bin/activate&lt;/span&gt;
&lt;span class=&quot;keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;property&quot;&gt;SIM_DATA&lt;/span&gt;=/p/scratch/myproject/sim/data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;typed again in every new shell and copied into every batch script(+ a one time cost of setting up the environment). The order matters as well: the venv’s Python is the module’s Python, and without the modules loaded it does not start at all, because it cannot find its own &lt;code&gt;libpython3.13.so&lt;/code&gt;. And the steps are slow. On a JURECA login node, loading the modules and starting Python takes about a second, of which Python itself needs &lt;strong&gt;20 ms&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/anoopkcn/zenv&quot; target=&quot;_blank&quot;&gt;zenv&lt;/a&gt; is a tool I wrote to keep these steps in a file next to the code, so that a project’s environment can be rebuilt on any machine and entered by name. It covers the cluster side, the modules and their slow loading, as well as the Python side, and every part is optional: on a laptop, the same tool can manage nothing but a venv.&lt;/p&gt;&lt;p&gt;It treates the environment as data. zenv records what activating an environment does, and replays that record in subsequent activations. The result is fast, reproducible, and usable from any shell, directory, batch script and Jupyter kernel.&lt;/p&gt;&lt;p&gt;It also comes with a shell hook that activates the environment when you &lt;code&gt;cd&lt;/code&gt; into the project and deactivates it when you leave the directory. No variable pollution, no stray entries in &lt;code&gt;PATH&lt;/code&gt;, and no typing the same three lines over and over.&lt;/p&gt;&lt;h2 id=&quot;one-file-next-to-the-code&quot;&gt;One file next to the code&lt;/h2&gt;&lt;p&gt;The three lines become an entry in &lt;code&gt;zenv.json&lt;/code&gt; at the root of the project. A file can hold several environments, here one for a laptop and one for JURECA:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;json&quot;&gt;{
  &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;base_dir&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;.zenv&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;sim&amp;quot;&lt;/span&gt;: {
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;target_machines&amp;quot;&lt;/span&gt;: [&lt;span class=&quot;string&quot;&gt;&amp;quot;*&amp;quot;&lt;/span&gt;],
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;dependency_file&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;requirements.txt&amp;quot;&lt;/span&gt;,
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;env&amp;quot;&lt;/span&gt;: { &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;SIM_DATA&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;${ZENV_PROJECT_DIR}/data&amp;quot;&lt;/span&gt; }
  },
  &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;sim-jureca&amp;quot;&lt;/span&gt;: {
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;target_machines&amp;quot;&lt;/span&gt;: [&lt;span class=&quot;string&quot;&gt;&amp;quot;jurecadc&amp;quot;&lt;/span&gt;],
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;modules&amp;quot;&lt;/span&gt;: [&lt;span class=&quot;string&quot;&gt;&amp;quot;Stages/2026&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;StdEnv&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;Python&amp;quot;&lt;/span&gt;],
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;dependency_file&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;requirements.txt&amp;quot;&lt;/span&gt;,
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;env&amp;quot;&lt;/span&gt;: { &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;SIM_DATA&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;/p/scratch/myproject/sim/data&amp;quot;&lt;/span&gt; }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;base_dir&lt;/code&gt; is where the environments are built, relative to the &lt;code&gt;zenv.json&lt;/code&gt; file. &lt;code&gt;target_machines&lt;/code&gt; names the machines an environment is meant for, zenv matches it against &lt;code&gt;$SYSTEMNAME&lt;/code&gt; where a site sets it. So One can describe multiple environments that target different machines in the same file. zenv also expands variables like &lt;code&gt;$HOME&lt;/code&gt;, &lt;code&gt;${ZENV_PROJECT_DIR}&lt;/code&gt; and &lt;code&gt;${ZENV_ENV_DIR}&lt;/code&gt; and takes everything else literally.&lt;/p&gt;&lt;p&gt;Build the environment once on each machine, and use it from then on:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;zenv&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;setup&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;sim-jureca&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;--uv&lt;/span&gt;            &lt;span class=&quot;comment&quot;&gt;# modules, venv, requirements.txt&lt;/span&gt;
&lt;span class=&quot;comment&quot;&gt;# --uv flag makes setup install packages with uv instead of pip. &lt;/span&gt;

&lt;span class=&quot;constant function&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;embedded constant&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;embedded constant function&quot;&gt;zenv&lt;/span&gt;&lt;span class=&quot;embedded&quot;&gt; &lt;/span&gt;&lt;span class=&quot;embedded constant&quot;&gt;activate&lt;/span&gt;&lt;span class=&quot;embedded&quot;&gt; &lt;/span&gt;&lt;span class=&quot;embedded constant&quot;&gt;sim-jureca&lt;/span&gt;&lt;span class=&quot;embedded&quot;&gt;)&lt;/span&gt;    &lt;span class=&quot;comment&quot;&gt;# activate; deactivate undoes it&lt;/span&gt;
&lt;span class=&quot;constant function&quot;&gt;zenv&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;sim-jureca&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;python&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;train.py&lt;/span&gt;   &lt;span class=&quot;comment&quot;&gt;# one command, no activation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Setup also registers the environment in &lt;code&gt;~/.zenv/registry.json&lt;/code&gt;, which lets the other commands find it by name from any directory. Inside the project, &lt;code&gt;.&lt;/code&gt; stands for the project’s environment on the current machine, so &lt;code&gt;zenv run . python train.py&lt;/code&gt; works unchanged on the laptop and on JURECA.&lt;/p&gt;&lt;p&gt;For a new project, &lt;code&gt;zenv init&lt;/code&gt; writes a &lt;code&gt;zenv.json&lt;/code&gt; from what it finds, such as a &lt;code&gt;requirements.txt&lt;/code&gt; or a &lt;code&gt;pyproject.toml&lt;/code&gt;, and &lt;code&gt;zenv validate&lt;/code&gt; reports mistakes in the file with line numbers.&lt;/p&gt;&lt;h2 id=&quot;activation-as-data&quot;&gt;Activation as data&lt;/h2&gt;&lt;p&gt;An activation script is a small program. Each time it runs, it calls &lt;code&gt;module load&lt;/code&gt;, which has Lmod work out the modules again, and then edits &lt;code&gt;PATH&lt;/code&gt; and the other variables. zenv runs those steps once, during setup, and keeps the result.&lt;/p&gt;&lt;p&gt;To do that, setup starts one bash process that purges the modules, writes out its environment with &lt;code&gt;env -0&lt;/code&gt;, loads the modules, and writes the environment out again. The difference between the two snapshots is the module cache: the variables the modules set, and the entries they put in front of or behind the old value of &lt;code&gt;PATH&lt;/code&gt;, &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt; and the other path variables. zenv stores it as a list of operations (set, prepend, append, unset), followed by the operations for the rest of activation: the venv’s &lt;code&gt;VIRTUAL_ENV&lt;/code&gt; and &lt;code&gt;bin/&lt;/code&gt; directory, the variables from &lt;code&gt;env&lt;/code&gt;, and a few for zenv’s own use. Together they make up &lt;code&gt;zenv-env.json&lt;/code&gt;, the record of what activating the environment does.&lt;/p&gt;&lt;p&gt;Every way of entering the environment reads that record. The activation scripts for bash and zsh, csh and tcsh, and fish are generated from it, and &lt;code&gt;zenv run&lt;/code&gt; applies it to its own process without starting a shell.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img class=&quot;full &quot; src=&quot;https://anoopkc.com/blog/zenv/fig-env-as-data.svg&quot; alt=&quot;Three inputs (module load, the venv, env in zenv.json) feed zenv-env.json, which is read by activate.sh, activate.csh, activate.fish and zenv run&quot;&gt;
&lt;figcaption&gt;zenv setup gathers what activation does into one record, which the activation scripts and zenv run then read.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Replaying the record costs little. On a JURECA login node, &lt;code&gt;zenv run&lt;/code&gt; starts Python from a module-based environment in 21 ms, where Python started directly takes about 20 ms. With &lt;code&gt;&amp;quot;module_cache&amp;quot;: false&lt;/code&gt;, zenv calls Lmod on every use instead, and the same command takes about a second. Activation in an interactive shell gains in the same way, since with the cache the generated script does not call &lt;code&gt;module&lt;/code&gt;: in bash it takes 9 ms with the cache and about a second without.&lt;/p&gt;&lt;p&gt;Because zenv knows exactly what activation changed, it can undo it. &lt;code&gt;deactivate&lt;/code&gt; gives each variable its previous value back, or unsets it if it had none, and activating a second environment deactivates the first. Switching back and forth leaves no stray entries in &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The record also makes the environment usable where nobody types &lt;code&gt;source&lt;/code&gt;. &lt;code&gt;zenv run&lt;/code&gt; replaces itself with the command it starts, so the exit status and signals are the command’s own, which is what a batch script expects from &lt;code&gt;zenv run sim-jureca python train.py&lt;/code&gt;. Jupyter kernels made with &lt;code&gt;zenv jupyter create sim-jureca&lt;/code&gt; start through &lt;code&gt;zenv run&lt;/code&gt; as well. A notebook then gets the environment’s modules even when the Jupyter server was started with other ones, and the Interrupt button reaches the Python process. The environment needs &lt;code&gt;ipykernel&lt;/code&gt; for this.&lt;/p&gt;&lt;p&gt;Two things cannot be recorded. Hooks in &lt;code&gt;activate.commands&lt;/code&gt; or &lt;code&gt;activate.script&lt;/code&gt; are shell code, so an environment that has them is run by sourcing its &lt;code&gt;activate.sh&lt;/code&gt; in bash; plain variables belong in &lt;code&gt;env&lt;/code&gt;, where they stay data. And the module cache does not notice when the site changes its modules. It is tied to the cluster through &lt;code&gt;$SYSTEMNAME&lt;/code&gt;, so it is reused on every node of JURECA and never replayed on another system, but after a module is updated during maintenance, run &lt;code&gt;zenv setup&lt;/code&gt; again to capture it afresh.&lt;/p&gt;&lt;h2 id=&quot;python-on-top-of-the-modules&quot;&gt;Python on top of the modules&lt;/h2&gt;&lt;p&gt;On a laptop the Python part is most of what zenv does. On a cluster it has to fit around the modules.&lt;/p&gt;&lt;p&gt;zenv picks the interpreter in a fixed order: a Python from the loaded modules first, then the &lt;code&gt;fallback_python&lt;/code&gt; named in &lt;code&gt;zenv.json&lt;/code&gt;, then a Python installed and pinned with &lt;code&gt;zenv python install&lt;/code&gt; and &lt;code&gt;zenv python pin&lt;/code&gt;, then the system’s &lt;code&gt;python3&lt;/code&gt;. If the &lt;code&gt;pyproject.toml&lt;/code&gt; that setup reads declares &lt;code&gt;requires-python&lt;/code&gt;, setup checks the chosen interpreter against it before creating anything, and when it does not match, says which of these to change.&lt;/p&gt;&lt;p&gt;zenv writes the venv itself. A venv is a &lt;code&gt;pyvenv.cfg&lt;/code&gt; file, &lt;code&gt;bin/python&lt;/code&gt; links to the base interpreter and empty &lt;code&gt;site-packages&lt;/code&gt; directories, and zenv creates them directly, without &lt;code&gt;python -m venv&lt;/code&gt; or &lt;code&gt;ensurepip&lt;/code&gt;. It then installs pip into the venv, with uv when you pass &lt;code&gt;--uv&lt;/code&gt;. A new environment without packages is ready in 115 ms on a Mac, where &lt;code&gt;python -m venv&lt;/code&gt; alone takes 1.5 s.&lt;/p&gt;&lt;p&gt;Modules often ship Python packages of their own, such as numpy or mpi4py built against the site’s libraries. Before installing, zenv reads the package metadata in the directories on the modules’ &lt;code&gt;PYTHONPATH&lt;/code&gt;, the same &lt;code&gt;*.dist-info&lt;/code&gt; and &lt;code&gt;*.egg-info&lt;/code&gt; entries that &lt;code&gt;importlib.metadata&lt;/code&gt; reads, and leaves out every requirement a module already provides, listing what it left out. &lt;code&gt;--force&lt;/code&gt; installs them anyway.&lt;/p&gt;&lt;p&gt;New dependencies go in with &lt;code&gt;zenv add&lt;/code&gt;, which installs a package and then records it in the project’s manifest:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;zenv&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;quot;xarray&amp;gt;=2024.1&amp;quot;&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;--uv&lt;/span&gt;   &lt;span class=&quot;comment&quot;&gt;# recorded in the manifest&lt;/span&gt;
&lt;span class=&quot;constant function&quot;&gt;zenv&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;pytest&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;--uv&lt;/span&gt;       &lt;span class=&quot;comment&quot;&gt;# as a development dependency&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where the package goes depends on what the environment uses: a line appended to &lt;code&gt;requirements.txt&lt;/code&gt;, an entry in &lt;code&gt;[project].dependencies&lt;/code&gt; (or &lt;code&gt;[dependency-groups].dev&lt;/code&gt; with &lt;code&gt;--dev&lt;/code&gt;) of &lt;code&gt;pyproject.toml&lt;/code&gt;, where zenv rewrites only that entry and leaves comments alone, or the &lt;code&gt;dependencies&lt;/code&gt; list in &lt;code&gt;zenv.json&lt;/code&gt;. The manifest changes only after the install succeeds, so a misspelled package name leaves the files as they were.&lt;/p&gt;&lt;h2 id=&quot;from-any-directory&quot;&gt;From any directory&lt;/h2&gt;&lt;p&gt;Registered environments can be used by name wherever you are. &lt;code&gt;zenv ls&lt;/code&gt; lists the ones for the current machine, &lt;code&gt;cd $(zenv cd sim-jureca)&lt;/code&gt; goes to the project, and &lt;code&gt;zenv alias create&lt;/code&gt; gives an environment a second name.&lt;/p&gt;&lt;p&gt;Aliases also help on shared filesystems. JURECA and JUWELS mount the same project directories, so one checkout serves both, with an environment for each machine in its &lt;code&gt;zenv.json&lt;/code&gt;. Give both environments the same alias, and the alias picks the one whose &lt;code&gt;target_machines&lt;/code&gt; match the machine you are on:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;zenv&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;hpc&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;sim-jureca&lt;/span&gt;
&lt;span class=&quot;constant function&quot;&gt;zenv&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;hpc&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;sim-juwels&lt;/span&gt;
&lt;span class=&quot;constant function&quot;&gt;zenv&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;hpc&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;python&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;train.py&lt;/span&gt;    &lt;span class=&quot;comment&quot;&gt;# sim-jureca on JURECA, sim-juwels on JUWELS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.&lt;/code&gt; resolves the same way.&lt;/p&gt;&lt;p&gt;For zsh and bash there is a hook that activates a project’s environment when you &lt;code&gt;cd&lt;/code&gt; into the project and deactivates it when you leave, much as direnv does:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;eval&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string embedded constant&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;string embedded constant function&quot;&gt;zenv&lt;/span&gt;&lt;span class=&quot;string embedded&quot;&gt; &lt;/span&gt;&lt;span class=&quot;string embedded constant&quot;&gt;hook&lt;/span&gt;&lt;span class=&quot;string embedded&quot;&gt; &lt;/span&gt;&lt;span class=&quot;string embedded constant&quot;&gt;zsh&lt;/span&gt;&lt;span class=&quot;string embedded&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt;    &lt;span class=&quot;comment&quot;&gt;# in ~/.zshrc; for ~/.bashrc, zenv hook bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It activates only environments you have set up yourself, so the &lt;code&gt;zenv.json&lt;/code&gt; in a freshly cloned repository does nothing until you run &lt;code&gt;zenv setup&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;zenv also keeps an environment in step with its configuration. &lt;code&gt;zenv run&lt;/code&gt;, &lt;code&gt;zenv activate&lt;/code&gt; and &lt;code&gt;zenv cd&lt;/code&gt; compare the environment’s entry in &lt;code&gt;zenv.json&lt;/code&gt;, and the dependency and module files it names, with their state at the last setup. If anything changed, they rebuild first, with the flags of that setup, so &lt;code&gt;--uv&lt;/code&gt; sticks. Add a line to &lt;code&gt;requirements.txt&lt;/code&gt;, and the next &lt;code&gt;zenv run&lt;/code&gt; installs the package before it runs your command. When the module list has not changed, the rebuild replays the module cache instead of calling Lmod again; on JURECA that brought the setup of a three-module environment from 1.25 s down to 0.16 s. The &lt;code&gt;cd&lt;/code&gt; hook is the exception. It never rebuilds, so that &lt;code&gt;cd&lt;/code&gt; stays fast, and prints a line saying the environment is out of date instead.&lt;/p&gt;&lt;h2 id=&quot;environments-without-python&quot;&gt;Environments without Python&lt;/h2&gt;&lt;p&gt;Nothing above requires Python. A C++ project can use zenv for its compilers and variables alone:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;json&quot;&gt;{
  &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;base_dir&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;.zenv&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;tools&amp;quot;&lt;/span&gt;: {
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;target_machines&amp;quot;&lt;/span&gt;: [&lt;span class=&quot;string&quot;&gt;&amp;quot;jurecadc&amp;quot;&lt;/span&gt;],
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;python&amp;quot;&lt;/span&gt;: &lt;span class=&quot;constant_builtin&quot;&gt;false&lt;/span&gt;,
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;modules&amp;quot;&lt;/span&gt;: [&lt;span class=&quot;string&quot;&gt;&amp;quot;Stages/2026&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;GCC&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;CMake&amp;quot;&lt;/span&gt;],
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;env&amp;quot;&lt;/span&gt;: {
      &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;PATH&amp;quot;&lt;/span&gt;: { &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;prepend&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;${ZENV_PROJECT_DIR}/scripts&amp;quot;&lt;/span&gt; },
      &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;OMP_NUM_THREADS&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;8&amp;quot;&lt;/span&gt;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Setup loads and caches the modules, creates an empty &lt;code&gt;bin/&lt;/code&gt; directory that activation puts on &lt;code&gt;PATH&lt;/code&gt;, and runs any setup hooks. There is no venv, and activation does not set &lt;code&gt;VIRTUAL_ENV&lt;/code&gt;, so uv and pip will not mistake the environment for one. Setting &lt;code&gt;&amp;quot;python&amp;quot;: true&lt;/code&gt; later adds a venv in the same directory and keeps the modules, the hooks and the files in &lt;code&gt;bin/&lt;/code&gt;.&lt;/p&gt;&lt;h2 id=&quot;how-fast-it-is&quot;&gt;How fast it is&lt;/h2&gt;&lt;p&gt;zenv is a single binary written in Zig, statically linked on Linux, and needs no Python to run, so it works on a machine before any Python is set up. Its own commands cost about as much as starting an empty process. The time you notice comes from the programs it starts: uv, Lmod and Python.&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Mean time (ms)&lt;/th&gt;&lt;th align=&apos;right&apos;&gt;Mac&lt;/th&gt;&lt;th align=&apos;right&apos;&gt;HPC system&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;true&lt;/code&gt;, an empty process&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;1.2&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;2.1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;zenv ls&lt;/code&gt;&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;1.9&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;2.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;zenv activate &amp;lt;env&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;2.0&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;2.2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;zenv run &amp;lt;env&amp;gt; true&lt;/code&gt;&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;3.1&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;3.9&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;bash -l&lt;/code&gt;, a login shell alone&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;–&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;495&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;source $(zenv activate &amp;lt;env&amp;gt;)&lt;/code&gt; in a login shell, with modules&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;–&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;525&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;module load&lt;/code&gt; and &lt;code&gt;source .venv/bin/activate&lt;/code&gt; by hand, in a login shell&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;–&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;1,586&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;zenv setup &amp;lt;env&amp;gt; --uv&lt;/code&gt;, nothing changed&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;21&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;166&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;zenv setup &amp;lt;env&amp;gt; --uv&lt;/code&gt;, new environment without packages&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;115&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;358&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;python -m venv&lt;/code&gt;, for comparison&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;1,456&lt;/td&gt;&lt;td align=&apos;right&apos;&gt;2,618&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;The numbers are hyperfine means for zenv 0.28 on an Apple Silicon Mac and on an HPC system, a JURECA login node (256 CPUs, project on GPFS). The login node is shared with other users, so its numbers vary by about 0.8 ms. The three login-shell rows need modules, so the Mac has none; they come from an earlier run with zenv 0.27.8, whose &lt;code&gt;zenv activate&lt;/code&gt; took 1.9 ms longer.&lt;/p&gt;&lt;h2 id=&quot;getting-it&quot;&gt;Getting it&lt;/h2&gt;&lt;p&gt;zenv runs on Linux and macOS, on x86_64 and aarch64:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-fsSL&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;https://raw.githubusercontent.com/anoopkcn/zenv/HEAD/install.sh&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;constant function&quot;&gt;bash&lt;/span&gt;
&lt;span class=&quot;comment&quot;&gt;# or&lt;/span&gt;
&lt;span class=&quot;constant function&quot;&gt;brew&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;anoopkcn/zenv/zenv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The source and the full reference, with every key of &lt;code&gt;zenv.json&lt;/code&gt; and the rules the &lt;code&gt;cd&lt;/code&gt; hook and the module cache follow, are at &lt;a href=&quot;https://github.com/anoopkcn/zenv&quot; target=&quot;_blank&quot;&gt;github.com/anoopkcn/zenv&lt;/a&gt;.&lt;/p&gt;</description>
				<link>https://anoopkc.com/blog/zenv/</link>
				<pubDate>Wed, 14 Oct 2026 18:14:11 +0000</pubDate>
				<guid>https://anoopkc.com/blog/zenv/</guid>
			</item>
		
			<item>
				<title>Dimensionality reduction: from pixels to a picture, a summary or a short list</title>
	      <description>&lt;h1&gt;Dimensionality reduction: from pixels to a picture, a summary or a short list&lt;/h1&gt;&lt;p&gt;&lt;em&gt;This is the written version of a 45-minute talk I gave in September 2026 on data preparation and dimensionality reduction. Every data figure except the curse-of-dimensionality plot is computed from the &lt;a href=&quot;https://github.com/anoopkcn/tabletop&quot; target=&quot;_blank&quot;&gt;tabletop&lt;/a&gt; dataset, and the numbers in the text come from the same runs.&lt;/em&gt;&lt;/p&gt;&lt;h2 id=&quot;one-object-on-a-table&quot;&gt;One object on a table&lt;/h2&gt;&lt;p&gt;Every example in this post uses the same data: &lt;a href=&quot;https://github.com/anoopkcn/tabletop&quot; target=&quot;_blank&quot;&gt;tabletop&lt;/a&gt; &lt;a href=&quot;https://github.com/anoopkcn/tabletop&quot; target=&quot;_blank&quot;&gt;[2]&lt;/a&gt;, a set of synthetic photos of a single object standing on a table. A small ray caster renders each photo, so every photo comes with the true values of the seven factors that produced it:&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Factor&lt;/th&gt;&lt;th&gt;Levels&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Shape&lt;/td&gt;&lt;td&gt;4 (cube, sphere, cylinder, capsule)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Colour&lt;/td&gt;&lt;td&gt;12&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Size&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Rotation&lt;/td&gt;&lt;td&gt;12&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Position&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Light&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Table&lt;/td&gt;&lt;td&gt;2 (beige, grey-blue)&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;Two cameras look at each scene. One takes a colour photo, the other a depth image that stores the distance from the camera along each pixel’s ray. Both are 64 x 64 pixels.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-tabletop.webp&quot; alt=&quot;Six colour photos of objects on a table with the matching depth images below them&quot;&gt;
&lt;figcaption&gt;Six scenes from tabletop. Top row: the colour photo. Bottom row: the depth image of the same scene.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;The full grid of factors gives 294,912 photos. I use a subset of 6,144 that is still a complete grid on its own: 4 colours (red, lime, cyan and violet), 4 rotations (0°, 90°, 180° and 270°), 3 positions, 2 lights, and every shape, size and table.&lt;/p&gt;&lt;p&gt;The depth camera sees shape, size, rotation and position. It cannot see colour, light or the table, because none of those change a distance. That detail comes back when we join the two cameras.&lt;/p&gt;&lt;h3 id=&quot;what-dimensionality-reduction-does&quot;&gt;What dimensionality reduction does&lt;/h3&gt;&lt;p&gt;A colour photo of 64 x 64 pixels with three channels is 12,288 numbers. Apart from a little camera jitter and pixel noise, only seven things change from one photo to the next. If you had to describe a photo in as few numbers as possible and knew the factors, seven would do.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-reduce.svg&quot; alt=&quot;A wide grid of n rows and p columns, an arrow labelled reduce, and a narrow grid of n rows and l columns&quot;&gt;
&lt;figcaption&gt;Each photo is one row of p = 12,288 numbers. Dimensionality reduction describes it with l numbers instead.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Formally, you start with &lt;script type=&quot;math/tex&quot;&gt;n&lt;/script&gt; samples, each described by &lt;script type=&quot;math/tex&quot;&gt;p&lt;/script&gt; features, and end with &lt;script type=&quot;math/tex&quot;&gt;l&lt;/script&gt; features per sample, where &lt;script type=&quot;math/tex&quot;&gt;l \ll p&lt;/script&gt;. Some information is always lost on the way. Each method makes its own choice of what to keep, and most of this post is about those choices.&lt;/p&gt;&lt;p&gt;The reduced data are useful in three ways. You can &lt;strong&gt;look&lt;/strong&gt; at them: scatter plots, heatmaps and quality control need two or three dimensions. You can &lt;strong&gt;model&lt;/strong&gt; with them: clustering, classification and trajectories work better on a few informative features. And they make &lt;strong&gt;computing&lt;/strong&gt; cheaper: less storage, faster training and fewer parameters to fit.&lt;/p&gt;&lt;p&gt;A plot of two principal components is easier to read than 12,288 numbers, but each component mixes thousands of pixels, so the plot does not tell you which pixels matter. Feature selection is the exception, because it keeps original measurements you can point at.&lt;/p&gt;&lt;h3 id=&quot;the-curse-of-dimensionality&quot;&gt;The curse of dimensionality&lt;/h3&gt;&lt;p&gt;High-dimensional space behaves in ways that 2-D intuition does not prepare you for. The plot below places 500 uniformly random points in a cube of growing dimension. For 20 query points, it compares the distance to the nearest point with the distance to the farthest.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-curse.webp&quot; alt=&quot;Line chart: nearest over farthest distance rises from near 0 in 2 dimensions to 0.97 at 12,288 dimensions&quot; width=&quot;560&quot;&gt;
&lt;figcaption&gt;The ratio of nearest to farthest distance for uniform random points. At 12,288 dimensions, the size of one photo, it reaches 0.97.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;In two dimensions the nearest point is close compared with the farthest. At 12,288 dimensions the nearest point is 97% as far away as the farthest one &lt;a href=&quot;https://doi.org/10.1007/3-540-49257-7_15&quot; target=&quot;_blank&quot;&gt;[3]&lt;/a&gt;. When every point is about equally far from every other, neighbourhoods and clusters lose their contrast.&lt;/p&gt;&lt;p&gt;Real data are not uniform, and that is what makes reduction possible. The tabletop photos occupy a thin, roughly seven-dimensional sliver of the 12,288-dimensional pixel space. Every method below is a different way of finding that sliver.&lt;/p&gt;&lt;h3 id=&quot;three-ways-to-get-from-p-features-to-l&quot;&gt;Three ways to get from p features to l&lt;/h3&gt;&lt;p&gt;The methods fall into three families. What separates them is what happens to the original features.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-families.svg&quot; alt=&quot;Three panels showing feature bars for transformation, aggregation and selection&quot;&gt;
&lt;figcaption&gt;Transformation mixes all features into new ones. Aggregation summarizes groups. Selection keeps a subset.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Transformation&lt;/strong&gt; builds new features, each a mix of &lt;em&gt;all&lt;/em&gt; the originals: PCA, ICA, t-SNE, UMAP and autoencoders.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Aggregation&lt;/strong&gt; splits the features into groups and summarizes each group with one number: pixel blocks, gene sets, clusters found by &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt;-medoids.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Selection&lt;/strong&gt; keeps a &lt;em&gt;subset&lt;/em&gt; of the original features: ReliefF, forward selection, the elastic net.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Only selection keeps measurements you can still point at, in our case single pixels. The other two trade that for compactness.&lt;/p&gt;&lt;p&gt;There is also a rough split by supervision. Transformation and aggregation usually run without a target variable. Selection usually keeps whatever predicts a target. The split is only a tendency: linear discriminant analysis is a supervised transformation, and dropping low-variance features is unsupervised selection.&lt;/p&gt;&lt;h2 id=&quot;preparing-the-data&quot;&gt;Preparing the data&lt;/h2&gt;&lt;p&gt;Before any method runs, the photos have to become a matrix of numbers on a common scale, and the data have to be split.&lt;/p&gt;&lt;h3 id=&quot;one-step-of-a-longer-pipeline&quot;&gt;One step of a longer pipeline&lt;/h3&gt;&lt;p&gt;Dimensionality reduction sits in the middle of a longer chain of steps, and the order of those steps matters.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-pipeline.svg&quot; alt=&quot;Ten numbered pipeline steps in two rows; steps 6 to 8 are marked as fitted on training data only&quot;&gt;
&lt;figcaption&gt;A typical pipeline. The split into training and test data comes before every step that learns from the data.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Missing-value imputation, scaling and dimensionality reduction all learn something from the data: a mean, a standard deviation, a set of components. If they run before the split, the test data have already shaped them. So the split comes before them, at step 5, and steps 6 to 8 are fitted on the training data alone.&lt;/p&gt;&lt;h3 id=&quot;from-image-files-to-an-analysis-matrix&quot;&gt;From image files to an analysis matrix&lt;/h3&gt;&lt;p&gt;The tabletop photos come in an HDF5 file. Turning them into a matrix with one row per photo takes a few lines:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;h5py&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;File&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;smoke.h5&amp;quot;&lt;/span&gt;) &lt;span class=&quot;keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;f&lt;/span&gt;:
    &lt;span class=&quot;constructor constant variable&quot;&gt;images&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;f&lt;/span&gt;[&lt;span class=&quot;string&quot;&gt;&amp;quot;images&amp;quot;&lt;/span&gt;][:]      &lt;span class=&quot;comment&quot;&gt;# uint8,  (6144, 64, 64, 3)&lt;/span&gt;
    &lt;span class=&quot;constructor constant variable&quot;&gt;depth&lt;/span&gt;  &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;f&lt;/span&gt;[&lt;span class=&quot;string&quot;&gt;&amp;quot;depth&amp;quot;&lt;/span&gt;][:]       &lt;span class=&quot;comment&quot;&gt;# uint16, (6144, 64, 64)&lt;/span&gt;
    &lt;span class=&quot;constructor constant variable&quot;&gt;labels&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;f&lt;/span&gt;[&lt;span class=&quot;string&quot;&gt;&amp;quot;labels&amp;quot;&lt;/span&gt;][:]      &lt;span class=&quot;comment&quot;&gt;# int8,   (6144, 7): one level per factor&lt;/span&gt;

&lt;span class=&quot;constructor constant variable&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;images&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;astype&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;np&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;float32&lt;/span&gt;) &lt;span class=&quot;operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;255&lt;/span&gt;        &lt;span class=&quot;comment&quot;&gt;# integers 0-255 -&amp;gt; numbers 0-1&lt;/span&gt;
&lt;span class=&quot;constructor constant variable&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;X&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;reshape&lt;/span&gt;(&lt;span class=&quot;constructor function_builtin constant variable function&quot;&gt;len&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;X&lt;/span&gt;), &lt;span class=&quot;operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;)                  &lt;span class=&quot;comment&quot;&gt;# (6144, 12288): one row per photo&lt;/span&gt;
&lt;span class=&quot;constructor constant variable&quot;&gt;D&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;depth&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;astype&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;np&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;float32&lt;/span&gt;) &lt;span class=&quot;operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;number&quot;&gt;65535&lt;/span&gt;   &lt;span class=&quot;comment&quot;&gt;# stored steps -&amp;gt; scene units&lt;/span&gt;
&lt;span class=&quot;constructor constant variable&quot;&gt;D&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;D&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;reshape&lt;/span&gt;(&lt;span class=&quot;constructor function_builtin constant variable function&quot;&gt;len&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;D&lt;/span&gt;), &lt;span class=&quot;operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;1&lt;/span&gt;)                  &lt;span class=&quot;comment&quot;&gt;# (6144, 4096)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three things happen here:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;The types change.&lt;/strong&gt; Pixels, depths and labels all arrive as small integer types. Anything used in arithmetic becomes a float first, for a reason the next section shows.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The photos are flattened.&lt;/strong&gt; Each colour photo becomes one row of 12,288 columns, and each depth image one row of 4,096. This throws away which pixel sits next to which. Methods that use the neighbourhood, such as convolutional networks, keep the 2-D shape instead.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The labels stay out of &lt;code&gt;X&lt;/code&gt;.&lt;/strong&gt; The seven factors are what we hope to recover. They colour the plots and serve as targets, but they are never inputs.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;integer-types-overflow-without-warning&quot;&gt;Integer types overflow without warning&lt;/h3&gt;&lt;p&gt;Both of the bugs below came up while I was preparing the figures for this talk.&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Expression&lt;/th&gt;&lt;th&gt;Expected&lt;/th&gt;&lt;th&gt;Got&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;(depth * 8 / 65535).max()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;8.0&lt;/td&gt;&lt;td&gt;1.0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;labels[:, 1] * 30&lt;/code&gt; for cyan&lt;/td&gt;&lt;td&gt;180&lt;/td&gt;&lt;td&gt;−76&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;labels[:, 1] * 30&lt;/code&gt; for violet&lt;/td&gt;&lt;td&gt;270&lt;/td&gt;&lt;td&gt;14&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;&lt;code&gt;depth&lt;/code&gt; is &lt;code&gt;uint16&lt;/code&gt;, which holds 0 to 65,535. The file spreads distances from 0 to 8 scene units over those steps, so multiplying by 8 and dividing by 65,535 turns a step count back into a distance. Multiplying the largest value by 8 does not fit in 16 bits, so NumPy wraps the product around and the depth range comes out as 0 to 1 instead of 0 to 8. &lt;code&gt;labels&lt;/code&gt; is &lt;code&gt;int8&lt;/code&gt;, which holds −128 to 127. The colour label, &lt;code&gt;labels[:, 1]&lt;/code&gt;, is a hue level from 0 to 11, and the twelve hues sit 30 degrees apart on the colour wheel, so the level times 30 gives the hue angle: 180 for cyan and 270 for violet. Both wrap, to 180 − 256 = −76 and 270 − 256 = 14, and the plot colours come out wrong. Neither case raises an error.&lt;/p&gt;&lt;p&gt;The fix is to convert first, with &lt;code&gt;.astype(np.float32)&lt;/code&gt;. The habit that catches these bugs is to print each array’s dtype, minimum and maximum right after loading.&lt;/p&gt;&lt;h3 id=&quot;scale-before-you-reduce&quot;&gt;Scale before you reduce&lt;/h3&gt;&lt;p&gt;Suppose you join the two cameras into one matrix, so each row holds the colour photo followed by the depth image:&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Camera&lt;/th&gt;&lt;th&gt;Features&lt;/th&gt;&lt;th&gt;Range&lt;/th&gt;&lt;th&gt;Share of total variance&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Colour&lt;/td&gt;&lt;td&gt;12,288&lt;/td&gt;&lt;td&gt;0 to 1&lt;/td&gt;&lt;td&gt;13%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Depth&lt;/td&gt;&lt;td&gt;4,096&lt;/td&gt;&lt;td&gt;1.8 to 8&lt;/td&gt;&lt;td&gt;87%&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;Most reduction methods follow the largest variance. Joined as stored, depth holds 87% of the variance with a third as many features, so depth decides what the method sees.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-scaling.webp&quot; alt=&quot;Two scatter plots; on the left the beige and grey-blue tables overlap, on the right they separate&quot;&gt;
&lt;figcaption&gt;1,500 random photos in the first two principal components, coloured by the table they stand on. Left: the cameras joined as stored. Right: each camera scaled first.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;In the colour photos alone, the table is the largest source of variation. The depth camera cannot see the table at all. Joined unscaled, the table vanishes from the top components (left panel). With both cameras scaled to the same total variance, it comes back as the second component (right panel).&lt;/p&gt;&lt;p&gt;There are two common fixes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Standardize each feature&lt;/strong&gt;, subtracting its mean and dividing by its standard deviation: &lt;script type=&quot;math/tex&quot;&gt;z_{ij} = (x_{ij} - \bar{x}_j) / s_j&lt;/script&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Scale each block&lt;/strong&gt; (camera, assay, instrument) to the same total variance.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Standardizing works column by column, so it gives the same result before or after the join. Block scaling needs to know which columns came from which camera.&lt;/p&gt;&lt;p&gt;Standardizing is the usual fix when features carry different units: centimetres and kilograms, gene counts and protein levels. For pixels from a single camera it is usually wrong. It would give the dull wall pixels, which barely change, as much weight as the pixels where the object appears. Pixels from one camera already share a scale, so subtracting each pixel’s mean is enough. (Tree-based models need no scaling at all, since a split on one feature does not care about its units.)&lt;/p&gt;&lt;h3 id=&quot;fit-preprocessing-on-the-training-data-only&quot;&gt;Fit preprocessing on the training data only&lt;/h3&gt;&lt;p&gt;Imputation, scaling, dimensionality reduction and feature selection all learn from data. Fit them on the training samples, and only apply them to the test samples:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;constructor constant variable&quot;&gt;pca&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor function_builtin constant variable function&quot;&gt;PCA&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;n_components&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;50&lt;/span&gt;)
&lt;span class=&quot;constructor constant variable&quot;&gt;Z_train&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;pca&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;fit_transform&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;X&lt;/span&gt;[&lt;span class=&quot;constructor constant variable&quot;&gt;train&lt;/span&gt;])   &lt;span class=&quot;comment&quot;&gt;# learns the mean and the components&lt;/span&gt;
&lt;span class=&quot;constructor constant variable&quot;&gt;Z_test&lt;/span&gt;  &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;pca&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;transform&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;X&lt;/span&gt;[&lt;span class=&quot;constructor constant variable&quot;&gt;test&lt;/span&gt;])        &lt;span class=&quot;comment&quot;&gt;# reuses them, learns nothing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The classic mistake is to choose the top features on all samples and then cross-validate a classifier on those features. The accuracy looks excellent and does not reproduce, because the test samples helped choose the features. scikit-learn’s &lt;code&gt;Pipeline&lt;/code&gt; &lt;a href=&quot;https://scikit-learn.org/stable/modules/compose.html&quot; target=&quot;_blank&quot;&gt;[4]&lt;/a&gt; makes the correct version the easy one to write.&lt;/p&gt;&lt;p&gt;Near-identical samples leak as well. A cube one size step larger looks almost the same as the smaller cube, so a random split puts near-copies of each test photo into the training set. Holding out whole groups avoids that. The feature-selection section below holds out one object colour at a time for this reason.&lt;/p&gt;&lt;p&gt;For plots with no test set, fitting on all samples is fine.&lt;/p&gt;&lt;h2 id=&quot;feature-transformation&quot;&gt;Feature transformation&lt;/h2&gt;&lt;h3 id=&quot;principal-component-analysis&quot;&gt;Principal component analysis&lt;/h3&gt;&lt;p&gt;Start with two pixels, three rows apart, across all 6,144 photos. They move together, with a correlation of 0.92.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-pca-two-pixels.webp&quot; alt=&quot;Scatter plot of two pixel values with PC1 along the diagonal at 96 percent and PC2 across it at 4 percent&quot; width=&quot;480&quot;&gt;
&lt;figcaption&gt;Two pixels three rows apart. The first principal component points along the diagonal and carries 96% of their joint variance.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Principal component analysis (PCA) &lt;a href=&quot;https://doi.org/10.1098/rsta.2015.0202&quot; target=&quot;_blank&quot;&gt;[5]&lt;/a&gt; finds the direction of largest variance in the data, then the direction of largest remaining variance at right angles to the first, and so on. For centred data &lt;script type=&quot;math/tex&quot;&gt;X&lt;/script&gt;, the first component is the unit vector&lt;/p&gt;&lt;script type=&quot;math/tex&quot;&gt;w_1 = \arg\max_{\lVert w \rVert = 1} \operatorname{Var}(Xw)
&lt;/script&gt;&lt;p&gt;For these two pixels, the first component alone carries 96% of the variance. With 12,288 pixels the same redundancy repeats in many directions at once.&lt;/p&gt;&lt;p&gt;With two pixels you can draw the original axes and the components on top of them, as above. With 12,288 pixels you cannot, so you plot the photos along two components instead. Each point is one photo, placed by its score on each component: the weighted sum of its pixels, &lt;script type=&quot;math/tex&quot;&gt;z_1 = x w_1&lt;/script&gt;. The scaling figure earlier is such a plot. PC1 against PC2 shows the most variation of any pair, but for these photos that is only 34% (22% and 12%, as the next figure shows), so other pairs are worth plotting too. Every axis mixes all the pixels, so it means nothing on its own. You find out what it tracks by colouring the points by a known factor, as the scaling figure does with the table, or by drawing the component as an image.&lt;/p&gt;&lt;p&gt;Each component is a weighted sum of &lt;em&gt;all&lt;/em&gt; the features. PCA needs no labels, runs fast, gives the same answer every time, and can project new samples onto the components it has learned. It is the usual first look at any new dataset.&lt;/p&gt;&lt;p&gt;The largest variance is not always the variation you care about. It may be technical, or a factor you are not studying, as the next figure shows.&lt;/p&gt;&lt;h3 id=&quot;each-component-is-an-image&quot;&gt;For images, each component is an image&lt;/h3&gt;&lt;p&gt;A component has one weight per feature. Here every feature is a pixel, so the 12,288 weights can be put back into the 64 x 64 x 3 grid the pixels came from and shown as a picture. This works only because pixels have a fixed place in a grid. For most data it does not: if the features are age, weight and blood pressure, the weights are just a list. The picture is easier to read added to the mean photo: start from the mean and add or subtract a multiple of the component, and you see what changes along it.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-pca-images.webp&quot; alt=&quot;Six columns of blurry photos; PC1 turns the table from blue to beige, PC2 and PC3 move the object, PC4 changes the light, PC6 changes its colour&quot;&gt;
&lt;figcaption&gt;The mean photo minus (top) and plus (bottom) three standard deviations of each of the first six components. Above each column: its share of the variation, and the factor that sorts the photos along it.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;The number above each column is the component’s share of all the variation in the photos. The word below it says what the component tracks. To find it, I gave every photo a score along the component and checked which of the seven factors sorts those scores best. Along PC1, the beige-table photos sit at one end and the grey-blue ones at the other, with almost no overlap, so PC1 is the table. PC2 and PC3 move the object, PC4 changes the light, and PC6 changes the object’s colour. No single factor sorts PC5; it changes colour and position together, so not every component has a clean meaning.&lt;/p&gt;&lt;p&gt;The largest source of variation in these photos is the table, which is rarely what anyone wants to study.&lt;/p&gt;&lt;h3 id=&quot;how-many-components-should-you-keep&quot;&gt;How many components should you keep?&lt;/h3&gt;&lt;p&gt;The cumulative explained variance tells you how much each additional component adds.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-pca-keep.webp&quot; alt=&quot;Cumulative variance curve reaching 50 percent at 5, 75 percent at 17 and 90 percent at 88 components, next to photos rebuilt from 5, 20 and 100 components&quot;&gt;
&lt;figcaption&gt;Left: cumulative explained variance. Right: photos rebuilt from their first k components.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Five components reach 50% of the variance, 17 reach 75%, and 88 reach 90%. So 90% of the variance of a 12,288-number photo fits in 88 numbers.&lt;/p&gt;&lt;p&gt;A variance target such as 75% or 90% is a convention. If the components feed a model, choose their number by cross-validation instead. The rebuilt photos show what the discarded components held: sharp edges and small details go first. With 20 components the position and colour of the object are already there.&lt;/p&gt;&lt;h3 id=&quot;independent-component-analysis&quot;&gt;Independent component analysis&lt;/h3&gt;&lt;p&gt;Independent component analysis (ICA) &lt;a href=&quot;https://doi.org/10.1016/S0893-6080(00)00026-5&quot; target=&quot;_blank&quot;&gt;[6]&lt;/a&gt; solves a different problem. Imagine two people talking at a party and two microphones, each recording a different mix of both voices. ICA recovers the two voices from the two recordings.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-ica.webp&quot; alt=&quot;Two source photos, two blended mixtures of them, and the two photos recovered by ICA in swapped order&quot; width=&quot;460&quot;&gt;
&lt;figcaption&gt;The cocktail party with photos. Two source photos are mixed twice with different weights. ICA recovers both, in its own order.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Here the voices are two photos. I mixed their table regions with weights 0.6/0.4 and 0.35/0.65, and ICA recovered both photos almost exactly. It returned them in swapped order, and another random seed could return the other order. Sign and scale are arbitrary too.&lt;/p&gt;&lt;p&gt;ICA looks for components that are statistically &lt;em&gt;independent&lt;/em&gt; and non-Gaussian &lt;a href=&quot;https://arxiv.org/abs/1404.2986&quot; target=&quot;_blank&quot;&gt;[7]&lt;/a&gt;. Independence is a stronger condition than the lack of correlation that PCA gives you. Independent components often line up with distinct underlying processes, even when those carry little variance. In the photos, one factor such as the light changes many pixels at once, and ICA tries to find one component per such source.&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;PCA&lt;/th&gt;&lt;th&gt;ICA&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Keeps&lt;/td&gt;&lt;td&gt;most variance&lt;/td&gt;&lt;td&gt;independent sources&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Order&lt;/td&gt;&lt;td&gt;by variance&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Good for&lt;/td&gt;&lt;td&gt;dominant effects&lt;/td&gt;&lt;td&gt;mixed signals&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;h3 id=&quot;t-sne-and-umap-neighbourhoods-first&quot;&gt;t-SNE and UMAP: neighbourhoods first&lt;/h3&gt;&lt;p&gt;PCA and ICA are linear. t-SNE &lt;a href=&quot;https://jmlr.org/papers/v9/vandermaaten08a.html&quot; target=&quot;_blank&quot;&gt;[8]&lt;/a&gt; and UMAP &lt;a href=&quot;https://arxiv.org/abs/1802.03426&quot; target=&quot;_blank&quot;&gt;[9]&lt;/a&gt; are nonlinear methods built to preserve neighbourhoods: photos that are close in the original 12,288 numbers should stay close in the 2-D map.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-embeddings.webp&quot; alt=&quot;Three scatter plots of the photos: PCA, t-SNE and UMAP, coloured by object colour with markers for shape&quot;&gt;
&lt;figcaption&gt;The same photos in three 2-D maps, drawn in the object’s colour with a marker for its shape. t-SNE uses perplexity 30, UMAP 15 neighbours, both with fixed seeds.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;To compare the three maps, I counted &lt;strong&gt;neighbours kept&lt;/strong&gt;: of each photo’s 10 nearest neighbours in the original space, the share that are still among its 10 nearest neighbours in the 2-D map. PCA keeps 29%, t-SNE 63% and UMAP 60%. The measure works for any embedding, and it is worth computing before you trust the local structure of a map.&lt;/p&gt;&lt;p&gt;PCA spreads the photos by size and colour. t-SNE and UMAP group them by colour and shape; in the UMAP map, the size of the object changes along each arm. Perplexity, t-SNE’s main setting, is roughly the number of neighbours each point attends to.&lt;/p&gt;&lt;p&gt;The two methods differ in ways that matter for how you use them:&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;t-SNE&lt;/th&gt;&lt;th&gt;UMAP&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Loss&lt;/td&gt;&lt;td&gt;KL divergence; repulsion only through normalization&lt;/td&gt;&lt;td&gt;cross-entropy with explicit attraction and repulsion&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;New samples&lt;/td&gt;&lt;td&gt;no mapping&lt;/td&gt;&lt;td&gt;can be embedded&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Output&lt;/td&gt;&lt;td&gt;2-D or 3-D for plots&lt;/td&gt;&lt;td&gt;any dimension; can feed a downstream model&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Global layout&lt;/td&gt;&lt;td&gt;distances between clusters mean little&lt;/td&gt;&lt;td&gt;keeps more of it&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Speed&lt;/td&gt;&lt;td&gt;slower&lt;/td&gt;&lt;td&gt;faster; approximate neighbours, worth checking on small data&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;Both methods find a local optimum, so a different seed can give a different layout. Run several seeds before you interpret one. A nonlinear method can follow curved structure and reveal groups that PCA smears together. You pay for that in interpretability, in stability across seeds, and for t-SNE in the ability to embed new points.&lt;/p&gt;&lt;h3 id=&quot;autoencoders&quot;&gt;Autoencoders&lt;/h3&gt;&lt;p&gt;An autoencoder is a neural network trained to copy its input through a narrow middle layer. The encoder compresses a photo &lt;script type=&quot;math/tex&quot;&gt;x&lt;/script&gt; to a small latent vector &lt;script type=&quot;math/tex&quot;&gt;z&lt;/script&gt;, and the decoder rebuilds a photo &lt;script type=&quot;math/tex&quot;&gt;\hat{x}&lt;/script&gt; from it. Training minimizes the reconstruction error &lt;script type=&quot;math/tex&quot;&gt;\lVert x - \hat{x} \rVert^2&lt;/script&gt;.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-autoencoder.svg&quot; alt=&quot;A photo of a red cube, an encoder, a column of 8 latent numbers, a decoder, and the rebuilt photo&quot;&gt;
&lt;figcaption&gt;A convolutional autoencoder with 8 latent numbers, trained for 20 epochs on all 6,144 photos. The photo on the right is rebuilt from those 8 numbers.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;The width of the latent layer sets the reduced dimension: 8 here, 2 if you want to plot it. Once trained, the encoder is a mapping, so new samples can be embedded without retraining.&lt;/p&gt;&lt;p&gt;With the same budget of 8 numbers per photo, the autoencoder rebuilds far better than PCA. Its reconstruction error is 22.7 per photo (squared error summed over the pixels), against 63.0 for PCA with 8 components.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-ae-vs-pca.webp&quot; alt=&quot;Three rows of photos: the original, the PCA reconstruction and the autoencoder reconstruction&quot; width=&quot;300&quot;&gt;
&lt;figcaption&gt;The same 8 numbers per photo. PCA blurs the object away; the autoencoder keeps it.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Architecture matters at this size. A dense first layer of 128 units on a 64 × 64 × 3 photo already has 12,288 × 128 + 128 = 1,572,992 weights and biases. The convolutional autoencoder above has 380,779 parameters in total.&lt;/p&gt;&lt;p&gt;PCA is a special case: an autoencoder with linear layers and squared loss learns a latent space that spans the top principal components.&lt;/p&gt;&lt;h3 id=&quot;variational-autoencoders&quot;&gt;Variational autoencoders&lt;/h3&gt;&lt;p&gt;A variational autoencoder (VAE) &lt;a href=&quot;https://arxiv.org/abs/1906.02691&quot; target=&quot;_blank&quot;&gt;[10]&lt;/a&gt; changes one thing. The encoder outputs a distribution, a mean &lt;script type=&quot;math/tex&quot;&gt;\mu&lt;/script&gt; and a standard deviation &lt;script type=&quot;math/tex&quot;&gt;\sigma&lt;/script&gt; for each latent number, and &lt;script type=&quot;math/tex&quot;&gt;z&lt;/script&gt; is sampled from it. The loss adds a penalty that pulls each of those distributions towards a standard normal:&lt;/p&gt;&lt;script type=&quot;math/tex&quot;&gt;\mathcal{L} = \underbrace{\lVert x - \hat{x} \rVert^2}_{\text{reconstruction}}
  + w \, \underbrace{\mathrm{KL}\big(\mathcal{N}(\mu, \sigma^2) \,\Vert\, \mathcal{N}(0, 1)\big)}_{\text{regularization}}
&lt;/script&gt;&lt;p&gt;The KL term stops the variances from shrinking to zero and the means from drifting apart. Without it, the VAE degenerates into a plain autoencoder. With it, nearby points in the latent space must decode to similar photos, so you can walk through the latent space and watch the photo change gradually.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-vae.svg&quot; alt=&quot;VAE diagram above two strips of seven decoded photos, one changing colour from cyan to red, one changing a cube into a capsule&quot;&gt;
&lt;figcaption&gt;Top: the VAE. Middle: changing only z6 changes the colour. Bottom: moving in a straight line from a red cube’s code to a red capsule’s code changes the shape.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;The weight &lt;script type=&quot;math/tex&quot;&gt;w&lt;/script&gt; sets the balance. Too small, and the latent space has little structure; too large, and the reconstructions blur. This VAE uses &lt;script type=&quot;math/tex&quot;&gt;w = 1&lt;/script&gt; and 8 latent numbers. Its reconstruction error is 27.2 per photo, a little above the plain autoencoder’s 22.7. That gap is the price of the regularization.&lt;/p&gt;&lt;p&gt;In the middle strip, only &lt;script type=&quot;math/tex&quot;&gt;z_6&lt;/script&gt; changes, from −2.5 to 2.5. Across all photos, colour explains 64% of the variance of &lt;script type=&quot;math/tex&quot;&gt;z_6&lt;/script&gt;. Other latent numbers follow the light (63%) and the position (55%). No latent number follows shape; the best reaches 10%. So the bottom strip cannot vary one number. It moves in a straight line from the red cube’s code to the code of a red capsule that matches it in every other factor, and the shape still changes smoothly.&lt;/p&gt;&lt;p&gt;Ideally a VAE on tabletop would give each of the seven factors its own latent number. This one did for colour, light and position, but not for shape, the factor that changes the fewest pixels. Whether a model can separate the factors this way is called &lt;em&gt;disentanglement&lt;/em&gt;, and it is the question tabletop was built to test.&lt;/p&gt;&lt;h3 id=&quot;choosing-a-transformation-method&quot;&gt;Choosing a transformation method&lt;/h3&gt;&lt;p&gt;Whether a method can embed new samples decides whether it can sit inside a prediction pipeline.&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Method&lt;/th&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;Embeds new samples&lt;/th&gt;&lt;th&gt;Typical use&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PCA&lt;/td&gt;&lt;td&gt;linear&lt;/td&gt;&lt;td&gt;yes&lt;/td&gt;&lt;td&gt;first exploration, quality control, preprocessing&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ICA&lt;/td&gt;&lt;td&gt;linear&lt;/td&gt;&lt;td&gt;yes&lt;/td&gt;&lt;td&gt;separating independent underlying signals&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;t-SNE&lt;/td&gt;&lt;td&gt;nonlinear&lt;/td&gt;&lt;td&gt;no&lt;/td&gt;&lt;td&gt;local structure, visualization only&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;UMAP&lt;/td&gt;&lt;td&gt;nonlinear&lt;/td&gt;&lt;td&gt;yes&lt;/td&gt;&lt;td&gt;visualization, and preprocessing for models&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Autoencoder&lt;/td&gt;&lt;td&gt;nonlinear&lt;/td&gt;&lt;td&gt;yes&lt;/td&gt;&lt;td&gt;learned representations, large data&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;h2 id=&quot;feature-aggregation&quot;&gt;Feature aggregation&lt;/h2&gt;&lt;p&gt;Aggregation replaces each group of features with one summary, usually the mean or median. Given groups &lt;script type=&quot;math/tex&quot;&gt;G_1, \dots, G_l&lt;/script&gt; of the &lt;script type=&quot;math/tex&quot;&gt;p&lt;/script&gt; features, the new feature &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt; of sample &lt;script type=&quot;math/tex&quot;&gt;i&lt;/script&gt; is&lt;/p&gt;&lt;script type=&quot;math/tex&quot;&gt;a_{ik} = \operatorname*{mean}_{j \in G_k} x_{ij}
&lt;/script&gt;&lt;p&gt;Groups may overlap, and a feature may belong to no group. Turning a colour photo into a grey one is already aggregation: three channel values become one.&lt;/p&gt;&lt;p&gt;The groups can come from two places. &lt;strong&gt;Knowledge-based&lt;/strong&gt; groups are ones you already know: blocks of neighbouring pixels, or gene sets. They are easy to interpret. &lt;strong&gt;Data-driven&lt;/strong&gt; groups come from clustering the features by how they change together. They can find structure you did not expect, but a cluster may not have a name.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-pixel-correlations.webp&quot; alt=&quot;Correlation heatmap of pixels with eight bright blocks along the diagonal&quot; width=&quot;400&quot;&gt;
&lt;figcaption&gt;Correlations between pixels, sorted into the 8 clusters found by k-medoids. Pixels in one cluster correlate 0.59 on average, against 0.17 overall.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;For the data-driven groups I clustered the 4,096 pixel positions with &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt;-medoids &lt;a href=&quot;https://doi.org/10.1002/9780470316801&quot; target=&quot;_blank&quot;&gt;[11]&lt;/a&gt;. Each cluster is summarized around its &lt;em&gt;medoid&lt;/em&gt;, a real pixel whose brightness profile best represents the group. &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt;-means would give an average profile instead, which is not any pixel you can point at.&lt;/p&gt;&lt;p&gt;The classic algorithm for &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt;-medoids is PAM, partitioning around medoids. It picks &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt; data points as the first medoids and assigns every point to the nearest one. The cost is the sum of the distances from each point to its medoid. PAM then tries swapping a medoid for another point, keeps the swap if the cost drops, undoes it if the cost rises, and stops when no swap helps. The animation runs PAM on 69 toy points in three blobs with &lt;script type=&quot;math/tex&quot;&gt;k = 3&lt;/script&gt;, few enough to follow every step.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;video muted controls&gt;
&lt;source src=&quot;https://anoopkc.com/blog/dimensionality-reduction/K-medoids.mp4&quot;&gt;
&lt;/video&gt;
&lt;figcaption&gt;k-medoids (PAM) on 69 toy points with k = 3. Each swap and each cost comes from the run itself. 78 seconds, no sound.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;The run starts at a cost of 117.2. The first swap it tries raises the cost to 218.0 and is undone. Three of the six swaps shown are kept, and they bring the cost down to 97.1, 83.7 and finally 70.8, where no swap lowers it further. In the last part of the clip six points drift away from the blue cluster. The mean, which &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt;-means would use as the centre, follows them. The medoid stays on the same point. A medoid is always one of the data points, so PAM needs only the distances between points, and any distance will do.&lt;/p&gt;&lt;p&gt;PAM tries every medoid–point swap on each pass, which is slow for 4,096 pixels. CLARA &lt;a href=&quot;https://doi.org/10.1002/9780470316801&quot; target=&quot;_blank&quot;&gt;[11]&lt;/a&gt; runs PAM on a few random samples of the pixels, assigns every pixel to the nearest medoid from each sample, and keeps the set of medoids with the lowest total cost.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-aggregation.webp&quot; alt=&quot;A red cube at full resolution, 4 by 4 blocks and 8 by 8 blocks with 99, 98 and 90 percent accuracy, next to a map of eight pixel groups&quot;&gt;
&lt;figcaption&gt;Left: averaging blocks of pixels, with accuracy on an unseen object colour. Right: the 8 pixel groups found by k-medoids; each colour becomes one new feature.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Downsampling a photo is knowledge-based aggregation: we know that neighbouring pixels belong together. To measure what it costs, I trained a logistic regression to tell cubes from spheres on three object colours and tested it on the fourth, averaged over the four colours. At full resolution it gets 99% right. Averaging 4 × 4 blocks leaves 768 features, 16 times fewer, and it still gets 98% right. With 8 × 8 blocks it drops to 90%.&lt;/p&gt;&lt;p&gt;The pixel clusters on the right range from 131 to 1,250 pixels. They are a map of pixel positions, not a photo, and without being told, they find the wall and the regions of the table where objects stand.&lt;/p&gt;&lt;h2 id=&quot;feature-selection&quot;&gt;Feature selection&lt;/h2&gt;&lt;h3 id=&quot;the-task-is-the-object-a-cube-or-a-sphere&quot;&gt;The task: is the object a cube or a sphere?&lt;/h3&gt;&lt;p&gt;Selection needs a target, so this section uses a classification task. I took the 1,024 photos with a cube or a sphere in the middle of the frame and turned them grey, leaving 4,096 pixels per photo. Grey is the mean of the three colour channels, so each object colour becomes a different shade of grey.&lt;/p&gt;&lt;p&gt;The test is whether the chosen pixels work for an object colour the model has never seen. Each fold trains on three colours (768 photos) and tests on the fourth (256 photos).&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-folds.svg&quot; alt=&quot;A four by four grid of folds and object colours with one test colour per fold&quot;&gt;
&lt;figcaption&gt;Leave-one-colour-out cross-validation. Each method is run four times, once per fold.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Holding out a colour is harder than a random split. A random split would put photos of the same object, turned by 30°, into both the training and the test set. Each method also produces four models, one per fold, and comparing them tells us how stable the selection is.&lt;/p&gt;&lt;p&gt;There are three families of selection methods &lt;a href=&quot;https://jmlr.org/papers/v3/guyon03a.html&quot; target=&quot;_blank&quot;&gt;[12]&lt;/a&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Filter&lt;/strong&gt; methods score each feature first and keep the top ones.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Wrapper&lt;/strong&gt; methods search over feature sets, scoring each set with a model.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Embedded&lt;/strong&gt; methods select features while the model trains.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;filter-methods-relieff&quot;&gt;Filter methods: ReliefF&lt;/h3&gt;&lt;p&gt;ReliefF &lt;a href=&quot;https://doi.org/10.1023/A:1025667309714&quot; target=&quot;_blank&quot;&gt;[13]&lt;/a&gt; scores each pixel by how well it separates photos that are close to each other. For each photo &lt;script type=&quot;math/tex&quot;&gt;i&lt;/script&gt;, it finds the &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt; nearest photos in each class and updates the score of every pixel &lt;script type=&quot;math/tex&quot;&gt;f&lt;/script&gt;:&lt;/p&gt;&lt;script type=&quot;math/tex&quot;&gt;\text{score}_f \mathrel{+}= \sum_{o \in \text{other}} \lvert x_{if} - x_{of} \rvert
  - \sum_{s \in \text{same}} \lvert x_{if} - x_{sf} \rvert
&lt;/script&gt;&lt;p&gt;A pixel gains score when it differs between the classes and agrees within one. Take a pixel where a cube has a corner and a sphere does not. It shows the object in cube photos and the background in sphere photos, so the first term is large, the second small, and the score high. A wall pixel looks almost the same in every photo: both terms are near zero, and so is its score.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-relieff.webp&quot; alt=&quot;A score map tracing the outline of the object, and two photos with the top 20 pixels marked near the lower edge of the object&quot; width=&quot;580&quot;&gt;
&lt;figcaption&gt;The ReliefF score map, and the top 20 pixels drawn on a cube and a sphere.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;The score map traces the outline of the cube and its shadow, which is where cubes and spheres differ. All of the top 20 pixels sit together near the lower edge of the object. They are near-copies of one pixel, and twenty copies add little over one. This redundancy is the weakness of scoring features one at a time.&lt;/p&gt;&lt;p&gt;ReliefF only proposes features. A separate classifier makes the prediction, here a decision tree on the top 20 pixels. It gets 98% right on the unseen lime and violet objects, 84% on red and 76% on cyan.&lt;/p&gt;&lt;h3 id=&quot;wrapper-methods-forward-selection&quot;&gt;Wrapper methods: forward selection&lt;/h3&gt;&lt;p&gt;Forward selection starts with no features and adds, at each step, the feature that most improves validation accuracy. To keep the search fast, the candidates here are the 256 averages of 4 × 4 pixel blocks, and the model is a decision tree. Each step tries every remaining block, trains a model on each candidate set, scores it on the three inner validation colours, and keeps the best.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-forward-selection.webp&quot; alt=&quot;A cube and a sphere with three numbered pixel blocks marked&quot; width=&quot;480&quot;&gt;
&lt;figcaption&gt;The first three blocks chosen with red held out. Block 1, at the base of the object, is picked first in all four folds.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;The number of features is now a hyperparameter, so it needs &lt;strong&gt;nested&lt;/strong&gt; cross-validation: an inner loop chooses the features, and an outer loop tests them on the held-out colour. On the unseen colour, the selected blocks give 95% on lime and violet, 90% on cyan and 78% on red.&lt;/p&gt;&lt;p&gt;Greedy search is far cheaper than trying every subset, but it can miss the best combination and overfit the validation data. Backward elimination is the mirror image: start with all features and remove one at a time.&lt;/p&gt;&lt;h3 id=&quot;embedded-methods-the-elastic-net&quot;&gt;Embedded methods: the elastic net&lt;/h3&gt;&lt;p&gt;The elastic net &lt;a href=&quot;https://doi.org/10.1111/j.1467-9868.2005.00503.x&quot; target=&quot;_blank&quot;&gt;[14]&lt;/a&gt; &lt;a href=&quot;https://doi.org/10.18637/jss.v033.i01&quot; target=&quot;_blank&quot;&gt;[15]&lt;/a&gt; fits a logistic regression with two penalties on the pixel coefficients &lt;script type=&quot;math/tex&quot;&gt;w&lt;/script&gt;:&lt;/p&gt;&lt;script type=&quot;math/tex&quot;&gt;\min_{w, c} \; C \sum_{i=1}^{n} \log\!\big(1 + e^{-y_i (x_i^\top w + c)}\big)
  + \rho \lVert w \rVert_1 + \frac{1 - \rho}{2} \lVert w \rVert_2^2
&lt;/script&gt;&lt;p&gt;The L1 penalty sets most coefficients to exactly zero, and the pixels with a non-zero coefficient are the selection. The L2 penalty keeps correlated pixels together instead of picking one of them at random. Neighbouring pixels are strongly correlated, which is the case the L2 term was designed for. This is scikit-learn’s formulation: &lt;script type=&quot;math/tex&quot;&gt;C&lt;/script&gt; multiplies the data term, so a smaller &lt;script type=&quot;math/tex&quot;&gt;C&lt;/script&gt; means a stronger penalty.&lt;/p&gt;&lt;p&gt;With &lt;script type=&quot;math/tex&quot;&gt;\rho = 0.5&lt;/script&gt; and &lt;script type=&quot;math/tex&quot;&gt;C = 0.05&lt;/script&gt;, the models get 96% to 100% right on the unseen colour while keeping about 210 of the 4,096 pixels each. Selection happens during training, so there is no separate search loop.&lt;/p&gt;&lt;h3 id=&quot;comparing-the-three-families&quot;&gt;Comparing the three families&lt;/h3&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Filter&lt;/th&gt;&lt;th&gt;Wrapper&lt;/th&gt;&lt;th&gt;Embedded&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Uses a model?&lt;/td&gt;&lt;td&gt;no&lt;/td&gt;&lt;td&gt;in the loop&lt;/td&gt;&lt;td&gt;in the objective&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cost&lt;/td&gt;&lt;td&gt;low&lt;/td&gt;&lt;td&gt;high&lt;/td&gt;&lt;td&gt;moderate&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Finds joint effects?&lt;/td&gt;&lt;td&gt;often not&lt;/td&gt;&lt;td&gt;yes&lt;/td&gt;&lt;td&gt;partly&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Picks redundant features?&lt;/td&gt;&lt;td&gt;often&lt;/td&gt;&lt;td&gt;rarely&lt;/td&gt;&lt;td&gt;in groups&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Overfitting risk&lt;/td&gt;&lt;td&gt;low&lt;/td&gt;&lt;td&gt;high&lt;/td&gt;&lt;td&gt;moderate&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;In this post&lt;/td&gt;&lt;td&gt;ReliefF&lt;/td&gt;&lt;td&gt;forward selection&lt;/td&gt;&lt;td&gt;elastic net&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;Wrappers need nested cross-validation, and embedded methods need their penalty tuned. In high dimensions a common, affordable combination is a filter to shrink the pool of features, followed by an embedded model.&lt;/p&gt;&lt;h3 id=&quot;measuring-selection-stability&quot;&gt;Measuring selection stability&lt;/h3&gt;&lt;p&gt;Accuracy tells you whether the selected features predict. It does not tell you whether you would select the same features again. With four folds we have four models, and we can compare what they chose.&lt;/p&gt;&lt;p&gt;Let model &lt;script type=&quot;math/tex&quot;&gt;j&lt;/script&gt; of &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt; select the positive predictors &lt;script type=&quot;math/tex&quot;&gt;P_j&lt;/script&gt; and the negative predictors &lt;script type=&quot;math/tex&quot;&gt;N_j&lt;/script&gt;. The intersections &lt;script type=&quot;math/tex&quot;&gt;P = P_1 \cap \dots \cap P_k&lt;/script&gt; and &lt;script type=&quot;math/tex&quot;&gt;N = N_1 \cap \dots \cap N_k&lt;/script&gt; hold the pixels that every model chose. The stability is&lt;/p&gt;&lt;script type=&quot;math/tex&quot;&gt;S = \frac{1}{2} \left( \frac{1}{k} \sum_{j=1}^{k} \frac{\lvert P \rvert}{\lvert P_j \rvert}
  + \frac{1}{k} \sum_{j=1}^{k} \frac{\lvert N \rvert}{\lvert N_j \rvert} \right)
&lt;/script&gt;&lt;p&gt;&lt;script type=&quot;math/tex&quot;&gt;S = 1&lt;/script&gt; means every model picks the same pixels. A low &lt;script type=&quot;math/tex&quot;&gt;S&lt;/script&gt; means the selection depends on the split. For the elastic net on cubes and spheres, each model keeps 203 to 218 pixels, only 146 of them appear in all four, and &lt;script type=&quot;math/tex&quot;&gt;S = 0.69&lt;/script&gt;.&lt;/p&gt;&lt;p&gt;This definition has a weakness: one unusual model can empty the intersection. Nogueira et al. &lt;a href=&quot;https://jmlr.org/papers/v18/17-514.html&quot; target=&quot;_blank&quot;&gt;[16]&lt;/a&gt; discuss measures with better properties.&lt;/p&gt;&lt;h3 id=&quot;stability-separates-what-accuracy-cannot&quot;&gt;Stability separates what accuracy cannot&lt;/h3&gt;&lt;p&gt;The next figure runs the elastic net across a range of penalties and plots accuracy and stability side by side.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/dimensionality-reduction/fig-stability.webp&quot; alt=&quot;Two panels, cube versus sphere and cylinder versus capsule, with accuracy near 1 and stability between 0.45 and 0.8 across values of C&quot;&gt;
&lt;figcaption&gt;Accuracy and selection stability of the elastic net as the penalty changes. Points are the mean over the four folds; bars span the minimum and maximum. The dashed line marks the best accuracy plus stability.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;For cubes and spheres, compare &lt;script type=&quot;math/tex&quot;&gt;C = 0.05&lt;/script&gt; and &lt;script type=&quot;math/tex&quot;&gt;C = 0.2&lt;/script&gt;. The accuracy ranges sit on top of each other, so choosing between them on accuracy alone is choosing on noise. The stability ranges do not overlap. With four folds these are ranges rather than confidence intervals, but the difference is still clear.&lt;/p&gt;&lt;p&gt;Cylinder against capsule is a harder task and less stable, with stability between 0.45 and 0.56. Choosing on accuracy plus stability picks &lt;script type=&quot;math/tex&quot;&gt;C = 0.1&lt;/script&gt; there, at almost the same accuracy (97.7% against 97.9% for the most accurate setting).&lt;/p&gt;&lt;p&gt;Stability stays well below 1 because neighbouring pixels carry nearly the same information. Many different pixel sets predict equally well, and a model has no reason to prefer one of them.&lt;/p&gt;&lt;h2 id=&quot;wrap-up&quot;&gt;Wrap-up&lt;/h2&gt;&lt;h3 id=&quot;a-starting-point-for-your-own-data&quot;&gt;A starting point for your own data&lt;/h3&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Goal&lt;/th&gt;&lt;th&gt;Try first&lt;/th&gt;&lt;th&gt;Watch out for&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;First look, quality control&lt;/td&gt;&lt;td&gt;PCA on scaled data&lt;/td&gt;&lt;td&gt;the largest variance may not be the one you care about&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Show group structure&lt;/td&gt;&lt;td&gt;t-SNE or UMAP&lt;/td&gt;&lt;td&gt;seeds, local optima&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Input for a model&lt;/td&gt;&lt;td&gt;PCA, UMAP, autoencoder&lt;/td&gt;&lt;td&gt;fit inside the cross-validation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Interpretable summaries&lt;/td&gt;&lt;td&gt;known groups (aggregation)&lt;/td&gt;&lt;td&gt;only finds the groups you know&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Which measurements matter&lt;/td&gt;&lt;td&gt;feature selection&lt;/td&gt;&lt;td&gt;nested cross-validation, stability&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;Treat the table as a place to start. Try two methods and compare them on the same data. Binary or categorical data need other methods, such as multiple correspondence analysis, logistic PCA, or nonlinear methods on a Jaccard distance.&lt;/p&gt;&lt;h3 id=&quot;what-to-take-home&quot;&gt;What to take home&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Prepare before you reduce.&lt;/strong&gt; Convert the types, flatten, and scale. On tabletop, unscaled depth hides the table from PCA.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Split first.&lt;/strong&gt; Fit scalers, reducers and selectors on the training data only.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Know what you keep.&lt;/strong&gt; Transformation mixes features, aggregation groups them, and selection keeps a subset.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Check a nonlinear map before you trust it.&lt;/strong&gt; Run several seeds and count the neighbours kept. t-SNE is for plots only; UMAP and autoencoders can also feed a model.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Report stability next to accuracy.&lt;/strong&gt; Several pixel sets can predict equally well.&lt;/li&gt;&lt;/ol&gt;&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;&lt;p&gt;The course notebooks, which use a gene-expression dataset, are at &lt;a href=&quot;https://github.com/HelmholtzAI-Consultants-Munich/DimRed-Course&quot; target=&quot;_blank&quot;&gt;github.com/HelmholtzAI-Consultants-Munich/DimRed-Course&lt;/a&gt;. The methods and code carry over to other data unchanged. The tabletop dataset is at &lt;a href=&quot;https://github.com/anoopkcn/tabletop&quot; target=&quot;_blank&quot;&gt;github.com/anoopkcn/tabletop&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;The course material is by the Helmholtz AI consultant team and licensed CC BY 4.0. tabletop is MIT-licensed.&lt;/p&gt;&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;Helmholtz AI consultant team. &lt;em&gt;Dimensionality reduction course.&lt;/em&gt; CC BY 4.0. &lt;a href=&quot;https://github.com/HelmholtzAI-Consultants-Munich/DimRed-Course&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;&lt;li&gt;A. Kaithalikunnel Chandran. &lt;em&gt;tabletop: a complete factor grid in colour, depth and words.&lt;/em&gt; v2.0.0, 2026. &lt;a href=&quot;https://github.com/anoopkcn/tabletop&quot; target=&quot;_blank&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;&lt;li&gt;K. Beyer, J. Goldstein, R. Ramakrishnan, U. Shaft. &lt;em&gt;When Is “Nearest Neighbor” Meaningful?&lt;/em&gt; ICDT 1999. &lt;a href=&quot;https://doi.org/10.1007/3-540-49257-7_15&quot; target=&quot;_blank&quot;&gt;doi:10.1007/3-540-49257-7_15&lt;/a&gt;&lt;/li&gt;&lt;li&gt;scikit-learn developers. &lt;em&gt;Pipelines and composite estimators.&lt;/em&gt; &lt;a href=&quot;https://scikit-learn.org/stable/modules/compose.html&quot; target=&quot;_blank&quot;&gt;scikit-learn.org&lt;/a&gt;&lt;/li&gt;&lt;li&gt;I. T. Jolliffe, J. Cadima. &lt;em&gt;Principal component analysis: a review and recent developments.&lt;/em&gt; Phil. Trans. R. Soc. A 374, 2016. &lt;a href=&quot;https://doi.org/10.1098/rsta.2015.0202&quot; target=&quot;_blank&quot;&gt;doi:10.1098/rsta.2015.0202&lt;/a&gt;&lt;/li&gt;&lt;li&gt;A. Hyvärinen, E. Oja. &lt;em&gt;Independent component analysis: algorithms and applications.&lt;/em&gt; Neural Networks 13, 2000. &lt;a href=&quot;https://doi.org/10.1016/S0893-6080(00)00026-5&quot; target=&quot;_blank&quot;&gt;doi:10.1016/S0893-6080(00)00026-5&lt;/a&gt;&lt;/li&gt;&lt;li&gt;J. Shlens. &lt;em&gt;A Tutorial on Independent Component Analysis.&lt;/em&gt; 2014. &lt;a href=&quot;https://arxiv.org/abs/1404.2986&quot; target=&quot;_blank&quot;&gt;arXiv:1404.2986&lt;/a&gt;&lt;/li&gt;&lt;li&gt;L. van der Maaten, G. Hinton. &lt;em&gt;Visualizing Data using t-SNE.&lt;/em&gt; JMLR 9, 2008. &lt;a href=&quot;https://jmlr.org/papers/v9/vandermaaten08a.html&quot; target=&quot;_blank&quot;&gt;jmlr.org&lt;/a&gt;&lt;/li&gt;&lt;li&gt;L. McInnes, J. Healy, J. Melville. &lt;em&gt;UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction.&lt;/em&gt; 2018. &lt;a href=&quot;https://arxiv.org/abs/1802.03426&quot; target=&quot;_blank&quot;&gt;arXiv:1802.03426&lt;/a&gt;&lt;/li&gt;&lt;li&gt;D. P. Kingma, M. Welling. &lt;em&gt;An Introduction to Variational Autoencoders.&lt;/em&gt; 2019. &lt;a href=&quot;https://arxiv.org/abs/1906.02691&quot; target=&quot;_blank&quot;&gt;arXiv:1906.02691&lt;/a&gt;&lt;/li&gt;&lt;li&gt;L. Kaufman, P. J. Rousseeuw. &lt;em&gt;Finding Groups in Data: An Introduction to Cluster Analysis.&lt;/em&gt; Wiley, 1990. &lt;a href=&quot;https://doi.org/10.1002/9780470316801&quot; target=&quot;_blank&quot;&gt;doi:10.1002/9780470316801&lt;/a&gt;&lt;/li&gt;&lt;li&gt;I. Guyon, A. Elisseeff. &lt;em&gt;An Introduction to Variable and Feature Selection.&lt;/em&gt; JMLR 3, 2003. &lt;a href=&quot;https://jmlr.org/papers/v3/guyon03a.html&quot; target=&quot;_blank&quot;&gt;jmlr.org&lt;/a&gt;&lt;/li&gt;&lt;li&gt;M. Robnik-Šikonja, I. Kononenko. &lt;em&gt;Theoretical and Empirical Analysis of ReliefF and RReliefF.&lt;/em&gt; Machine Learning 53, 2003. &lt;a href=&quot;https://doi.org/10.1023/A:1025667309714&quot; target=&quot;_blank&quot;&gt;doi:10.1023/A:1025667309714&lt;/a&gt;&lt;/li&gt;&lt;li&gt;H. Zou, T. Hastie. &lt;em&gt;Regularization and variable selection via the elastic net.&lt;/em&gt; JRSS B 67, 2005. &lt;a href=&quot;https://doi.org/10.1111/j.1467-9868.2005.00503.x&quot; target=&quot;_blank&quot;&gt;doi:10.1111/j.1467-9868.2005.00503.x&lt;/a&gt;&lt;/li&gt;&lt;li&gt;J. Friedman, T. Hastie, R. Tibshirani. &lt;em&gt;Regularization Paths for Generalized Linear Models via Coordinate Descent.&lt;/em&gt; J. Stat. Softw. 33, 2010. &lt;a href=&quot;https://doi.org/10.18637/jss.v033.i01&quot; target=&quot;_blank&quot;&gt;doi:10.18637/jss.v033.i01&lt;/a&gt;&lt;/li&gt;&lt;li&gt;S. Nogueira, K. Sechidis, G. Brown. &lt;em&gt;On the Stability of Feature Selection Algorithms.&lt;/em&gt; JMLR 18, 2018. &lt;a href=&quot;https://jmlr.org/papers/v18/17-514.html&quot; target=&quot;_blank&quot;&gt;jmlr.org&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;</description>
				<link>https://anoopkc.com/blog/dimensionality-reduction/</link>
				<pubDate>Sun, 27 Sep 2026 12:57:21 +0000</pubDate>
				<guid>https://anoopkc.com/blog/dimensionality-reduction/</guid>
			</item>
		
			<item>
				<title>Agentic workflows: how coding agents work, and where you can steer them</title>
	      <description>&lt;h1&gt;Agentic workflows: how coding agents work, and where you can steer them&lt;/h1&gt;&lt;p&gt;&lt;em&gt;This is the written version of a talk I gave in the AI-assisted coding course in August 2026. It covers the slides of the main talk; the prompt-engineering appendix is left for another post. Course notes live at &lt;a href=&quot;https://anoopkcn.github.io/ai-assisted-coding&quot; target=&quot;_blank&quot;&gt;anoopkcn.github.io/ai-assisted-coding&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;&lt;h2 id=&quot;you-type-one-sentence-what-happens-next&quot;&gt;You type one sentence. What happens next?&lt;/h2&gt;&lt;p&gt;Here is a typical exchange with a coding agent such as Claude Code:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;gt; fix the failing test

• Bash(pytest -x)
  └ FAILED tests/test_auth.py::test_login
    fixture &amp;apos;client&amp;apos; not found
• Read(tests/conftest.py)
• Edit(tests/conftest.py)  +6 lines
• Bash(pytest -x)
  └ 24 passed in 1.8s

The client fixture was never defined,
so pytest could not inject it. I added
it to tests/conftest.py; all tests pass.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You typed one sentence, and the agent ran the tests twice, read a file, edited it and explained the fix. Four questions hide in that exchange:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;The model.&lt;/strong&gt; How did it pick each word?&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The context.&lt;/strong&gt; How did it know your code?&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The loop.&lt;/strong&gt; How did it run &lt;code&gt;pytest&lt;/code&gt;?&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The harness.&lt;/strong&gt; Who let it edit files?&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Each question maps to a layer, and the layers nest. The model sits at the centre. The context is everything the model sees. The loop lets the model act on the world. The harness is the program wrapped around all of it. The rest of this post walks outward from the centre, one layer at a time.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-layers.svg&quot; alt=&quot;Four concentric rings labelled model, context, loop and harness&quot;&gt;
&lt;figcaption&gt;The four layers of a coding agent. Each section of this post covers one ring.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;h2 id=&quot;the-model-how-is-the-next-token-chosen&quot;&gt;1. The model: how is the next token chosen?&lt;/h2&gt;&lt;h3 id=&quot;a-fast-pair-programmer-with-no-judgment&quot;&gt;A fast pair programmer with no judgment&lt;/h3&gt;&lt;p&gt;Think of the model as a fast, well-read colleague who has never seen your project.&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;What it brings&lt;/th&gt;&lt;th&gt;What you bring&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Broad knowledge of languages and libraries&lt;/td&gt;&lt;td&gt;Direction: what to build and why&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A first draft in seconds&lt;/td&gt;&lt;td&gt;Judgment: reviewing the changes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Patience for boilerplate&lt;/td&gt;&lt;td&gt;The final call on what ships&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;Under the hood the model predicts text &lt;strong&gt;one token at a time&lt;/strong&gt;, by probability. It does not “understand” code the way you do.&lt;/p&gt;&lt;h3 id=&quot;tokens-the-units-a-model-reads-and-writes&quot;&gt;Tokens: the units a model reads and writes&lt;/h3&gt;&lt;p&gt;A &lt;strong&gt;token&lt;/strong&gt; is a chunk of text that the model treats as a single unit. Common words are often one token. Rarer words, identifiers and punctuation get split into several.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-tokens.svg&quot; alt=&quot;Four strings split into coloured token chunks, with token counts of 1, 4, 4 and 7&quot;&gt;
&lt;figcaption&gt;How a few strings might be split into tokens.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;A common rule of thumb is that &lt;strong&gt;one token is about four characters of English prose&lt;/strong&gt; &lt;a href=&quot;https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them&quot; target=&quot;_blank&quot;&gt;[22]&lt;/a&gt;. Two caveats matter for programmers:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Code costs &lt;strong&gt;more tokens per character&lt;/strong&gt; than prose. Symbols, brackets and camelCase identifiers split into many small pieces.&lt;/li&gt;&lt;li&gt;Each model family has its own tokenizer, so the same file can have noticeably different counts across models.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;why-tokens-matter&quot;&gt;Why tokens matter&lt;/h3&gt;&lt;p&gt;Tokens are the unit of both cost and capacity.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Cost: you pay per token.&lt;/strong&gt; Input and output tokens are billed separately, and output tokens cost several times more than input. Pasting a whole file to fix one function wastes input tokens. Asking for a full rewrite where a targeted edit would do wastes output tokens.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Capacity: a fixed budget.&lt;/strong&gt; The system prompt, tool definitions, messages, files and tool output all share the same context window. The model is also &lt;strong&gt;stateless&lt;/strong&gt;: the whole conversation is re-sent on every request (more on that in section 2). Long sessions fill the window fast.&lt;/p&gt;&lt;h3 id=&quot;how-the-next-token-is-picked&quot;&gt;How the next token is picked&lt;/h3&gt;&lt;p&gt;At every step the model assigns a score (a &lt;em&gt;logit&lt;/em&gt;) &lt;script type=&quot;math/tex&quot;&gt;z_i&lt;/script&gt; to every token in its vocabulary. The scores are turned into probabilities with a softmax, and one token is &lt;strong&gt;sampled&lt;/strong&gt; from that distribution. The &lt;strong&gt;temperature&lt;/strong&gt; &lt;script type=&quot;math/tex&quot;&gt;T&lt;/script&gt; rescales the scores before the softmax:&lt;/p&gt;&lt;script type=&quot;math/tex&quot;&gt;p_i = \frac{e^{z_i / T}}{\sum_j e^{z_j / T}}
&lt;/script&gt;&lt;p&gt;With &lt;script type=&quot;math/tex&quot;&gt;T = 1&lt;/script&gt; you get the model’s own distribution. A lower temperature sharpens it: the top token gets more of the mass. As &lt;script type=&quot;math/tex&quot;&gt;T \to 0&lt;/script&gt; the top token always wins, which is &lt;em&gt;greedy decoding&lt;/em&gt;. A higher temperature flattens the distribution, so less likely tokens get picked more often.&lt;/p&gt;&lt;p&gt;The figure takes the prompt &lt;em&gt;“The animal that barks is the ___”&lt;/em&gt; and five candidate tokens. It starts from the probabilities at &lt;script type=&quot;math/tex&quot;&gt;T = 1&lt;/script&gt; and applies the formula above to get the other two panels.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-temperature.svg&quot; alt=&quot;Three bar charts of next-token probabilities for dog, cat, wolf, fox and car at temperatures 0.5, 1 and 2&quot;&gt;
&lt;figcaption&gt;Temperature reshapes the same distribution: lower is more focused, higher gives more surprises.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;At &lt;script type=&quot;math/tex&quot;&gt;T = 0.5&lt;/script&gt;, &lt;code&gt;dog&lt;/code&gt; gets 88% of the mass. At &lt;script type=&quot;math/tex&quot;&gt;T = 2&lt;/script&gt; it drops to 39%, and even &lt;code&gt;car&lt;/code&gt; gets picked about one time in nine.&lt;/p&gt;&lt;h3 id=&quot;temperature-match-it-to-the-task&quot;&gt;Temperature: match it to the task&lt;/h3&gt;&lt;p&gt;The right temperature depends on what you want from the model:&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Task&lt;/th&gt;&lt;th&gt;Temperature&lt;/th&gt;&lt;th&gt;Why&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Code generation&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;Consistency and correctness&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Bug fixing&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;The most probable fix&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Code review&lt;/td&gt;&lt;td&gt;0 – 0.3&lt;/td&gt;&lt;td&gt;Surface different issues&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Documentation&lt;/td&gt;&lt;td&gt;0.3 – 0.5&lt;/td&gt;&lt;td&gt;Some natural variety&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Brainstorming&lt;/td&gt;&lt;td&gt;0.7 – 1.0&lt;/td&gt;&lt;td&gt;Diverse ideas&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;Coding tools such as Claude Code and Cursor set these values for you. You only deal with them when you call the API directly.&lt;/p&gt;&lt;h3 id=&quot;top-p-and-top-k-drop-the-tail-then-sample&quot;&gt;Top-p and top-k: drop the tail, then sample&lt;/h3&gt;&lt;p&gt;Temperature reshapes the whole distribution. Two other knobs cut off its unlikely &lt;strong&gt;tail&lt;/strong&gt; before sampling:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Top-p&lt;/strong&gt; (nucleus sampling) &lt;a href=&quot;https://arxiv.org/abs/1904.09751&quot; target=&quot;_blank&quot;&gt;[1]&lt;/a&gt; keeps the smallest set of tokens whose probabilities add up to at least &lt;script type=&quot;math/tex&quot;&gt;p&lt;/script&gt;. Because it works on cumulative probability, it adapts to the shape of the distribution: it keeps few tokens when the model is confident and more when it is unsure.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Top-k&lt;/strong&gt; &lt;a href=&quot;https://arxiv.org/abs/1805.04833&quot; target=&quot;_blank&quot;&gt;[2]&lt;/a&gt; always keeps the &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt; most likely tokens, however the probability is spread.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The surviving tokens are renormalized and one is sampled. In the figure below, &lt;code&gt;car&lt;/code&gt; is never drawn under either rule.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-topp-topk.svg&quot; alt=&quot;Three stacked horizontal bars showing which tokens top-p 0.9 and top-k 2 keep&quot;&gt;
&lt;figcaption&gt;Top-p = 0.9 keeps dog, cat, wolf and fox, since together they reach 0.95. Top-k = 2 keeps only dog and cat.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;h3 id=&quot;model-knobs-cheat-sheet&quot;&gt;Model knobs: cheat sheet&lt;/h3&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Knob&lt;/th&gt;&lt;th&gt;Default&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;temperature&lt;/code&gt;&lt;/td&gt;&lt;td&gt;1.0&lt;/td&gt;&lt;td&gt;0 for code. Change this &lt;em&gt;or&lt;/em&gt; &lt;code&gt;top_p&lt;/code&gt;, not both&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;top_p&lt;/code&gt;&lt;/td&gt;&lt;td&gt;1.0 (off)&lt;/td&gt;&lt;td&gt;0.9 – 0.95 when you want controlled variety&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;top_k&lt;/code&gt;&lt;/td&gt;&lt;td&gt;off&lt;/td&gt;&lt;td&gt;Rarely exposed by coding tools&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;max_tokens&lt;/code&gt;&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;Hard cap on the response length&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;stop_sequences&lt;/code&gt;&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;Strings that end generation early&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;A clearer prompt changes the output far more than any of these settings.&lt;/p&gt;&lt;p&gt;Newer reasoning models drop the sampling knobs altogether: Claude Opus 4.7 and later &lt;strong&gt;reject&lt;/strong&gt; them. You set an &lt;strong&gt;effort&lt;/strong&gt; level instead, which controls how much the model thinks before it answers &lt;a href=&quot;https://docs.claude.com/en/docs/build-with-claude/effort&quot; target=&quot;_blank&quot;&gt;[3]&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;the-context-what-does-the-model-see&quot;&gt;2. The context: what does the model see?&lt;/h2&gt;&lt;h3 id=&quot;the-context-window-is-working-memory&quot;&gt;The context window is working memory&lt;/h3&gt;&lt;p&gt;The &lt;strong&gt;context window&lt;/strong&gt; is everything the model can “see” at once: its instructions, the tool definitions and the whole conversation so far. Whatever is left over is free space &lt;a href=&quot;https://docs.claude.com/en/docs/build-with-claude/context-windows&quot; target=&quot;_blank&quot;&gt;[4]&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-context-window.svg&quot; alt=&quot;Stacked bar: system 12 percent, tools 18 percent, conversation 40 percent, free space 30 percent&quot;&gt;
&lt;figcaption&gt;A typical mid-session breakdown of a coding agent’s context. These colours mean the same thing in every figure that follows.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Two parts are sent with every request: the &lt;strong&gt;system prompt&lt;/strong&gt; and the &lt;strong&gt;tool definitions&lt;/strong&gt;. Together they can take close to a third of the window before you type a word. The conversation (your messages, the model’s replies, and above all the tool calls and their results) grows every turn. The remainder is headroom.&lt;/p&gt;&lt;h3 id=&quot;the-model-has-no-memory-between-calls&quot;&gt;The model has no memory between calls&lt;/h3&gt;&lt;p&gt;The model keeps nothing between requests. The harness stores the list of messages and &lt;strong&gt;re-sends all of it&lt;/strong&gt; on every request. Each answer is appended and goes out again with the next request.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-stateless.svg&quot; alt=&quot;Three requests, each longer than the last, showing earlier messages re-sent&quot;&gt;
&lt;figcaption&gt;Every request carries the full history. The system prompt and tool definitions form a stable prefix.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;This has three consequences:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Quadratic cost.&lt;/strong&gt; If every turn re-sends everything before it, the total number of input tokens after &lt;script type=&quot;math/tex&quot;&gt;n&lt;/script&gt; turns grows roughly as &lt;script type=&quot;math/tex&quot;&gt;n^2&lt;/script&gt;. Say the prefix is &lt;script type=&quot;math/tex&quot;&gt;P&lt;/script&gt; tokens and each turn adds &lt;script type=&quot;math/tex&quot;&gt;d&lt;/script&gt; tokens. Then request &lt;script type=&quot;math/tex&quot;&gt;k&lt;/script&gt; carries &lt;script type=&quot;math/tex&quot;&gt;P + kd&lt;/script&gt; tokens, and after &lt;script type=&quot;math/tex&quot;&gt;n&lt;/script&gt; turns you have sent&lt;/p&gt;&lt;script type=&quot;math/tex&quot;&gt;\sum_{k=1}^{n} (P + kd) = nP + d\,\frac{n(n+1)}{2}
&lt;/script&gt;&lt;p&gt;tokens in total.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-quadratic.svg&quot; alt=&quot;Line chart: cumulative tokens re-sent in full grow quadratically to 4.1 million after 40 turns; new tokens alone grow linearly to 180 thousand&quot;&gt;
&lt;figcaption&gt;Illustrative numbers: after 40 turns the harness has sent about 23 times more input tokens than the conversation actually contains.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Tool traffic dominates.&lt;/strong&gt; Tool calls and their results are messages too, and they pile up. A single test run or &lt;code&gt;grep&lt;/code&gt; can return thousands of tokens, and those tokens then ride along on every later request.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Keep the prefix stable.&lt;/strong&gt; Providers cache the unchanged start of a request. With prompt caching, a repeated prefix is billed at a fraction of the normal input price and processed faster &lt;a href=&quot;https://docs.claude.com/en/docs/build-with-claude/prompt-caching&quot; target=&quot;_blank&quot;&gt;[5]&lt;/a&gt;. The cache matches byte for byte, so anything that changes the prefix (an edited system prompt, a reordered tool list, a timestamp) invalidates it. Put stable content first and keep it fixed.&lt;/p&gt;&lt;h3 id=&quot;message-roles&quot;&gt;Message roles&lt;/h3&gt;&lt;p&gt;Every request has two fields that ride along each time, then a list of messages:&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Part&lt;/th&gt;&lt;th&gt;What it holds&lt;/th&gt;&lt;th&gt;Note&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;System prompt&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Persistent instructions: persona, rules, constraints&lt;/td&gt;&lt;td&gt;A separate field, not a message. Sent first, every time&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Tool metadata&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Name, description and JSON schema of every tool&lt;/td&gt;&lt;td&gt;Sent on every request, whether it is used or not&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;User&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Your prompt, &lt;em&gt;and also the results of tool calls&lt;/em&gt;&lt;/td&gt;&lt;td&gt;Tool results are observations, not instructions&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Assistant&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;The model’s earlier replies: text and tool &lt;em&gt;calls&lt;/em&gt;&lt;/td&gt;&lt;td&gt;Re-sent because the model is stateless&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;h3 id=&quot;anatomy-of-one-request&quot;&gt;Anatomy of one request&lt;/h3&gt;&lt;p&gt;This is what the harness sends to the API &lt;a href=&quot;https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview&quot; target=&quot;_blank&quot;&gt;[12]&lt;/a&gt;, slightly trimmed:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;json&quot;&gt;{
  &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;model&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;claude-opus-5&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;system&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;You are a coding assistant. Prefer small, tested changes.&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;tools&amp;quot;&lt;/span&gt;: [{&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;read_file&amp;quot;&lt;/span&gt;,
             &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;description&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;Read a file from the repository.&amp;quot;&lt;/span&gt;,
             &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;input_schema&amp;quot;&lt;/span&gt;: {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;object&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;required&amp;quot;&lt;/span&gt;: [&lt;span class=&quot;string&quot;&gt;&amp;quot;path&amp;quot;&lt;/span&gt;],
                              &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;properties&amp;quot;&lt;/span&gt;: {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;path&amp;quot;&lt;/span&gt;: {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;string&amp;quot;&lt;/span&gt;}}}}],
  &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;messages&amp;quot;&lt;/span&gt;: [
    {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;role&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;user&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;Why does test_auth fail?&amp;quot;&lt;/span&gt;},
    {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;role&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;assistant&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;: [{&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;tool_use&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;t1&amp;quot;&lt;/span&gt;,
        &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;read_file&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;input&amp;quot;&lt;/span&gt;: {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;path&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;tests/test_auth.py&amp;quot;&lt;/span&gt;}}]},
    {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;role&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;user&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;: [{&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;tool_result&amp;quot;&lt;/span&gt;,
        &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;tool_use_id&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;t1&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;def test_login(client): ...&amp;quot;&lt;/span&gt;}]},
    {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;role&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;assistant&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;The fixture `client` is never...&amp;quot;&lt;/span&gt;}
  ]}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reading it top to bottom: the system prompt, the tool metadata, your prompt, a tool call &lt;em&gt;from the model&lt;/em&gt;, the tool result &lt;em&gt;from the harness&lt;/em&gt;, and the answer. All of it is re-sent next turn. The tool result arrives as a &lt;strong&gt;user&lt;/strong&gt; message, and the model treats it as something it observed rather than a request from you. That matters when a file or web page contains text that looks like a command.&lt;/p&gt;&lt;h3 id=&quot;agents-md-instructions-loaded-every-time&quot;&gt;AGENTS.md: instructions loaded every time&lt;/h3&gt;&lt;p&gt;Most harnesses let you keep a project file that is injected into the system prompt on every request. It is the place for what the agent should always know:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;md&quot;&gt;&lt;span class=&quot;punctuation_special&quot;&gt;#&lt;/span&gt; &lt;span class=&quot;text_title&quot;&gt;orders-api&lt;/span&gt;

&lt;span class=&quot;punctuation_special&quot;&gt;##&lt;/span&gt; &lt;span class=&quot;text_title&quot;&gt;Commands&lt;/span&gt;
&lt;span class=&quot;punctuation_special&quot;&gt;- &lt;/span&gt;Test: uv run pytest -x
&lt;span class=&quot;punctuation_special&quot;&gt;- &lt;/span&gt;Lint: uv run ruff check --fix

&lt;span class=&quot;punctuation_special&quot;&gt;##&lt;/span&gt; &lt;span class=&quot;text_title&quot;&gt;Conventions&lt;/span&gt;
&lt;span class=&quot;punctuation_special&quot;&gt;- &lt;/span&gt;Python 3.12, type hints everywhere
&lt;span class=&quot;punctuation_special&quot;&gt;- &lt;/span&gt;Business logic in src/services/
&lt;span class=&quot;punctuation_special&quot;&gt;- &lt;/span&gt;Never edit files under vendor/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The file name depends on the tool:&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Tool&lt;/th&gt;&lt;th&gt;File&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Open standard &lt;a href=&quot;https://agents.md&quot; target=&quot;_blank&quot;&gt;[10]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Claude Code &lt;a href=&quot;https://code.claude.com/docs/en/memory&quot; target=&quot;_blank&quot;&gt;[11]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cursor&lt;/td&gt;&lt;td&gt;&lt;code&gt;.cursor/rules/*.mdc&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GitHub Copilot&lt;/td&gt;&lt;td&gt;&lt;code&gt;.github/copilot-instructions.md&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;Treat it like any other config:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Version-control it.&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Keep it short.&lt;/strong&gt; Every request pays for every line.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Put the most important rules first.&lt;/strong&gt;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Prune stale instructions regularly.&lt;/strong&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;advertised-context-is-not-usable-context&quot;&gt;Advertised context is not usable context&lt;/h3&gt;&lt;p&gt;Model cards advertise context windows of 128K, 200K or a million tokens. How much of that can a model actually &lt;em&gt;use&lt;/em&gt;? The RULER benchmark &lt;a href=&quot;https://arxiv.org/abs/2404.06654&quot; target=&quot;_blank&quot;&gt;[6]&lt;/a&gt; measures this. It defines a model’s &lt;strong&gt;effective length&lt;/strong&gt; as the longest input at which it still scores above a fixed baseline on retrieval, multi-hop tracing and aggregation tasks. The long-context survey by Liu et al. &lt;a href=&quot;https://arxiv.org/abs/2503.17407&quot; target=&quot;_blank&quot;&gt;[7]&lt;/a&gt; collects those numbers for a range of models:&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-effective-context.svg&quot; alt=&quot;Horizontal bar chart comparing claimed and effective context length for 17 models&quot;&gt;
&lt;figcaption&gt;Claimed versus effective context length. Log scale, so every gridline is a factor of ten.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Most models hold up for only &lt;strong&gt;12 – 50%&lt;/strong&gt; of their advertised length, and some for under 2%. These are 2024 models, and newer ones do better. When you budget a long session, use the effective number.&lt;/p&gt;&lt;h3 id=&quot;when-the-window-fills-up&quot;&gt;When the window fills up&lt;/h3&gt;&lt;p&gt;Quality drops well before the window is full, in three ways:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Lost in the middle.&lt;/strong&gt; Models use information at the start and end of a long context better than information in the middle &lt;a href=&quot;https://arxiv.org/abs/2307.03172&quot; target=&quot;_blank&quot;&gt;[8]&lt;/a&gt;. As the window fills, details mid-context get overlooked.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Autocompaction.&lt;/strong&gt; Near the limit, the harness summarizes older turns. You lose detail, references break, and the agent repeats work it has already done.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Diluted rules.&lt;/strong&gt; Conflicting instructions compete, and key requirements fade.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-window-fill.svg&quot; alt=&quot;Context bar filled to about 55 percent, with a highlighted 50 to 60 percent band and an autocompaction marker near 92 percent&quot;&gt;
&lt;figcaption&gt;A practical threshold: when a session reaches half to 60% of the window, start a fresh one rather than wait for autocompaction.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;h3 id=&quot;compaction-summarize-then-drop&quot;&gt;Compaction: summarize, then drop&lt;/h3&gt;&lt;p&gt;Near the limit the harness swaps older turns for a &lt;strong&gt;summary&lt;/strong&gt;. The system prompt, tool definitions and the most recent turns stay as they are.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-compaction.svg&quot; alt=&quot;Two context bars: before, full of messages; after, system, tools, a summary block and a few recent messages&quot;&gt;
&lt;figcaption&gt;Before compaction, the window is full of old turns. After, a summary replaces them.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;What survives and what is lost:&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Survives&lt;/th&gt;&lt;th&gt;Lost&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Facts the summary mentions&lt;/td&gt;&lt;td&gt;Exact output, error messages, line numbers&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;The most recent turns&lt;/td&gt;&lt;td&gt;Earlier reasoning and rejected options&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Anything on disk&lt;/td&gt;&lt;td&gt;Constraints you stated only once&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;The last row of the right column is the dangerous one. If a constraint matters, write it into &lt;code&gt;AGENTS.md&lt;/code&gt; or a file on disk, or restate it after compaction.&lt;/p&gt;&lt;h3 id=&quot;cheaper-levers-than-compaction&quot;&gt;Cheaper levers than compaction&lt;/h3&gt;&lt;p&gt;Compaction is the last resort. There are cheaper ways to keep the window small &lt;a href=&quot;https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents&quot; target=&quot;_blank&quot;&gt;[9]&lt;/a&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Truncate output.&lt;/strong&gt; The harness caps long tool results. You can do it too: &lt;code&gt;tail&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;--quiet&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Clear old results.&lt;/strong&gt; Keep the tool call but drop its bulky result once it has been used.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Delegate.&lt;/strong&gt; Run noisy work in a subagent; only its conclusion comes back (see section 4).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Persist to files.&lt;/strong&gt; Plans and notes on disk survive compaction.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Load fewer tools.&lt;/strong&gt; Every tool definition is sent every turn. Remove MCP servers you do not use.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Start fresh.&lt;/strong&gt; The only lossless option: you choose what carries over.&lt;/li&gt;&lt;/ul&gt;&lt;h3 id=&quot;context-hygiene&quot;&gt;Context hygiene&lt;/h3&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Do&lt;/th&gt;&lt;th&gt;Don’t&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;/clear&lt;/code&gt; between tasks&lt;/td&gt;&lt;td&gt;Paste whole files or logs&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Point at files instead of pasting them&lt;/td&gt;&lt;td&gt;Drift across unrelated tasks in one session&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Keep &lt;code&gt;AGENTS.md&lt;/code&gt; short and ordered&lt;/td&gt;&lt;td&gt;Load MCP servers you won’t use&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Send noisy work to a subagent&lt;/td&gt;&lt;td&gt;Rely on the model remembering old turns&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Filter command output&lt;/td&gt;&lt;td&gt;Wait for autocompaction&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Restate key constraints after compaction&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;h2 id=&quot;the-loop-how-does-a-model-take-action&quot;&gt;3. The loop: how does a model take action?&lt;/h2&gt;&lt;h3 id=&quot;tools-the-model-asks-the-harness-acts&quot;&gt;Tools: the model asks, the harness acts&lt;/h3&gt;&lt;p&gt;A model can only produce text. It cannot run a test, read a file or edit code. &lt;strong&gt;Tools&lt;/strong&gt; bridge that gap in three steps &lt;a href=&quot;https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview&quot; target=&quot;_blank&quot;&gt;[12]&lt;/a&gt;:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1. Definition&lt;/strong&gt; (sent with every request). The harness describes each tool: a name, a description and a JSON schema for its input.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;json&quot;&gt;{&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;run_tests&amp;quot;&lt;/span&gt;,
 &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;description&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;Run the test suite. Call after every code change.&amp;quot;&lt;/span&gt;,
 &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;input_schema&amp;quot;&lt;/span&gt;: {
   &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;object&amp;quot;&lt;/span&gt;,
   &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;properties&amp;quot;&lt;/span&gt;: {
     &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;path&amp;quot;&lt;/span&gt;: {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;string&amp;quot;&lt;/span&gt;}}}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. Call&lt;/strong&gt; (model to harness). Instead of plain text, the model emits a structured request to use a tool.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;json&quot;&gt;{&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;tool_use&amp;quot;&lt;/span&gt;,
 &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;id&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;t1&amp;quot;&lt;/span&gt;,
 &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;run_tests&amp;quot;&lt;/span&gt;,
 &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;input&amp;quot;&lt;/span&gt;: {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;path&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;tests/&amp;quot;&lt;/span&gt;}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. Result&lt;/strong&gt; (harness to model). The harness runs the tool and sends the output back, tagged with the same id.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;json&quot;&gt;{&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;tool_result&amp;quot;&lt;/span&gt;,
 &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;tool_use_id&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;t1&amp;quot;&lt;/span&gt;,
 &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;3 passed, 1 failed ...&amp;quot;&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The model &lt;strong&gt;never runs code&lt;/strong&gt;. It emits a structured request; the harness runs it and feeds the result back as text. The description is also &lt;strong&gt;a prompt&lt;/strong&gt;: it tells the model &lt;em&gt;when&lt;/em&gt; and &lt;em&gt;how&lt;/em&gt; to use the tool. “Call after every code change” in the definition above is an instruction, and the model will follow it.&lt;/p&gt;&lt;p&gt;Typical tools in a coding agent: &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;edit&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;glob&lt;/code&gt;, &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;web fetch&lt;/code&gt; and &lt;code&gt;ask user&lt;/code&gt;.&lt;/p&gt;&lt;h3 id=&quot;the-agent-loop&quot;&gt;The agent loop&lt;/h3&gt;&lt;p&gt;Tool use on its own is one step. Put it in a loop and you get an agent &lt;a href=&quot;https://www.anthropic.com/engineering/building-effective-agents&quot; target=&quot;_blank&quot;&gt;[13]&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-agent-loop.svg&quot; alt=&quot;Cycle of three boxes: Think, Act, Observe, with an exit arrow labelled done: respond&quot;&gt;
&lt;figcaption&gt;Tool use plus a loop is agentic coding.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;The model thinks about what to do next, acts by calling a tool, and observes the result. If it is not done, it goes round again. That is how it finishes multi-step tasks on its own: search, read, edit, run the tests, and repeat.&lt;/p&gt;&lt;p&gt;How does the loop know when to stop? “Done” means the model replied &lt;strong&gt;without&lt;/strong&gt; requesting a tool. The API signals this with &lt;code&gt;stop_reason = &amp;quot;end_turn&amp;quot;&lt;/code&gt; instead of &lt;code&gt;&amp;quot;tool_use&amp;quot;&lt;/code&gt;.&lt;/p&gt;&lt;h3 id=&quot;one-turn-step-by-step&quot;&gt;One turn, step by step&lt;/h3&gt;&lt;p&gt;Here is the opening example again, as a sequence of messages between you, the harness, the model and your machine:&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-sequence.svg&quot; alt=&quot;Sequence diagram between You, Harness, Model and Shell, with an agent loop of tool calls&quot;&gt;
&lt;figcaption&gt;One user turn can contain many model calls. The harness sits in the middle of every exchange.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;You send one message. The harness sends the system prompt, the tools and the history to the model. The model asks for &lt;code&gt;bash(&amp;quot;pytest -x&amp;quot;)&lt;/code&gt;. The harness checks permissions, runs the command, truncates the output and sends it back. The model asks for an edit, and so on. The loop repeats until the model answers in plain text, and the harness passes that text back to you.&lt;/p&gt;&lt;h3 id=&quot;a-complete-agent-in-about-20-lines&quot;&gt;A complete agent in about 20 lines&lt;/h3&gt;&lt;p&gt;This is small enough to write out in full. With the Anthropic Python SDK:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;subprocess&lt;/span&gt;, &lt;span class=&quot;constructor constant variable&quot;&gt;anthropic&lt;/span&gt;
&lt;span class=&quot;constructor constant variable&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;anthropic&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;Anthropic&lt;/span&gt;()
&lt;span class=&quot;constructor constant variable&quot;&gt;TOOLS&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; [{&lt;span class=&quot;string&quot;&gt;&amp;quot;name&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;bash&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;description&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;Run a shell command and return its output.&amp;quot;&lt;/span&gt;,
          &lt;span class=&quot;string&quot;&gt;&amp;quot;input_schema&amp;quot;&lt;/span&gt;: {&lt;span class=&quot;string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;object&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;required&amp;quot;&lt;/span&gt;: [&lt;span class=&quot;string&quot;&gt;&amp;quot;cmd&amp;quot;&lt;/span&gt;],
                           &lt;span class=&quot;string&quot;&gt;&amp;quot;properties&amp;quot;&lt;/span&gt;: {&lt;span class=&quot;string&quot;&gt;&amp;quot;cmd&amp;quot;&lt;/span&gt;: {&lt;span class=&quot;string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;string&amp;quot;&lt;/span&gt;}}}}]
&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;constructor constant variable function&quot;&gt;run_bash&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;cmd&lt;/span&gt;):                                    &lt;span class=&quot;comment&quot;&gt;# the tool: no checks, no limits&lt;/span&gt;
    &lt;span class=&quot;constructor constant variable&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;subprocess&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;run&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;cmd&lt;/span&gt;, &lt;span class=&quot;constructor constant variable&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;constant_builtin&quot;&gt;True&lt;/span&gt;, &lt;span class=&quot;constructor constant variable&quot;&gt;capture_output&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;constant_builtin&quot;&gt;True&lt;/span&gt;, &lt;span class=&quot;constructor constant variable&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;constant_builtin&quot;&gt;True&lt;/span&gt;)
    &lt;span class=&quot;keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;p&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;stdout&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;p&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;stderr&lt;/span&gt;
&lt;span class=&quot;constructor constant variable&quot;&gt;messages&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; [{&lt;span class=&quot;string&quot;&gt;&amp;quot;role&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;user&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;: &lt;span class=&quot;constructor function_builtin constant variable function&quot;&gt;input&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;gt; &amp;quot;&lt;/span&gt;)}]
&lt;span class=&quot;keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;constant_builtin&quot;&gt;True&lt;/span&gt;:                                           &lt;span class=&quot;comment&quot;&gt;# the agent loop&lt;/span&gt;
    &lt;span class=&quot;constructor constant variable&quot;&gt;resp&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;client&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;messages&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;create&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;claude-opus-5&amp;quot;&lt;/span&gt;, &lt;span class=&quot;constructor constant variable&quot;&gt;max_tokens&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;number&quot;&gt;16000&lt;/span&gt;, &lt;span class=&quot;constructor constant variable&quot;&gt;tools&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;constructor constant variable&quot;&gt;TOOLS&lt;/span&gt;,
                                  &lt;span class=&quot;constructor constant variable&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;You are a coding agent.&amp;quot;&lt;/span&gt;, &lt;span class=&quot;constructor constant variable&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;constructor constant variable&quot;&gt;messages&lt;/span&gt;)
    &lt;span class=&quot;constructor constant variable&quot;&gt;messages&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;append&lt;/span&gt;({&lt;span class=&quot;string&quot;&gt;&amp;quot;role&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;assistant&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;: &lt;span class=&quot;constructor constant variable&quot;&gt;resp&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;content&lt;/span&gt;})
    &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;resp&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;stop_reason&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;tool_use&amp;quot;&lt;/span&gt;:                &lt;span class=&quot;comment&quot;&gt;# no tool requested: done&lt;/span&gt;
        &lt;span class=&quot;constructor function_builtin constant variable function&quot;&gt;print&lt;/span&gt;(&lt;span class=&quot;constructor function_builtin constant variable function&quot;&gt;next&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;b&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;resp&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;b&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;text&amp;quot;&lt;/span&gt;)); &lt;span class=&quot;keyword&quot;&gt;break&lt;/span&gt;
    &lt;span class=&quot;constructor constant variable&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;=&lt;/span&gt; [{&lt;span class=&quot;string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;tool_result&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;tool_use_id&amp;quot;&lt;/span&gt;: &lt;span class=&quot;constructor constant variable&quot;&gt;b&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;id&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;: &lt;span class=&quot;constructor function_builtin constant variable function&quot;&gt;run_bash&lt;/span&gt;(&lt;span class=&quot;constructor constant variable&quot;&gt;b&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;input&lt;/span&gt;[&lt;span class=&quot;string&quot;&gt;&amp;quot;cmd&amp;quot;&lt;/span&gt;])}
               &lt;span class=&quot;keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;resp&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;constructor constant variable&quot;&gt;b&lt;/span&gt;.&lt;span class=&quot;constructor property constant variable&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;tool_use&amp;quot;&lt;/span&gt;]
    &lt;span class=&quot;constructor constant variable&quot;&gt;messages&lt;/span&gt;.&lt;span class=&quot;constructor property constant function_method variable&quot;&gt;append&lt;/span&gt;({&lt;span class=&quot;string&quot;&gt;&amp;quot;role&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;user&amp;quot;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&amp;quot;content&amp;quot;&lt;/span&gt;: &lt;span class=&quot;constructor constant variable&quot;&gt;results&lt;/span&gt;})    &lt;span class=&quot;comment&quot;&gt;# results go back as a user turn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything from the previous sections is here: the tool definition, the growing &lt;code&gt;messages&lt;/code&gt; list that is re-sent every time, the assistant turn with tool calls, the results returned as a &lt;em&gt;user&lt;/em&gt; turn, and the &lt;code&gt;stop_reason&lt;/code&gt; check that ends the loop.&lt;/p&gt;&lt;p&gt;It works. It will also run &lt;code&gt;rm -rf&lt;/code&gt; if the model asks for it, dump a 40 MB log into the context, and hang on &lt;code&gt;npm start&lt;/code&gt;, because a dev server never exits.&lt;/p&gt;&lt;h3 id=&quot;what-a-production-harness-adds&quot;&gt;What a production harness adds&lt;/h3&gt;&lt;p&gt;A production harness keeps the same short loop and adds the engineering around it:&lt;/p&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Concern&lt;/th&gt;&lt;th&gt;Naive (above)&lt;/th&gt;&lt;th&gt;Real harness&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Safety&lt;/td&gt;&lt;td&gt;Runs anything, as you&lt;/td&gt;&lt;td&gt;Allow/deny rules, approval prompts, sandbox&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Output size&lt;/td&gt;&lt;td&gt;Everything into the context&lt;/td&gt;&lt;td&gt;Truncated, paginated, offset/limit parameters&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Time&lt;/td&gt;&lt;td&gt;Blocks until it finishes&lt;/td&gt;&lt;td&gt;Timeouts, background jobs, cancellation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;State&lt;/td&gt;&lt;td&gt;A new shell on every call&lt;/td&gt;&lt;td&gt;Persistent working directory and environment&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Errors&lt;/td&gt;&lt;td&gt;An exception kills the loop&lt;/td&gt;&lt;td&gt;Exit code and stderr returned, flagged &lt;code&gt;is_error&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Description&lt;/td&gt;&lt;td&gt;One sentence&lt;/td&gt;&lt;td&gt;Hundreds of words: when, how, what to avoid&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Parallelism&lt;/td&gt;&lt;td&gt;One call at a time&lt;/td&gt;&lt;td&gt;Independent calls run concurrently&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cost&lt;/td&gt;&lt;td&gt;Nothing tracked&lt;/td&gt;&lt;td&gt;Token accounting, prompt caching, compaction&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;h3 id=&quot;agent-model-harness&quot;&gt;Agent = model + harness&lt;/h3&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-harness.svg&quot; alt=&quot;A model box at the centre surrounded by six harness components inside a dashed boundary&quot;&gt;
&lt;figcaption&gt;The model is the engine. Everything around it is the harness.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;An agent is a model plus a harness. The harness supplies the system prompt (rules, persona, project config), the tools (their definitions and the code that runs them), the loop, context management (compaction, caching), permissions (approvals, sandbox, hooks) and the interface (CLI, IDE, chat, memory). Use the same model with a different harness and you get &lt;strong&gt;a different agent&lt;/strong&gt;. Claude Code, Codex CLI, Cursor and the 20-line script above are all harnesses.&lt;/p&gt;&lt;h3 id=&quot;why-the-harness-matters&quot;&gt;Why the harness matters&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;Most of what you experience as “the agent” is harness behaviour: which files it reads first, when it asks permission, how it recovers from a failed test, what it forgets.&lt;/li&gt;&lt;li&gt;Agent benchmarks score &lt;em&gt;model plus scaffold&lt;/em&gt;. Change the scaffold and the score moves. The SWE-agent paper &lt;a href=&quot;https://arxiv.org/abs/2405.15793&quot; target=&quot;_blank&quot;&gt;[14]&lt;/a&gt; showed this directly: with the same model, a better agent–computer interface (the tools, their output formats and guardrails) solved far more real GitHub issues.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;You rarely get to change the model. The harness you change all the time: config files, permissions, hooks, tools, MCP servers, skills, subagents and worktrees.&lt;/p&gt;&lt;h2 id=&quot;the-harness-where-do-you-have-leverage&quot;&gt;4. The harness: where do you have leverage?&lt;/h2&gt;&lt;h3 id=&quot;the-tool-landscape&quot;&gt;The tool landscape&lt;/h3&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Category&lt;/th&gt;&lt;th&gt;What it is&lt;/th&gt;&lt;th&gt;Examples&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;API platforms&lt;/td&gt;&lt;td&gt;You build the harness&lt;/td&gt;&lt;td&gt;Anthropic, OpenAI, Gemini&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CLI agents&lt;/td&gt;&lt;td&gt;An agent in the terminal&lt;/td&gt;&lt;td&gt;Claude Code, Codex, Gemini CLI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;IDE extensions&lt;/td&gt;&lt;td&gt;An agent in the editor&lt;/td&gt;&lt;td&gt;Copilot, Cursor, Windsurf&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Chat assistants&lt;/td&gt;&lt;td&gt;Conversation first&lt;/td&gt;&lt;td&gt;Claude, ChatGPT, Gemini&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;Only the first row leaves the harness to you. The other three ship with one.&lt;/p&gt;&lt;h3 id=&quot;six-levers-you-control&quot;&gt;Six levers you control&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;AGENTS.md.&lt;/strong&gt; What the agent should always know. Loaded on every request (section 2).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Subagents.&lt;/strong&gt; Isolate noisy work in a separate context window.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Worktrees.&lt;/strong&gt; Isolate files so several sessions can run in parallel.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Skills.&lt;/strong&gt; Package a workflow once, and load it only when it is needed.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;MCP.&lt;/strong&gt; Connect new tools and data sources through one protocol.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Hooks.&lt;/strong&gt; Run deterministic checks at fixed points in the loop.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The examples below use Claude Code, but most harnesses have equivalents.&lt;/p&gt;&lt;h3 id=&quot;subagents-a-fresh-context-for-noisy-work&quot;&gt;Subagents: a fresh context for noisy work&lt;/h3&gt;&lt;p&gt;A subagent is a specialized assistant with &lt;strong&gt;its own context window&lt;/strong&gt; and a restricted tool set &lt;a href=&quot;https://code.claude.com/docs/en/sub-agents&quot; target=&quot;_blank&quot;&gt;[15]&lt;/a&gt;. The main agent hands it a task. The subagent does the noisy work (reading dozens of files, running searches) in its own window, and only its conclusion returns to the main conversation.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-subagents.svg&quot; alt=&quot;Main agent context with a task and a summary block; two subagent contexts full of tool calls, one returning its summary&quot;&gt;
&lt;figcaption&gt;The main agent sees the task and the summary. The subagent’s tool traffic never enters the main context.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;This gives you three things:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Context isolation.&lt;/strong&gt; Verbose output stays out of the main conversation.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Parallelism.&lt;/strong&gt; Several subagents can run at once.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Cost control.&lt;/strong&gt; Simple tasks can go to cheaper, faster models.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Claude Code ships with built-in types. &lt;em&gt;Explore&lt;/em&gt; and &lt;em&gt;Plan&lt;/em&gt; are read-only; &lt;em&gt;General&lt;/em&gt; has full access.&lt;/p&gt;&lt;h3 id=&quot;worktrees-parallel-sessions-isolated-files&quot;&gt;Worktrees: parallel sessions, isolated files&lt;/h3&gt;&lt;p&gt;A subagent isolates context; a worktree isolates &lt;strong&gt;files&lt;/strong&gt;. A git worktree is an extra working directory attached to the same repository &lt;a href=&quot;https://git-scm.com/docs/git-worktree&quot; target=&quot;_blank&quot;&gt;[16]&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-worktrees.svg&quot; alt=&quot;A .git box connected to three worktree directories on different branches&quot;&gt;
&lt;figcaption&gt;One repository, three working directories, three branches.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Each worktree has its &lt;strong&gt;own files, branch and working state&lt;/strong&gt;.&lt;/li&gt;&lt;li&gt;Commit history and remotes are shared.&lt;/li&gt;&lt;li&gt;You can run parallel features, experiments or isolated agents without them overwriting each other’s changes.&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;comment&quot;&gt;# plain git&lt;/span&gt;
&lt;span class=&quot;constant function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;worktree&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;../app-auth&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-b&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;feature-auth&lt;/span&gt;

&lt;span class=&quot;comment&quot;&gt;# Claude Code shortcut&lt;/span&gt;
&lt;span class=&quot;constant function&quot;&gt;claude&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;--worktree&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;feature-auth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the Claude Code docs for the full workflow &lt;a href=&quot;https://code.claude.com/docs/en/common-workflows&quot; target=&quot;_blank&quot;&gt;[17]&lt;/a&gt;.&lt;/p&gt;&lt;h3 id=&quot;skills-reusable-workflows-loaded-on-demand&quot;&gt;Skills: reusable workflows, loaded on demand&lt;/h3&gt;&lt;p&gt;A skill is a folder with a &lt;code&gt;SKILL.md&lt;/code&gt; file: instructions, plus optional scripts and reference files &lt;a href=&quot;https://code.claude.com/docs/en/skills&quot; target=&quot;_blank&quot;&gt;[18]&lt;/a&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;md&quot;&gt;---
name: explain-code
description: Explains code with diagrams
user-invocable: true
allowed-tools: Read, Grep, Glob
---
When explaining code:
&lt;span class=&quot;punctuation_special&quot;&gt;1. &lt;/span&gt;Draw a diagram: ASCII art
&lt;span class=&quot;punctuation_special&quot;&gt;2. &lt;/span&gt;Walk through: step-by-step
&lt;span class=&quot;punctuation_special&quot;&gt;3. &lt;/span&gt;Highlight a gotcha
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Skills are &lt;strong&gt;cheap until used&lt;/strong&gt;. Only the name and description sit in the context. The body is loaded when the skill runs. Compare that with &lt;code&gt;AGENTS.md&lt;/code&gt;, which is paid for on every request. You can invoke a skill yourself as a slash command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;/explain-code src/auth/middleware.ts
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;or let the model pick it based on its description.&lt;/p&gt;&lt;h3 id=&quot;mcp-the-model-context-protocol&quot;&gt;MCP: the Model Context Protocol&lt;/h3&gt;&lt;p&gt;The Model Context Protocol (MCP) &lt;a href=&quot;https://modelcontextprotocol.io&quot; target=&quot;_blank&quot;&gt;[19]&lt;/a&gt; is an open standard for connecting models to tools and data. It is often called “USB for AI”.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-mcp.svg&quot; alt=&quot;Four MCP clients on the left connected through a central MCP hub to five servers on the right&quot;&gt;
&lt;figcaption&gt;Many clients, many servers, one protocol in between.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;p&gt;Write a server once and any client can use it. Adding a server to Claude Code is one command &lt;a href=&quot;https://code.claude.com/docs/en/mcp&quot; target=&quot;_blank&quot;&gt;[20]&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;sh&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;claude&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;mcp&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;context7&lt;/span&gt; \
  &lt;span class=&quot;constant&quot;&gt;--transport&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;--scope&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;user&lt;/span&gt; \
  &lt;span class=&quot;constant&quot;&gt;https://mcp.context7.com/mcp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A team can share its setup in a &lt;code&gt;.mcp.json&lt;/code&gt; file at the repository root.&lt;/p&gt;&lt;p&gt;The catch: &lt;strong&gt;every server adds its tool definitions to every request.&lt;/strong&gt; Recall the context breakdown from section 2: tools can already be a fifth of the window. Before adding a server, make sure the capability is worth the context it costs.&lt;/p&gt;&lt;h3 id=&quot;hooks-rules-that-always-run&quot;&gt;Hooks: rules that always run&lt;/h3&gt;&lt;p&gt;Hooks are shell commands that run at fixed points in the loop &lt;a href=&quot;https://code.claude.com/docs/en/hooks&quot; target=&quot;_blank&quot;&gt;[21]&lt;/a&gt;. The model can overlook an instruction in &lt;code&gt;AGENTS.md&lt;/code&gt;. A hook runs every time its event fires.&lt;/p&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/agentic-workflows/fig-hooks.svg&quot; alt=&quot;Timeline from model picks a tool, through PreToolUse, tool runs, PostToolUse, Notification and Stop hook events&quot;&gt;
&lt;figcaption&gt;Hook events sit at fixed points of the loop. Your command runs there every time.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;PreToolUse.&lt;/strong&gt; Runs before a tool executes. Use it to block dangerous commands.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;PostToolUse.&lt;/strong&gt; Runs after a tool executes. Use it to auto-format edited files.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Notification.&lt;/strong&gt; Alerts you when the agent needs input.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Stop.&lt;/strong&gt; Runs when the agent wants to finish. Use it to check that the task is really done, for example that the tests pass.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Here is a &lt;code&gt;PostToolUse&lt;/code&gt; hook that runs &lt;code&gt;ruff format&lt;/code&gt; on every file the agent edits or writes. The hook receives the tool call as JSON on stdin:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;json&quot;&gt;{&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;hooks&amp;quot;&lt;/span&gt;: {&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;PostToolUse&amp;quot;&lt;/span&gt;: [{&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;matcher&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;Edit|Write&amp;quot;&lt;/span&gt;,
  &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;hooks&amp;quot;&lt;/span&gt;: [{&lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;command&amp;quot;&lt;/span&gt;,
    &lt;span class=&quot;string_special_key string&quot;&gt;&amp;quot;command&amp;quot;&lt;/span&gt;: &lt;span class=&quot;string&quot;&gt;&amp;quot;jq -r &amp;apos;.tool_input.file_path&amp;apos; | xargs ruff format&amp;quot;&lt;/span&gt;}]}]}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Put it in &lt;code&gt;.claude/settings.json&lt;/code&gt;, and formatting stops depending on whether the model remembered to do it.&lt;/p&gt;&lt;h2 id=&quot;wrap-up-what-to-take-home&quot;&gt;5. Wrap-up: what to take home&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Context is the scarce resource.&lt;/strong&gt; Start fresh sessions, watch your usage, and put the key instructions first.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Be specific.&lt;/strong&gt; Name the file and the function, define what “done” means, and state the constraints.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The loop does the work.&lt;/strong&gt; Think, act, observe, until the model stops asking for tools.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Agent = model + harness.&lt;/strong&gt; The loop is 20 lines. Most of the work sits in the tools, context handling and permissions around it.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;You own the result.&lt;/strong&gt; Commit often, review every diff, and run the tests.&lt;/li&gt;&lt;/ol&gt;&lt;h3 id=&quot;take-home-exercise-fizzbuzz-the-agentic-way&quot;&gt;Take-home exercise: FizzBuzz, the agentic way&lt;/h3&gt;&lt;p&gt;Apply these ideas to a simple problem, then evaluate the result:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Try a vague prompt first, then a structured one, and compare.&lt;/li&gt;&lt;li&gt;Watch the context usage as you go (&lt;code&gt;/context&lt;/code&gt; in Claude Code).&lt;/li&gt;&lt;li&gt;Review the diff and run the tests yourself.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The course notes, with more detail on each topic, are at &lt;a href=&quot;https://anoopkcn.github.io/ai-assisted-coding&quot; target=&quot;_blank&quot;&gt;anoopkcn.github.io/ai-assisted-coding&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;A. Holtzman, J. Buys, L. Du, M. Forbes, Y. Choi. &lt;em&gt;The Curious Case of Neural Text Degeneration.&lt;/em&gt; ICLR 2020. &lt;a href=&quot;https://arxiv.org/abs/1904.09751&quot; target=&quot;_blank&quot;&gt;arXiv:1904.09751&lt;/a&gt;&lt;/li&gt;&lt;li&gt;A. Fan, M. Lewis, Y. Dauphin. &lt;em&gt;Hierarchical Neural Story Generation.&lt;/em&gt; ACL 2018. &lt;a href=&quot;https://arxiv.org/abs/1805.04833&quot; target=&quot;_blank&quot;&gt;arXiv:1805.04833&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Effort.&lt;/em&gt; Claude API documentation. &lt;a href=&quot;https://docs.claude.com/en/docs/build-with-claude/effort&quot; target=&quot;_blank&quot;&gt;docs.claude.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Context windows.&lt;/em&gt; Claude API documentation. &lt;a href=&quot;https://docs.claude.com/en/docs/build-with-claude/context-windows&quot; target=&quot;_blank&quot;&gt;docs.claude.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Prompt caching.&lt;/em&gt; Claude API documentation. &lt;a href=&quot;https://docs.claude.com/en/docs/build-with-claude/prompt-caching&quot; target=&quot;_blank&quot;&gt;docs.claude.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;C.-P. Hsieh et al. &lt;em&gt;RULER: What’s the Real Context Size of Your Long-Context Language Models?&lt;/em&gt; COLM 2024. &lt;a href=&quot;https://arxiv.org/abs/2404.06654&quot; target=&quot;_blank&quot;&gt;arXiv:2404.06654&lt;/a&gt;&lt;/li&gt;&lt;li&gt;J. Liu et al. &lt;em&gt;A Comprehensive Survey on Long Context Language Modeling.&lt;/em&gt; 2025, §7.1.1. &lt;a href=&quot;https://arxiv.org/abs/2503.17407&quot; target=&quot;_blank&quot;&gt;arXiv:2503.17407&lt;/a&gt;&lt;/li&gt;&lt;li&gt;N. F. Liu et al. &lt;em&gt;Lost in the Middle: How Language Models Use Long Contexts.&lt;/em&gt; TACL 2024. &lt;a href=&quot;https://arxiv.org/abs/2307.03172&quot; target=&quot;_blank&quot;&gt;arXiv:2307.03172&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Effective context engineering for AI agents.&lt;/em&gt; 2025. &lt;a href=&quot;https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents&quot; target=&quot;_blank&quot;&gt;anthropic.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;em&gt;AGENTS.md&lt;/em&gt;, an open format for guiding coding agents. &lt;a href=&quot;https://agents.md&quot; target=&quot;_blank&quot;&gt;agents.md&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Manage Claude’s memory (CLAUDE.md).&lt;/em&gt; Claude Code documentation. &lt;a href=&quot;https://code.claude.com/docs/en/memory&quot; target=&quot;_blank&quot;&gt;code.claude.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Tool use with Claude.&lt;/em&gt; Claude API documentation. &lt;a href=&quot;https://docs.claude.com/en/docs/agents-and-tools/tool-use/overview&quot; target=&quot;_blank&quot;&gt;docs.claude.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;E. Schluntz, B. Zhang. &lt;em&gt;Building effective agents.&lt;/em&gt; Anthropic, 2024. &lt;a href=&quot;https://www.anthropic.com/engineering/building-effective-agents&quot; target=&quot;_blank&quot;&gt;anthropic.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;J. Yang et al. &lt;em&gt;SWE-agent: Agent-Computer Interfaces Enable Automated Software Engineering.&lt;/em&gt; NeurIPS 2024. &lt;a href=&quot;https://arxiv.org/abs/2405.15793&quot; target=&quot;_blank&quot;&gt;arXiv:2405.15793&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Subagents.&lt;/em&gt; Claude Code documentation. &lt;a href=&quot;https://code.claude.com/docs/en/sub-agents&quot; target=&quot;_blank&quot;&gt;code.claude.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;em&gt;git-worktree: Manage multiple working trees.&lt;/em&gt; Git documentation. &lt;a href=&quot;https://git-scm.com/docs/git-worktree&quot; target=&quot;_blank&quot;&gt;git-scm.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Common workflows: run parallel sessions with git worktrees.&lt;/em&gt; Claude Code documentation. &lt;a href=&quot;https://code.claude.com/docs/en/common-workflows&quot; target=&quot;_blank&quot;&gt;code.claude.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Agent Skills.&lt;/em&gt; Claude Code documentation. &lt;a href=&quot;https://code.claude.com/docs/en/skills&quot; target=&quot;_blank&quot;&gt;code.claude.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;em&gt;Model Context Protocol.&lt;/em&gt; Specification and documentation. &lt;a href=&quot;https://modelcontextprotocol.io&quot; target=&quot;_blank&quot;&gt;modelcontextprotocol.io&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Connect Claude Code to tools via MCP.&lt;/em&gt; Claude Code documentation. &lt;a href=&quot;https://code.claude.com/docs/en/mcp&quot; target=&quot;_blank&quot;&gt;code.claude.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Anthropic. &lt;em&gt;Hooks reference.&lt;/em&gt; Claude Code documentation. &lt;a href=&quot;https://code.claude.com/docs/en/hooks&quot; target=&quot;_blank&quot;&gt;code.claude.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;OpenAI. &lt;em&gt;What are tokens and how to count them?&lt;/em&gt; OpenAI Help Center. &lt;a href=&quot;https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them&quot; target=&quot;_blank&quot;&gt;help.openai.com&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;</description>
				<link>https://anoopkc.com/blog/agentic-workflows/</link>
				<pubDate>Sat, 01 Aug 2026 12:35:28 +0000</pubDate>
				<guid>https://anoopkc.com/blog/agentic-workflows/</guid>
			</item>
		
			<item>
				<title>Tombili of Istanbul: Relax and Enjoy for a Moment</title>
	      <description>&lt;h1&gt;Tombili of Istanbul: Relax and Enjoy for a Moment&lt;/h1&gt;&lt;p&gt;&lt;figure&gt;&lt;img src=&quot;https://anoopkc.com/blog/Tombili-of-Istanbul/tcat.webp&quot; width=&quot;400&quot;&gt;
&lt;figcaption&gt;Tombili of Istanbul&lt;/figcaption&gt;&lt;/figure&gt;&lt;/p&gt;</description>
				<link>https://anoopkc.com/blog/Tombili-of-Istanbul/</link>
				<pubDate>Fri, 11 Apr 2025 10:46:29 +0000</pubDate>
				<guid>https://anoopkc.com/blog/Tombili-of-Istanbul/</guid>
			</item>
		
			<item>
				<title>CUDA Programming Model and CUDA Fortran</title>
	      <description>&lt;h1&gt;CUDA Programming Model and CUDA Fortran&lt;/h1&gt;&lt;p&gt;&lt;em&gt;Note, September 2026: this post is from 2018. Since then the PGI compilers have become part of the NVIDIA HPC SDK, and &lt;code&gt;pgf90&lt;/code&gt; is now &lt;code&gt;nvfortran&lt;/code&gt;. With &lt;code&gt;nvfortran&lt;/code&gt;, &lt;code&gt;-cuda&lt;/code&gt; enables CUDA Fortran and &lt;code&gt;-gpu=cc60&lt;/code&gt; selects the compute capability. The commands below keep the 2018 names.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;CUDA-enabled GPUs are capable of running tens of thousands of threads concurrently. This functionality is possible due to a large number of processor cores available on each GPU device. They are grouped into multiprocessors. The design of the CUDA programming model is done such that it can utilise the massive parallelism capability of the GPUs. A subroutine or a kernel is launched with a grid of threads grouped into thread blocks. These thread blocks can operate independently of one another thereby allowing scalability of a program. In addition to that data can be shared between these threads enabling more granular data parallelism.&lt;/p&gt;&lt;p&gt;The available multiprocessors can schedule these blocks of threads in any order allowing the program to be executed on any device with any number of multiprocessors. This scheduling is performed automatically so that one can concentrate on the partition of the program into fractions that can be run and parallelised on a block of threads.&lt;/p&gt;&lt;p&gt;CUDA programming model allows utilising both CPU and GPU to perform computations. This hybrid computing capability can be used to achieve massive performance improvements. A typical sequence of operations for a simple CUDA Fortran code are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Declare and allocate host (CPU) and device (GPU) memory&lt;/li&gt;&lt;li&gt;Initialize host data&lt;/li&gt;&lt;li&gt;Transfer data from the host to the device&lt;/li&gt;&lt;li&gt;Execute one or more kernels which perform operations on the device data&lt;/li&gt;&lt;li&gt;Transfer results from the device to the host&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;There are a number of good programming practices to be observed when moving data back and forth from host to device and vice-versa. Among those the most noticeable being the bandwidth of PCI and bandwidth between device’s memory and GPU, where the former is an order of magnitude lower than that of the latter. From a performance point of view, the less number of data transfers between the host and the device the better will be the performance.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;allocate()&lt;/code&gt; command and the assignment operators are overloaded in the CUDA Fortran model so that it can also allow allocation of the device memory (using &lt;code&gt;device&lt;/code&gt; attribute) and data transfer between the host and device memory spaces. The Fortran2003 source allocation construct, &lt;code&gt;allocate(A, source=B)&lt;/code&gt;, for cloning &lt;code&gt;B&lt;/code&gt; into &lt;code&gt;A&lt;/code&gt; is also extended. In CUDA Fortran, if the &lt;code&gt;A&lt;/code&gt; array is defined as a device array and &lt;code&gt;B&lt;/code&gt; is defined as host array, then contents of &lt;code&gt;B&lt;/code&gt; will be copied over the PCI bus to &lt;code&gt;A&lt;/code&gt;. These methods of data transfer are all blocking transfers, in that control is not returned to the CPU thread until the transfer is complete. This prevents the possibility of overlapping data transfers with computation on both the host and device. For concurrent computation one might employ CUDA API function &lt;code&gt;cudaMemcpyAsync()&lt;/code&gt; to perform asynchronous data transfers.&lt;/p&gt;&lt;p&gt;Kernels, or subroutines are typically invoked in host code just as any subroutine is called, but since the kernel code is executed by many threads in parallel an additional execution configuration has to be provided to indicate thread specifications. Data elements are mapped to threads using the automatically defined variables &lt;code&gt;threadIdx&lt;/code&gt;, &lt;code&gt;blockIdx&lt;/code&gt;, &lt;code&gt;blockDim&lt;/code&gt;, and &lt;code&gt;gridDim&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;One of the functionalities provided by CUDA Fortran is that it can automatically generate and invoke kernel code from a region of host code containing tightly nested loops. Such type of kernels are referred to as CUF kernels. A simple example of a CUF kernel is:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;!$cuf kernel do &amp;lt;&amp;lt;&amp;lt;*,*&amp;gt;&amp;gt;&amp;gt;
do i=1, n
 ! a_d is a device array, b is a host scalar
 a_d(i) = a_d(i) + b
end do
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the above code wild-cards are used to make the runtime system determine the execution configuration. Even though the scalar &lt;code&gt;b&lt;/code&gt; is a host variable it is passed as a kernel argument by value upon the execution of the CUF kernel.&lt;/p&gt;&lt;p&gt;In the following code subroutine &lt;code&gt;add_scalar&lt;/code&gt; is making use of the module &lt;code&gt;array_def&lt;/code&gt; which defines the array &lt;code&gt;a(n)&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module array_def
 real :: a(n)
end module array_def
!
! Following subroutine make use of the module array_def
subroutine add_scalar
 use array_def, only: a
 do i=1, n
  a(i) = a(i) + b
 end do
end subroutine add_scalar
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Converting this code to a GPU friendly one can be done as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module array_def
 real :: a(n)
 real,device :: a_d(n)
end module array_def
!
subroutine add_scalar
#ifdef USE_GPU
 use array_def, only: a =&amp;gt; a_d
#else
 use array_def, only: a
#endif
!$cuf kernel do &amp;lt;&amp;lt;&amp;lt;*,*&amp;gt;&amp;gt;&amp;gt;
 do i=1, n
  a(i) = a(i) + b
 end do
end subroutine add_scalar
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When compiling without the preprocessor directive &lt;code&gt;USE_GPU&lt;/code&gt;, the code will behave the same as the CPU version, ignoring the CUF kernel execution configuration. When GPU is enabled the device allocated array &lt;code&gt;a_d(n)&lt;/code&gt; is used instead, for a calculation respecting the CUF kernel.&lt;/p&gt;&lt;p&gt;If the arrays used in the subroutine are explicitly passed then one can make use of the &lt;code&gt;device&lt;/code&gt; attribute to achieve the same effect as above. The following code illustrates the same.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;subroutine add_scalar(a,n)
 real:: a(n)
#ifdef USE_GPU
 attributes(device) :: a
#endif
!$cuf kernel do &amp;lt;&amp;lt;&amp;lt;*,*&amp;gt;&amp;gt;&amp;gt;
 do i=1, n
  a(i) = a(i) + b
 end do
end subroutine add_scalar
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Compiling and Running the Code&lt;/h2&gt;&lt;p&gt;Any code in a file with a &lt;code&gt;.cuf&lt;/code&gt; or &lt;code&gt;.CUF&lt;/code&gt; extension is compiled with CUDA Fortran automatically enabled. Otherwise, the compiler flag &lt;code&gt;-Mcuda&lt;/code&gt; can be used to compile CUDA Fortran code with other extensions. If above code is in a file named &lt;code&gt;cufortran.cuf&lt;/code&gt;, compilation and execution of the code is as simple as:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;pgf90&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;cufortran&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;cufortran.cuf&lt;/span&gt;
&lt;span class=&quot;constant function&quot;&gt;./cufortran&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For files that are intended to compile both on CPU and GPU, as explained in this post, one can compile the code using the following sequence.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;pgf90&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-Mcuda=cc60&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-O3&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-Mpreprocess&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-DUSE_GPU&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;cufortran.F90&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The preprocessor directive &lt;code&gt;-Mpreprocess -DUSE_GPU&lt;/code&gt; will activate the CUDA parts of the code when used. The value of the &lt;code&gt;-Mcuda=cc60&lt;/code&gt; flag will be inferred by the GPU card one is using: &lt;code&gt;cc60&lt;/code&gt; is for NVIDIA P100 cards and &lt;code&gt;cc35&lt;/code&gt; is for K80 cards and so on.&lt;/p&gt;&lt;p&gt;A community edition of the PGI compiler (&gt;= version 16.10) is available at the NVIDIA website, where CUDA Fortran is implemented.&lt;/p&gt;&lt;h2&gt;References&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/cuda-fortran&quot; target=&quot;_blank&quot;&gt;CUDA Fortran for NVIDIA&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://devblogs.nvidia.com/parallelforall/easy-introduction-cuda-fortran/&quot; target=&quot;_blank&quot;&gt;An easy introduction&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/anoopkcn/graphene-model&quot; target=&quot;_blank&quot;&gt;Graphene tight binding model for porting to fortran and then to gpu&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</description>
				<link>https://anoopkc.com/blog/cuda-fortran/</link>
				<pubDate>Wed, 18 Apr 2018 00:00:00 +0000</pubDate>
				<guid>https://anoopkc.com/blog/cuda-fortran/</guid>
			</item>
		
			<item>
				<title>Using crossref for building a bib</title>
	      <description>&lt;h1&gt;Using crossref for building a bib&lt;/h1&gt;&lt;h2&gt;tl;dr&lt;/h2&gt;&lt;p&gt;This is based on a one-liner post. Provided a DOI of a paper or an article, the following will use the crossref API &lt;a href=&quot;https://www.crossref.org&quot; target=&quot;_blank&quot;&gt;[1]&lt;/a&gt; and display the information regarding the document such as author, year, publication, etc.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;comment&quot;&gt;#DOI=DOI of a paper&lt;/span&gt;
&lt;span class=&quot;property&quot;&gt;DOI&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;apos;DOI&amp;apos;&lt;/span&gt;;
&lt;span class=&quot;constant function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;quot;https://api.crossref.org/works/&lt;/span&gt;&lt;span class=&quot;string embedded constant&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;string embedded constant property&quot;&gt;DOI&lt;/span&gt;&lt;span class=&quot;string embedded constant&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;string constant&quot;&gt;/transform/application/x-bibtex&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following is a bash function which makes use of this one-liner to do slightly complicated tasks such as appending the crossref info of a document to a file, selectively displaying the information when not given a file, etc.&lt;/p&gt;&lt;h2&gt;Bash Function&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;file&lt;/code&gt; variable is initially empty, this is to store the file name the user would provide upon calling the function &lt;code&gt;doi2bib&lt;/code&gt; with the flag &lt;code&gt;-f&lt;/code&gt;. Likewise &lt;code&gt;info&lt;/code&gt; is an empty string which would hold the information tag such as author, year, publisher, volume etc., provided by the user with the flag &lt;code&gt;-i&lt;/code&gt;. The flags &lt;code&gt;-f&lt;/code&gt; and &lt;code&gt;-i&lt;/code&gt; are mutually exclusive in the following script. Flag &lt;code&gt;-d&lt;/code&gt; is mandatory since this specifies the &lt;code&gt;DOI&lt;/code&gt; of the document in question.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;function&quot;&gt;doi2bib&lt;/span&gt;() {
  local &lt;span class=&quot;property&quot;&gt;doi&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt; &lt;span class=&quot;property&quot;&gt;info&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt; &lt;span class=&quot;property&quot;&gt;file&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt;
  local &lt;span class=&quot;property&quot;&gt;api&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;https://api.crossref.org/works&amp;quot;&lt;/span&gt; &lt;span class=&quot;property&quot;&gt;trans&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&amp;quot;transform/application/x-bibtex&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;while&lt;/span&gt; [ &lt;span class=&quot;operator&quot;&gt;$&lt;/span&gt;# -gt 0 ]; &lt;span class=&quot;keyword&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string property&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;in&lt;/span&gt;
      -h&lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt;--help)
        &lt;span class=&quot;constant function&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;quot;usage: doi2bib -d DOI [-i FIELD | -f FILE]&amp;quot;&lt;/span&gt;
        &lt;span class=&quot;constant function&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;0&lt;/span&gt; ;;
      -d&lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt;--doi)  &lt;span class=&quot;property&quot;&gt;doi&lt;/span&gt;=&lt;span class=&quot;embedded&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;embedded property&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;embedded&quot;&gt;-}&lt;/span&gt;;  &lt;span class=&quot;constant function&quot;&gt;shift&lt;/span&gt; ;;
      -i&lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt;--info) &lt;span class=&quot;property&quot;&gt;info&lt;/span&gt;=&lt;span class=&quot;embedded&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;embedded property&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;embedded&quot;&gt;-}&lt;/span&gt;; &lt;span class=&quot;constant function&quot;&gt;shift&lt;/span&gt; ;;
      -f&lt;span class=&quot;operator&quot;&gt;|&lt;/span&gt;--file) &lt;span class=&quot;property&quot;&gt;file&lt;/span&gt;=&lt;span class=&quot;embedded&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;embedded property&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;embedded&quot;&gt;-}&lt;/span&gt;; &lt;span class=&quot;constant function&quot;&gt;shift&lt;/span&gt; ;;
      *) &lt;span class=&quot;constant function&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;quot;doi2bib: unknown option &lt;/span&gt;&lt;span class=&quot;string constant operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string constant property&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;string constant&quot;&gt;&amp;quot;&lt;/span&gt; &amp;gt;&amp;amp;2; &lt;span class=&quot;constant function&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;1&lt;/span&gt; ;;
    &lt;span class=&quot;keyword&quot;&gt;esac&lt;/span&gt;
    [ &lt;span class=&quot;operator&quot;&gt;$&lt;/span&gt;# -gt 0 ] &lt;span class=&quot;operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;constant function&quot;&gt;shift&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;done&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; [ -z &lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string property&quot;&gt;doi&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt; ]; &lt;span class=&quot;keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;constant function&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;quot;doi2bib: no DOI given, use -d DOI&amp;quot;&lt;/span&gt; &amp;gt;&amp;amp;2; &lt;span class=&quot;constant function&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;fi&lt;/span&gt;

  local &lt;span class=&quot;property&quot;&gt;bib&lt;/span&gt;
  &lt;span class=&quot;property&quot;&gt;bib&lt;/span&gt;=&lt;span class=&quot;embedded&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;embedded constant function&quot;&gt;curl&lt;/span&gt;&lt;span class=&quot;embedded&quot;&gt; &lt;/span&gt;&lt;span class=&quot;embedded constant&quot;&gt;-sfL&lt;/span&gt;&lt;span class=&quot;embedded&quot;&gt; &lt;/span&gt;&lt;span class=&quot;embedded string constant&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;embedded string constant operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;embedded string constant property&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;embedded string constant&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;embedded string constant operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;embedded string constant property&quot;&gt;doi&lt;/span&gt;&lt;span class=&quot;embedded string constant&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;embedded string constant operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;embedded string constant property&quot;&gt;trans&lt;/span&gt;&lt;span class=&quot;embedded string constant&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;embedded&quot;&gt;)&lt;/span&gt; || {
    &lt;span class=&quot;constant function&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;quot;doi2bib: crossref has no BibTeX for &lt;/span&gt;&lt;span class=&quot;string constant operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string constant property&quot;&gt;doi&lt;/span&gt;&lt;span class=&quot;string constant&quot;&gt;&amp;quot;&lt;/span&gt; &amp;gt;&amp;amp;2; &lt;span class=&quot;constant function&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;1&lt;/span&gt;
  }

  &lt;span class=&quot;keyword&quot;&gt;if&lt;/span&gt; [ -n &lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string property&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt; ]; &lt;span class=&quot;keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;constant function&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;apos;\n%s\n&amp;apos;&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string constant operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string constant property&quot;&gt;bib&lt;/span&gt;&lt;span class=&quot;string constant&quot;&gt;&amp;quot;&lt;/span&gt; &lt;span class=&quot;operator&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string property&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;elif&lt;/span&gt; [ -n &lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string property&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;&lt;/span&gt; ]; &lt;span class=&quot;keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;constant function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-oE&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string constant operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string constant property&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;string constant&quot;&gt;=(\{[^}]*\}|[^,} ]*)&amp;quot;&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;&amp;lt;&amp;lt;&amp;lt; &lt;/span&gt;&lt;span class=&quot;string constant&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string constant operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string constant property&quot;&gt;bib&lt;/span&gt;&lt;span class=&quot;string constant&quot;&gt;&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;constant function&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;apos;%s\n&amp;apos;&lt;/span&gt; &lt;span class=&quot;string constant&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;string constant operator&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;string constant property&quot;&gt;bib&lt;/span&gt;&lt;span class=&quot;string constant&quot;&gt;&amp;quot;&lt;/span&gt;
  &lt;span class=&quot;keyword&quot;&gt;fi&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few typical examples of the usage of the above script are given as follows.&lt;/p&gt;&lt;p&gt;To show the full info on the terminal:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;doi2bib&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;https://doi.org/10.1016/j.jssc.2016.08.030&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To show the selective info on the terminal:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;doi2bib&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;https://doi.org/10.1016/j.jssc.2016.08.030&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;author&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To append the full information in the bibtex format to a file named &lt;code&gt;mypaper.bib&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;bash&quot;&gt;&lt;span class=&quot;constant function&quot;&gt;doi2bib&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;https://doi.org/10.1016/j.jssc.2016.08.030&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;-f&lt;/span&gt; &lt;span class=&quot;constant&quot;&gt;mypaper.bib&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;References&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;The online service of &lt;a href=&quot;https://www.crossref.org&quot; target=&quot;_blank&quot;&gt;crossref&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</description>
				<link>https://anoopkc.com/blog/crossref-bib/</link>
				<pubDate>Fri, 24 Nov 2017 00:00:00 +0000</pubDate>
				<guid>https://anoopkc.com/blog/crossref-bib/</guid>
			</item>
		
	</channel>
</rss>
