<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Codex &#8211; About Things | A Hans Scharler Blog</title>
	<atom:link href="https://nothans.com/tag/codex/feed" rel="self" type="application/rss+xml" />
	<link>https://nothans.com</link>
	<description>Life, Comedy, Games, Tech, Marketing, and Community</description>
	<lastBuildDate>Sun, 27 Sep 2026 15:26:17 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://i0.wp.com/nothans.com/wp-content/uploads/2023/02/cropped-settings.png?fit=32%2C32&#038;ssl=1</url>
	<title>Codex &#8211; About Things | A Hans Scharler Blog</title>
	<link>https://nothans.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">114568856</site>	<item>
		<title>DFS Fantasy Football Optimization (Agentic AI + MATLAB Optimization Toolbox)</title>
		<link>https://nothans.com/dfs-fantasy-football-optimization-agentic-ai-matlab-optimization-toolbox</link>
					<comments>https://nothans.com/dfs-fantasy-football-optimization-agentic-ai-matlab-optimization-toolbox#respond</comments>
		
		<dc:creator><![CDATA[Hans Scharler]]></dc:creator>
		<pubDate>Sun, 27 Sep 2026 15:26:13 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Projects]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[Codex]]></category>
		<category><![CDATA[fantasy football]]></category>
		<category><![CDATA[matlab]]></category>
		<guid isPermaLink="false">https://nothans.com/?p=5732</guid>

					<description><![CDATA[]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Four seasons ago I wrote a post about <a href="https://nothans.com/win-at-dfs-by-optimizing-your-fantasy-football-lineups">winning (or doing better) at daily fantasy football with MATLAB</a>. The pitch was simple: DFS is an optimization problem wearing a jersey, so let the Optimization Toolbox pick your lineup. Sixty lines of MATLAB, one salary cap, one answer.</p>



<p class="wp-block-paragraph">That post still works, and the script still runs. But it taught the wrong objective for half the contests people play. And it assumed the person running it was you, pasting code into a Live Script. Both of those changed, so I rebuilt the thing.</p>



<p class="wp-block-paragraph">The repo is the same one: <a href="https://github.com/nothans/dfs-optimizer">github.com/nothans/dfs-optimizer</a>. It now has a function library, an app for exploring the options, a test suite, and a section for coding agents. In 2026 the &#8220;user&#8221; of a tool like this is as likely to be Claude Code or OpenAI Codex with a MATLAB session open as it is to be a human with a mouse. This post is the new tutorial.</p>



<h2 id="what-the-2022-script-got-right-and-what-it-got-wrong" class="wp-block-heading">What the 2022 script got right, and what it got wrong</h2>



<p class="wp-block-paragraph">The 2022 model was one binary variable per player and a handful of linear constraints: one QB, one defense, two to three running backs, three to four receivers, one to two tight ends, nine players total, salary under the cap. Maximize projected points. That is still the core, and the new code returns the identical lineup on the same data. There is a test that proves it.</p>



<p class="wp-block-paragraph">What it got wrong is subtler. &#8220;Maximize projected points&#8221; is the correct objective for a <strong>cash game</strong>, a 50/50 or a double-up, where you&#8217;re paid if you finish in the top half. You want the highest floor, and the highest projection is a decent proxy.</p>



<p class="wp-block-paragraph">It&#8217;s the wrong objective for a <strong>tournament</strong> (a GPP, guaranteed prize pool), where a few thousand entries compete for a top-heavy payout and the winner needs a top-1% finish. In a tournament, the highest-projected lineup is the lineup everyone else has too. You want ceiling and you want to be different. That means correlated players (a QB and his receiver score on the same plays), lower-owned players (so a hit separates you from the field), and more than one lineup.</p>



<h2 id="get-the-data" class="wp-block-heading">Get the data</h2>



<p class="wp-block-paragraph">The projections still come from <a href="https://www.dailyfantasyfuel.com/nfl/projections/">Daily Fantasy Fuel</a>. Pick the DraftKings or FanDuel tab, clear any filters, let the whole table load, then click <strong>Download CSV</strong> and save the file. The button exports the rows on screen, so a filtered view gives you a filtered file. The header looks like this:</p>



<pre class="wp-block-code"><code>first_name, last_name, position, injury_status, week, game_date, slate, team, opp,
spread, over_under, implied_team_score, salary, L5_dvp_rank, L5_fppg_avg,
L10_fppg_avg, szn_fppg_avg, ppg_projection, value_projection, ownership_projection</code></pre>



<p class="wp-block-paragraph">That last column matters. Projected ownership is what makes the tournament levers work, and the 2022 post ignored it.</p>



<p class="wp-block-paragraph">You don&#8217;t need to import the file by hand anymore. The loader reads it directly, drops anyone marked out, and keeps every original column:</p>



<pre class="wp-block-code"><code>players = dfs.loadProjections("DFF_NFL_cheatsheet.csv");</code></pre>



<p class="wp-block-paragraph">If you don&#8217;t have a download handy, the repo ships a synthetic 13-game slate in the same header, with made-up names, so every example below runs as-is, and you&#8217;ll meet a few of those names again before this is over.</p>



<h2 id="the-app-in-five-clicks" class="wp-block-heading">The app, in five clicks</h2>



<p class="wp-block-paragraph">Open MATLAB in the repo folder (or click the <strong>Open in MATLAB Online</strong> badge on the README) and run:</p>



<pre class="wp-block-code"><code>DFSOptimizerApp</code></pre>



<p class="wp-block-paragraph">It opens on the sample slate. Levers on the left, results on the right.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="750" height="469" data-attachment-id="5725" data-permalink="https://nothans.com/01-app-opens-on-sample-slate" data-orig-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/01-app-opens-on-sample-slate.png?fit=1280%2C800&amp;ssl=1" data-orig-size="1280,800" data-comments-opened="0" data-image-title="01-app-opens-on-sample-slate" data-image-description="" data-image-caption="" data-large-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/01-app-opens-on-sample-slate.png?fit=750%2C469&amp;ssl=1" src="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/01-app-opens-on-sample-slate.png?resize=750%2C469&#038;ssl=1" alt="" class="wp-image-5725" srcset="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/01-app-opens-on-sample-slate.png?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/01-app-opens-on-sample-slate.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/01-app-opens-on-sample-slate.png?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/01-app-opens-on-sample-slate.png?resize=750%2C469&amp;ssl=1 750w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/01-app-opens-on-sample-slate.png?w=1280&amp;ssl=1 1280w" sizes="(max-width: 750px) 100vw, 750px" /></figure>



<p class="wp-block-paragraph"><strong>Click one: the Cash game preset, then Optimize.</strong> This is the 2022 script with a face. One lineup, maximum projection, no stacking, ownership ignored. On the sample slate it lands at 176.4 projected points and a summed ownership of 192%, which is a polite way of saying &#8220;everyone has these players.&#8221;</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" width="750" height="469" data-attachment-id="5726" data-permalink="https://nothans.com/02-cash-lineup" data-orig-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/02-cash-lineup.png?fit=1280%2C800&amp;ssl=1" data-orig-size="1280,800" data-comments-opened="0" data-image-title="02-cash-lineup" data-image-description="" data-image-caption="" data-large-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/02-cash-lineup.png?fit=750%2C469&amp;ssl=1" src="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/02-cash-lineup.png?resize=750%2C469&#038;ssl=1" alt="" class="wp-image-5726" srcset="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/02-cash-lineup.png?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/02-cash-lineup.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/02-cash-lineup.png?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/02-cash-lineup.png?resize=750%2C469&amp;ssl=1 750w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/02-cash-lineup.png?w=1280&amp;ssl=1 1280w" sizes="(max-width: 750px) 100vw, 750px" /></figure>



<p class="wp-block-paragraph"><strong>Click two: the Tournament preset, then Optimize.</strong> Now the sidebar asks for a QB stack (one receiver or tight end from the QB&#8217;s own team), a bring-back (one skill player from the opponent), and no defense against your own QB. It also wants a small penalty on projected ownership and twenty lineups, where nobody appears in more than half and every pair differs by at least three players. A progress bar counts them off, about a quarter second each.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" width="750" height="469" data-attachment-id="5727" data-permalink="https://nothans.com/03-gpp-portfolio" data-orig-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/03-gpp-portfolio.png?fit=1280%2C800&amp;ssl=1" data-orig-size="1280,800" data-comments-opened="0" data-image-title="03-gpp-portfolio" data-image-description="" data-image-caption="" data-large-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/03-gpp-portfolio.png?fit=750%2C469&amp;ssl=1" src="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/03-gpp-portfolio.png?resize=750%2C469&#038;ssl=1" alt="" class="wp-image-5727" srcset="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/03-gpp-portfolio.png?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/03-gpp-portfolio.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/03-gpp-portfolio.png?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/03-gpp-portfolio.png?resize=750%2C469&amp;ssl=1 750w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/03-gpp-portfolio.png?w=1280&amp;ssl=1 1280w" sizes="(max-width: 750px) 100vw, 750px" /></figure>



<p class="wp-block-paragraph">Click any row to see the roster. The summed ownership drops into the 70 to 150 range and the QB column shows the portfolio spreading across three or four quarterbacks instead of one.</p>



<p class="wp-block-paragraph"><strong>Click three: lock and exclude.</strong> The Player pool tab is the whole slate with two checkbox columns. Tick <strong>Lock</strong> on a player you believe in and <strong>Exclude</strong> on one you don&#8217;t, and every solve from then on honors it.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="750" height="469" data-attachment-id="5728" data-permalink="https://nothans.com/04-lock-and-exclude" data-orig-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/04-lock-and-exclude.png?fit=1280%2C800&amp;ssl=1" data-orig-size="1280,800" data-comments-opened="0" data-image-title="04-lock-and-exclude" data-image-description="" data-image-caption="" data-large-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/04-lock-and-exclude.png?fit=750%2C469&amp;ssl=1" src="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/04-lock-and-exclude-1024x640.png?resize=750%2C469&#038;ssl=1" alt="" class="wp-image-5728" srcset="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/04-lock-and-exclude.png?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/04-lock-and-exclude.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/04-lock-and-exclude.png?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/04-lock-and-exclude.png?resize=750%2C469&amp;ssl=1 750w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/04-lock-and-exclude.png?w=1280&amp;ssl=1 1280w" sizes="auto, (max-width: 750px) 100vw, 750px" /></figure>



<p class="wp-block-paragraph"><strong>Click four: Exposure.</strong> This is the chart I wanted in 2022 and didn&#8217;t have. Your exposure per player across the twenty lineups, next to the field&#8217;s projected ownership. The gap is leverage. A player you have at 40% who the field has at 2% is where a tournament gets won or lost.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="750" height="469" data-attachment-id="5729" data-permalink="https://nothans.com/05-exposure-vs-field" data-orig-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/05-exposure-vs-field.png?fit=1280%2C800&amp;ssl=1" data-orig-size="1280,800" data-comments-opened="0" data-image-title="05-exposure-vs-field" data-image-description="" data-image-caption="" data-large-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/05-exposure-vs-field.png?fit=750%2C469&amp;ssl=1" src="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/05-exposure-vs-field.png?resize=750%2C469&#038;ssl=1" alt="" class="wp-image-5729" srcset="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/05-exposure-vs-field.png?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/05-exposure-vs-field.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/05-exposure-vs-field.png?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/05-exposure-vs-field.png?resize=750%2C469&amp;ssl=1 750w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/05-exposure-vs-field.png?w=1280&amp;ssl=1 1280w" sizes="auto, (max-width: 750px) 100vw, 750px" /></figure>



<p class="wp-block-paragraph"><strong>Click five: Simulate.</strong> A projection is a mean, not a promise. The Monte Carlo tab draws fifty (or five hundred) noisy versions of the projections, re-solves the lineup for each one, and counts who made it. A player who&#8217;s optimal in 60% of the draws is a play. A player who&#8217;s optimal only at the exact point estimate is a coin flip with a good agent.</p>



<figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" width="750" height="469" data-attachment-id="5730" data-permalink="https://nothans.com/06-monte-carlo" data-orig-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/06-monte-carlo.png?fit=1280%2C800&amp;ssl=1" data-orig-size="1280,800" data-comments-opened="0" data-image-title="06-monte-carlo" data-image-description="" data-image-caption="" data-large-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/06-monte-carlo.png?fit=750%2C469&amp;ssl=1" src="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/06-monte-carlo.png?resize=750%2C469&#038;ssl=1" alt="" class="wp-image-5730" srcset="https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/06-monte-carlo.png?resize=1024%2C640&amp;ssl=1 1024w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/06-monte-carlo.png?resize=300%2C188&amp;ssl=1 300w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/06-monte-carlo.png?resize=768%2C480&amp;ssl=1 768w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/06-monte-carlo.png?resize=750%2C469&amp;ssl=1 750w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/09/06-monte-carlo.png?w=1280&amp;ssl=1 1280w" sizes="auto, (max-width: 750px) 100vw, 750px" /></figure>



<p class="wp-block-paragraph">There&#8217;s a sixth button, <strong>Copy as code</strong>, and it&#8217;s the one I use most. It writes your current sidebar as a function call and puts it on the clipboard, so a session in the app turns into a script you can rerun next week:</p>



<pre class="wp-block-code"><code>players = dfs.loadProjections("data/sample_DFF_NFL.csv");
&#91;lineups, summary, exposure] = dfs.generateLineups(players, 20, ...
    Site="DraftKings", SalaryCap=50000, OwnershipWeight=0.05, StackSize=1, BringBack=1, ...
    AvoidQBvsDST=true, MaxExposure=0.5, MinUnique=3, Randomness=0.1, Seed=1);</code></pre>



<h2 id="the-levers-one-constraint-each" class="wp-block-heading">The levers, one constraint each</h2>



<p class="wp-block-paragraph">The app is a thin layer over a package called <code>+dfs</code>, and every lever is a name-value option on <code>dfs.optimizeLineup</code>. Each one is a single linear constraint on the same binary variables the 2022 script used. Plain words first, then the code, lever by lever.</p>



<p class="wp-block-paragraph"><strong>Stack.</strong> For every quarterback in the pool, &#8220;the number of his own receivers and tight ends you pick is at least k times whether you picked him.&#8221; When you don&#8217;t pick him the right side is zero and the constraint sleeps. When you do, it demands k partners. No big-M tricks, one row per QB.</p>



<pre class="wp-block-code"><code>L = dfs.optimizeLineup(players, StackSize=2);</code></pre>



<p class="wp-block-paragraph"><strong>Bring-back.</strong> The same shape, aimed at the opponent&#8217;s skill players. It bets on a shootout.</p>



<pre class="wp-block-code"><code>L = dfs.optimizeLineup(players, StackSize=1, BringBack=1);</code></pre>



<p class="wp-block-paragraph"><strong>No QB against your own defense.</strong> A pairwise exclusion: QB plus the opposing defense is at most one. Your defense scores when his offense fails. Do not root against yourself.</p>



<pre class="wp-block-code"><code>L = dfs.optimizeLineup(players, AvoidQBvsDST=true);</code></pre>



<p class="wp-block-paragraph"><strong>Ownership penalty.</strong> Instead of maximizing projection, maximize projection minus lambda times projected ownership. On the sample slate, a lambda of 0.05 with a two-man game stack gives up 3.8 points and sheds 23 points of summed ownership.</p>



<pre class="wp-block-code"><code>&#91;L, info] = dfs.optimizeLineup(players, OwnershipWeight=0.05);</code></pre>



<p class="wp-block-paragraph"><strong>Exposure and uniqueness.</strong> When you build twenty lineups in a row, each new solve gets two extra rules: players at their exposure cap sit out, and &#8220;the overlap with every earlier lineup is at most nine minus u players.&#8221;</p>



<pre class="wp-block-code"><code>&#91;lineups, summary, exposure] = dfs.generateLineups(players, 20, MaxExposure=0.4, MinUnique=3);</code></pre>



<p class="wp-block-paragraph"><strong>FanDuel.</strong> Different cap, a four-per-team limit, three-team minimum. One word.</p>



<pre class="wp-block-code"><code>L = dfs.optimizeLineup(players, Site="FanDuel");</code></pre>



<p class="wp-block-paragraph">If you like your math in one block, this is the whole model:</p>



<pre class="wp-block-code"><code>maximize    sum_i (p_i - lambda * o_i) x_i
subject to  sum_i x_i = 9
            1 QB, 1 DST, 2-3 RB, 3-4 WR, 1-2 TE
            sum_i s_i x_i &lt;= cap
            players from at least 2 games            (DraftKings)
            at most 4 per team, at least 3 teams     (FanDuel)
            partners of QB q  &gt;= k * x_q             (stack)
            opponents of QB q &gt;= b * x_q             (bring-back)
            x_q + DST facing q &lt;= 1                  (no QB vs DST)
            overlap with earlier lineup &lt;= 9 - u     (uniqueness)
            x_i in {0, 1}</code></pre>



<p class="wp-block-paragraph">The FLEX spot isn&#8217;t a variable. It&#8217;s the slack between each position&#8217;s base count and its maximum, pinned by the total of nine. That trick was in the 2022 script and it&#8217;s still the reason the model stays at one variable per player.</p>



<p class="wp-block-paragraph">The academic version, stacking constraints included, is Hunter, Vielma and Zaman&#8217;s <a href="https://arxiv.org/abs/1604.01455">&#8220;Picking Winners in Daily Fantasy Sports Using Integer Programming&#8221;</a> from MIT. The levers above are the practical subset every serious optimizer ends up with.</p>



<h2 id="trust-then-verify" class="wp-block-heading">Trust, then verify</h2>



<p class="wp-block-paragraph">Two things happen after every solve, and both are new.</p>



<p class="wp-block-paragraph">First, the code checks the solver&#8217;s exit flag and refuses to hand you a lineup the solver didn&#8217;t actually finish. Second, the lineup goes through <code>dfs.validateLineup</code>, a function that knows nothing about the solver. It counts positions, adds salary, checks the games and teams, and complains in plain English if anything is off. If the solver and the validator ever disagree, you get an error, not a lineup.</p>



<p class="wp-block-paragraph">There are 22 tests. The one I care about most feeds the same slate to the 2022 formulation and the new one and asserts they return the same players and the same 176.4 points. The model got a lot of new rules, but the old answer didn&#8217;t move.</p>



<h2 id="the-other-user-a-coding-agent" class="wp-block-heading">The other user: a coding agent</h2>



<p class="wp-block-paragraph">MathWorks ships the <a href="https://github.com/matlab/matlab-agentic-toolkit">MATLAB Agentic Toolkit</a>. It does two things. It installs the MATLAB MCP Server, which gives a coding agent (Claude Code, GitHub Copilot, Codex, Gemini CLI, Amp) a live MATLAB session it can run code in, lint with, and test through. And it installs skills: curated MathWorks knowledge so the agent writes idiomatic MATLAB and stops inventing toolbox functions.</p>



<p class="wp-block-paragraph">I used two of those skills to build the refresh. <code>matlab-solve-optimization</code> is where &#8220;check the exit flag, then validate independently&#8221; comes from. <code>matlab-build-app</code> is where the app&#8217;s layout comes from (it calls the sidebar-plus-display shape an Explorer, and it&#8217;s right). The skills read like design documents from someone who has built a lot of MATLAB apps and solved a lot of optimization problems, which is what they are.</p>



<p class="wp-block-paragraph">Setting it up takes three steps:</p>



<ol class="wp-block-list">
<li>Download <a href="https://github.com/matlab/simulink-agentic-toolkit/releases/latest/download/agenticToolkitInstaller.mltbx">agenticToolkitInstaller.mltbx</a>, open it in MATLAB, and run <code>setupAgenticToolkit("install")</code>. Pick the skill groups you want; for this repo that&#8217;s Math and Optimization, MATLAB App Building, and MATLAB Core. (The installer is shared with the Simulink Agentic Toolkit, which is why the download lives in that repo&#8217;s releases.)</li>



<li>In MATLAB, run <code>shareMATLABSession()</code> so the agent works in the session you can see.</li>



<li>Open the repo in your agent. There&#8217;s an <code>AGENTS.md</code> (and a <code>CLAUDE.md</code> for Claude Code) that gives it the map, the rules for changes, and prompts that work.</li>
</ol>



<p class="wp-block-paragraph">Then you stop clicking and start asking. This is a real exchange from my session, with the agent driving MATLAB through the MCP server:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>Me:</strong> Load the sample slate, build 20 DraftKings tournament lineups with a 2-man stack, 40% max exposure and 3 unique players per pair, and tell me the five players where my exposure is furthest above projected ownership.</p>



<p class="wp-block-paragraph"><strong>Agent:</strong> Built 20 lineups (164.2 to 173.1 projected points, summed ownership 106% to 187%). Your biggest leverage plays:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Name</th><th>Pos</th><th>Team</th><th>Exposure</th><th>Field</th><th>Leverage</th></tr></thead><tbody><tr><td>Nico Ironwood</td><td>WR</td><td>IND</td><td>40%</td><td>1.7%</td><td>+38.3</td></tr><tr><td>Cameron Quillon</td><td>WR</td><td>DEN</td><td>40%</td><td>7.5%</td><td>+32.5</td></tr><tr><td>Gideon Okafor</td><td>WR</td><td>LAR</td><td>40%</td><td>7.7%</td><td>+32.3</td></tr><tr><td>Elliot Ashcombe</td><td>TE</td><td>MIA</td><td>40%</td><td>8.7%</td><td>+31.3</td></tr><tr><td>Xavi Fenwick</td><td>WR</td><td>NO</td><td>40%</td><td>11.7%</td><td>+28.3</td></tr></tbody></table></figure>
</blockquote>



<p class="wp-block-paragraph">The names are fake because the slate is synthetic. The workflow isn&#8217;t. The agent called <code>dfs.generateLineups</code>, sorted the exposure table by leverage, and showed me the top five. Then I asked it to add a rule I had been meaning to write for a year (cap the players from any one game), and it added the constraint, a validator clause, and a test, ran the suite, and reported 23 of 23.</p>



<p class="wp-block-paragraph">The README&#8217;s &#8220;getting started&#8221; section used to be steps for a person. Now the useful section is a list of prompts, each of which is a claim the agent can check by running the code.</p>



<h2 id="the-caveats-because-its-still-football" class="wp-block-heading">The caveats, because it&#8217;s still football</h2>



<p class="wp-block-paragraph">Projections are still projections. The optimizer is only as good as the numbers you feed it, and the numbers are a mean over a game that hasn&#8217;t happened.</p>



<p class="wp-block-paragraph">The Monte Carlo view uses per-position volatility guesses (quarterbacks are steadier than tight ends, defenses are chaos) that I picked, not measured. They&#8217;re labeled as working values in the code. Replace them with your own standard deviations if you have them; the simulator will use a <code>StdDev</code> column if it finds one.</p>



<p class="wp-block-paragraph">The stacking wisdom is what the DFS sites and the MIT paper agree on, but the correlation numbers you see quoted around the internet are site-reported, not something I verified. Treat the levers as structure, not as guarantees.</p>



<p class="wp-block-paragraph">And the sample slate&#8217;s players don&#8217;t exist. Please do not roster Nico Ironwood.</p>



<h2 id="go-build-one" class="wp-block-heading">Go build one</h2>



<p class="wp-block-paragraph">The code is MIT-licensed at <a href="https://github.com/nothans/dfs-optimizer">github.com/nothans/dfs-optimizer</a> and on <a href="https://www.mathworks.com/matlabcentral/fileexchange/117835-dfs-fantasy-football-lineup-optimizer">MATLAB File Exchange</a>. The <a href="https://nothans.com/win-at-dfs-by-optimizing-your-fantasy-football-lineups">2022 post</a> is still there if you want to optimize a lineup.</p>



<p class="wp-block-paragraph">Download a slate, run the Tournament preset, look at the exposure chart, and then argue with it. Let me know what your lineup looked like, and send a pull request if you teach it a new trick.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://nothans.com/dfs-fantasy-football-optimization-agentic-ai-matlab-optimization-toolbox/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5732</post-id>	</item>
		<item>
		<title>Create the &#8220;Best Agent Skill&#8221; with SkillOpt from Microsoft Research</title>
		<link>https://nothans.com/create-the-best-agent-skill-with-skillopt-from-microsoft-research</link>
					<comments>https://nothans.com/create-the-best-agent-skill-with-skillopt-from-microsoft-research#respond</comments>
		
		<dc:creator><![CDATA[Hans Scharler]]></dc:creator>
		<pubDate>Fri, 19 Jun 2026 01:15:49 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Agent Skills]]></category>
		<category><![CDATA[Codex]]></category>
		<category><![CDATA[Microsoft Research]]></category>
		<category><![CDATA[OpenAI]]></category>
		<category><![CDATA[SkillOpt]]></category>
		<guid isPermaLink="false">https://nothans.com/?p=5447</guid>

					<description><![CDATA[]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">For two years the move was simple: pick a better model. Then the models got good, and stayed good, and the gaps between them got boring. So the interesting lever is the other thing you hand the agent now. The skill file.</p>



<p class="wp-block-paragraph">Microsoft Research just shipped a tool called <a href="https://github.com/microsoft/SkillOpt">SkillOpt</a> that takes that idea literally.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">SkillOpt is a text-space optimizer that trains reusable natural-language skills for frozen LLM agents through trajectory-driven edits, validation-gated updates, and deployable best_skill.md artifacts.</p>
</blockquote>



<p class="wp-block-paragraph">It treats the skill markdown you hand an agent (the instructions, the system prompt, the SKILL.md) as trainable state. It runs epochs. It has a batch size. It has a learning rate. It just never touches the model weights. The weights stay frozen behind the API. The thing that gets trained is the text.</p>



<p class="wp-block-paragraph">The loop is short. Run the agent on a batch of tasks. Score each one. A second model reads the failures and proposes small edits to the skill file. Keep an edit only if it improves a held-out score. Repeat. What you get at the end is a&nbsp;<code>best_skill.md</code>&nbsp;you drop in front of the same unchanged model.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><a href="https://github.com/microsoft/SkillOpt"><img data-recalc-dims="1" loading="lazy" decoding="async" width="750" height="750" data-attachment-id="5448" data-permalink="https://nothans.com/create-the-best-agent-skill-with-skillopt-from-microsoft-research/image-108" data-orig-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/06/image.png?fit=750%2C750&amp;ssl=1" data-orig-size="750,750" data-comments-opened="0" data-image-title="SkillOpt from Microsoft Research" data-image-description="" data-image-caption="" data-large-file="https://i0.wp.com/nothans.com/wp-content/uploads/2026/06/image.png?fit=750%2C750&amp;ssl=1" src="https://i0.wp.com/nothans.com/wp-content/uploads/2026/06/image.png?resize=750%2C750&#038;ssl=1" alt="" class="wp-image-5448" style="width:504px;height:auto" srcset="https://i0.wp.com/nothans.com/wp-content/uploads/2026/06/image.png?w=750&amp;ssl=1 750w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/06/image.png?resize=300%2C300&amp;ssl=1 300w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/06/image.png?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/06/image.png?resize=530%2C530&amp;ssl=1 530w, https://i0.wp.com/nothans.com/wp-content/uploads/2026/06/image.png?resize=500%2C500&amp;ssl=1 500w" sizes="auto, (max-width: 750px) 100vw, 750px" /></a><figcaption class="wp-element-caption">Microsoft Research <a href="https://github.com/microsoft/SkillOpt">SkillOpt</a></figcaption></figure>
</div>


<p class="wp-block-paragraph">I wanted to see it actually work, so I gave it a job&#8230;</p>



<h2 id="the-setup" class="wp-block-heading">The setup</h2>



<p class="wp-block-paragraph">I had OpenAI Codex do the writing. The task: take a paragraph stuffed with AI slop and clean it up. The skill it started from was short:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Improve the passage so it reads a little better. Keep the meaning and roughly the length.</p>
</blockquote>



<p class="wp-block-paragraph">No mention of slop. No list of banned words. Nothing to go on.</p>



<p class="wp-block-paragraph">The verifier was the part that mattered. I wrote a dumb little counter that scans the output for the tells (the canned phrases, the em-dashes) and returns how many are left. Fewer is better. That is the whole eval. Objective, cheap, no judgment calls.</p>



<p class="wp-block-paragraph">Then I let SkillOpt run.</p>



<h2 id="what-it-wrote" class="wp-block-heading">What it wrote</h2>



<p class="wp-block-paragraph">The seed turned into a thirty-line deslop skill. SkillOpt wrote it.</p>



<p class="wp-block-paragraph">It worked out, on its own, that &#8220;remove AI slop&#8221; is a removal constraint and not a tone nudge. It named the exact phrases that kept leaking through (&#8220;let&#8217;s dive in,&#8221; &#8220;have you ever wondered,&#8221; &#8220;it&#8217;s worth noting,&#8221; &#8220;furthermore,&#8221; &#8220;in conclusion,&#8221; &#8220;great question&#8221;). It flagged em-dashes. And it caught the sneaky failure mode I never told it about: the model dodging a banned word by swapping in a synonym. That one became its own rule, the last line of the file:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Before answering, do a quick scan to ensure none of the original flagged words remain, including close synonyms you may have introduced.</p>
</blockquote>



<p class="wp-block-paragraph">The score went from passing half the held-out passages to passing all of them. The skill it wrote is one I would actually keep.</p>



<h2 id="the-part-worth-keeping" class="wp-block-heading">The part worth keeping</h2>



<p class="wp-block-paragraph">The optimizer is the easy part.</p>



<p class="wp-block-paragraph">I went in assuming the clever bit was the model proposing edits. It isn&#8217;t. The clever bit is the verifier. Give SkillOpt a score and nothing else and it does nothing. I tried that first. It saw the failures, shrugged, and changed not a single line, because &#8220;you got a 0.6&#8221; tells it nothing about what to fix. The run that worked was the one where the verifier also said which words leaked. Same model, same loop. The difference was the signal.</p>



<p class="wp-block-paragraph">So the rule of thumb is less &#8220;use the self-improving optimizer&#8221; and more: can you score this cheaply, and can you tell it why it failed. If yes, the skill mostly writes itself. If no, there is nothing to train and the fancy loop sits there idle.</p>



<p class="wp-block-paragraph">That is also the catch, and it is the same catch hiding under every self-improving-agent demo. A slop counter is a clean verifier. Most of what you do in a day is not. &#8220;Was this brief any good.&#8221; &#8220;Did this post land.&#8221; No cheap score, no training. The optimizer was never the bottleneck. The verifier is.</p>



<p class="wp-block-paragraph">So build the verifier first. Get that right and the skill mostly writes itself.</p>



<h2 id="bonus-a-taste-of-running-it" class="wp-block-heading">Bonus: a taste of running it</h2>



<p class="wp-block-paragraph">The repo is&nbsp;<a href="https://github.com/microsoft/SkillOpt">github.com/microsoft/SkillOpt</a>. MIT licensed, Python.</p>



<p class="wp-block-paragraph">Get it:</p>



<pre class="wp-block-code"><code>pip install skillopt
# or, to poke at the internals:
git clone https://github.com/microsoft/SkillOpt &amp;&amp; cd SkillOpt &amp;&amp; pip install -e .</code></pre>



<p class="wp-block-paragraph">The mental model is one small folder per task. A loader that hands over your examples, a rollout that runs your agent and scores it, and a seed skill to start from. SkillOpt&#8217;s whole job is to grow that seed.</p>



<p class="wp-block-paragraph">The config reads like a training run, on purpose:</p>



<pre class="wp-block-code"><code>train:
  num_epochs: 4
  batch_size: 40
optimizer:
  learning_rate: 4        # max edits to the skill per step
  lr_scheduler: cosine
evaluation:
  use_gate: true          # keep an edit only if it beats the held-out score
model:
  optimizer: gpt-5.4      # the model that proposes the edits</code></pre>



<p class="wp-block-paragraph">Then it is one command:</p>



<pre class="wp-block-code"><code>python scripts/train.py --config configs/yourtask/default.yaml</code></pre>



<p class="wp-block-paragraph">The only part that is really on you is the scoring. Your rollout returns, per task, a pass/fail and a number between 0 and 1:</p>



<pre class="wp-block-code"><code>return {"id": task_id, "hard": passed, "soft": fraction_correct}</code></pre>



<p class="wp-block-paragraph">That is the whole contract. Give it tasks, a way to score them, and a skill to start from. It runs the epochs.</p>



<p class="wp-block-paragraph">One side note: keep the tasks hard enough that the agent fails some of them. If it aces everything on the seed skill, there is nothing to learn and SkillOpt politely does nothing. Ask me how I know.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://nothans.com/create-the-best-agent-skill-with-skillopt-from-microsoft-research/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5447</post-id>	</item>
	</channel>
</rss>
