<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ty215</title>
    <description>The latest articles on DEV Community by ty215 (@ty215).</description>
    <link>https://dev.to/ty215</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4003976%2Fea562849-9420-452a-967c-9c6894287a45.png</url>
      <title>DEV Community: ty215</title>
      <link>https://dev.to/ty215</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ty215"/>
    <language>en</language>
    <item>
      <title>A Small Prototype for Cost-Aware Bug Investigation</title>
      <dc:creator>ty215</dc:creator>
      <pubDate>Fri, 10 Jul 2026 00:10:24 +0000</pubDate>
      <link>https://dev.to/ty215/a-small-prototype-for-cost-aware-bug-investigation-43p3</link>
      <guid>https://dev.to/ty215/a-small-prototype-for-cost-aware-bug-investigation-43p3</guid>
      <description>&lt;h2&gt;
  
  
  The question that started it
&lt;/h2&gt;

&lt;p&gt;A weak point in AI-assisted coding is not always broken syntax or a failing test suite. The original concern behind this project was about tests too: if the AI-assisted workflow helps with testing, is that set of tests enough?&lt;/p&gt;

&lt;p&gt;If the testing pattern is biased, a bug may sit where the tests do not look. The early idea was adversarial and game-theoretic, even equilibrium-like: treat bugs or failure conditions as if they were trying to stay hidden, then ask what testing or investigation policy would still hold up.&lt;/p&gt;

&lt;p&gt;That is not what &lt;code&gt;v0.1.0&lt;/code&gt; proves. The current repository is not a formal game-theoretic debugger, and it does not claim real-world debugging accuracy.&lt;/p&gt;

&lt;p&gt;That is the frame of &lt;a href="https://github.com/guriguri215-lang/bug-cause-inference-game/tree/v0.1.0" rel="noopener noreferrer"&gt;&lt;code&gt;bug-cause-inference-game&lt;/code&gt;&lt;/a&gt;, a small Python prototype for cost-aware bug-cause investigation. The version discussed here is tag &lt;code&gt;v0.1.0&lt;/code&gt;, which points to commit &lt;code&gt;9e30c93f246602d840c875e975c362e6ab1e7747&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This version is a preparation step. P1a asks how a cost-aware policy chooses the next investigation action on synthetic cases. P1b compares metadata-derived evidence with execution-grounded evidence in a small injected-bug scaffold. P1c looks beyond averages to worst-case and difficult buckets.&lt;/p&gt;

&lt;p&gt;The conclusion is narrow: the prototype shows the design value and limits of cost-aware investigation and worst-case reporting, not a finished debugger.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary matters
&lt;/h2&gt;

&lt;p&gt;The repository is careful about what it does not claim. It is not a production fault-localization engine, an automated repair tool, an LLM debugging benchmark, a fuzzing framework, or a formal game-theoretic debugger.&lt;/p&gt;

&lt;p&gt;That boundary is part of the design. The project is closest to probabilistic debugging, Bayesian fault diagnosis, and active diagnosis: rank likely causes from observations, then choose the next observation under cost.&lt;/p&gt;

&lt;p&gt;For AI-assisted coding workflows, that modest scope is the point. If generated code can look plausible while missing the intended behavior, the next layer of tooling should help decide what evidence to collect, not just produce another confident explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  P1a: make the investigation action explicit
&lt;/h2&gt;

&lt;p&gt;P1a is the smallest slice. It works on synthetic observed-bug cases, updates posterior probabilities over coarse cause categories, compares investigation policies, and emits a &lt;code&gt;DecisionReport&lt;/code&gt; whose main output is &lt;code&gt;recommended_next_action&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The synthetic dataset contains 50 cases, built from 5 cause categories and a fixed seed &lt;code&gt;20260627&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The 5 cause categories are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;boundary_condition&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;missing_null_handling&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;configuration_environment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;race_order_dependence&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;specification_mismatch&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each case has 8 possible investigation actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;inspect_error_log&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run_boundary_tests&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;compare_environment&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inspect_recent_diff&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run_reproduction_matrix&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add_instrumentation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;check_spec_acceptance&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run_concurrency_stress&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That action list is the first useful abstraction. Instead of asking an agent to "debug this," the prototype asks it to choose among named actions with explicit costs.&lt;/p&gt;

&lt;p&gt;The implemented P1a policies include &lt;code&gt;random&lt;/code&gt;, &lt;code&gt;fixed_checklist&lt;/code&gt;, &lt;code&gt;posterior_greedy&lt;/code&gt;, &lt;code&gt;cheapest_first&lt;/code&gt;, &lt;code&gt;information_gain&lt;/code&gt;, &lt;code&gt;information_gain_per_cost&lt;/code&gt;, and &lt;code&gt;static_posterior&lt;/code&gt;. The main policy is &lt;code&gt;information_gain_per_cost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The stopping settings are also explicit: top probability threshold &lt;code&gt;0.75&lt;/code&gt;, margin threshold &lt;code&gt;0.15&lt;/code&gt;, budget limit &lt;code&gt;10&lt;/code&gt;, max steps &lt;code&gt;5&lt;/code&gt;, minimum expected information gain per cost &lt;code&gt;0.03&lt;/code&gt;, and failure cost &lt;code&gt;12&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That makes the prototype less magical. It cannot hide behind "the model decided." The design keeps the current belief state, the available actions, the action costs, and the next recommended action explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What P1a showed
&lt;/h2&gt;

&lt;p&gt;The current P1a dataset is not hard. Before any investigation action, the generated cases already have initial top-1 accuracy of &lt;code&gt;70%&lt;/code&gt; and initial top-2 accuracy of &lt;code&gt;100%&lt;/code&gt;, with 15 initially-wrong top-1 cases out of 50.&lt;/p&gt;

&lt;p&gt;Even with that caveat, the policy comparison is useful. On the synthetic setup, &lt;code&gt;information_gain_per_cost&lt;/code&gt; has mean &lt;code&gt;cost_to_true_cause_top1&lt;/code&gt; of &lt;code&gt;1.12&lt;/code&gt;, while &lt;code&gt;fixed_checklist&lt;/code&gt; has &lt;code&gt;1.56&lt;/code&gt;. The recorded fixed-checklist cost reduction is about &lt;code&gt;28%&lt;/code&gt;, and the primary success rate within budget is &lt;code&gt;94%&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same result also exposes a warning sign. The primary wrong-stop rate is about &lt;code&gt;13%&lt;/code&gt;, where wrong-stop means stopping on a high-confidence but incorrect cause hypothesis.&lt;/p&gt;

&lt;p&gt;The initially-wrong subset makes the tradeoff sharper. On those cases, &lt;code&gt;information_gain_per_cost&lt;/code&gt; has lower mean cost than &lt;code&gt;fixed_checklist&lt;/code&gt; (&lt;code&gt;3.73&lt;/code&gt; versus &lt;code&gt;5.2&lt;/code&gt;), but &lt;code&gt;fixed_checklist&lt;/code&gt; has the higher success rate (&lt;code&gt;0.87&lt;/code&gt; versus &lt;code&gt;0.8&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;So the safe reading is narrow: cost-aware active investigation reduced average investigation cost in this synthetic setup, but it did not prove real-world debugging accuracy.&lt;/p&gt;

&lt;p&gt;That is already a useful habit for AI-assisted coding tools. A good report should show where the policy saves cost, where it stops too early, and where a boring checklist still wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  P1b: make the evidence less synthetic
&lt;/h2&gt;

&lt;p&gt;P1b adds a small injected-bug checkout/pricing benchmark scaffold. It evaluates budget-aware policies on 20 buggy variants and 5 clean variants.&lt;/p&gt;

&lt;p&gt;The P1b evaluator looks at failure discovery, function-level location ranking, coarse cause inference, and fix-intent prediction. It still does not generate patches.&lt;/p&gt;

&lt;p&gt;P1b uses two observation modes. &lt;code&gt;metadata_synth&lt;/code&gt; is the frozen Phase A/B baseline that synthesizes evidence from variant metadata. &lt;code&gt;execution_grounded&lt;/code&gt; builds observations from checkout test results, exceptions, traced checkout functions, coverage-spectrum suspicion, and Phase C real-diff artifacts.&lt;/p&gt;

&lt;p&gt;The P1b action set is larger than P1a's and closer to a debugging workflow. It includes test actions such as &lt;code&gt;run_smoke_tests&lt;/code&gt;, &lt;code&gt;run_boundary_tests&lt;/code&gt;, &lt;code&gt;run_null_missing_tests&lt;/code&gt;, &lt;code&gt;run_config_matrix_tests&lt;/code&gt;, and &lt;code&gt;run_state_sequence_tests&lt;/code&gt;, plus inspection actions such as &lt;code&gt;inspect_traceback&lt;/code&gt;, &lt;code&gt;inspect_coverage_spectrum&lt;/code&gt;, &lt;code&gt;inspect_recent_diff&lt;/code&gt;, and &lt;code&gt;inspect_spec_clause&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The main P1b policy is &lt;code&gt;expected_utility_per_cost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is still a small scaffold, but it creates a useful pressure test: does a policy that looks good with metadata-derived evidence still look good when the observations are tied to execution?&lt;/p&gt;

&lt;h2&gt;
  
  
  The metadata optimism gap
&lt;/h2&gt;

&lt;p&gt;If this article had to be reduced to one lesson, it would be the P1b comparison. The &lt;code&gt;metadata_synth&lt;/code&gt; baseline made the primary policy look better than &lt;code&gt;execution_grounded&lt;/code&gt; evidence on several metrics.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;expected_utility_per_cost&lt;/code&gt;, the primary-policy comparison reports:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;&lt;code&gt;metadata_synth&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;execution_grounded&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;bug discovery within budget&lt;/td&gt;
&lt;td&gt;0.55&lt;/td&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cause top-1 accuracy&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;0.55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fix-intent top-1 accuracy&lt;/td&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mean investigation cost&lt;/td&gt;
&lt;td&gt;2.80&lt;/td&gt;
&lt;td&gt;4.64&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;metadata_synth&lt;/code&gt; still has a role as a baseline. The point is that its optimism gap is visible.&lt;/p&gt;

&lt;p&gt;For AI-assisted coding, this is the more general warning. Metadata-derived evidence can make a policy look better than it looks with execution-grounded evidence. Execution-grounded evidence is still small here, but the gap is visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  P1c: stop hiding behind averages
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;v0.1.0&lt;/code&gt;, P1c is an analysis-only report over the existing 25-variant P1b scaffold.&lt;/p&gt;

&lt;p&gt;The first P1c goal is to ask whether existing investigation policies remain useful when the evidence is deliberately difficult, ambiguous, expensive, or misleading inside a small reproducible benchmark.&lt;/p&gt;

&lt;p&gt;The planning notes name several candidate adversarial models: worst-case variant selection, evidence-ambiguity buckets, observation-cost stress, and observation dropout or delay.&lt;/p&gt;

&lt;p&gt;The implemented label buckets are &lt;code&gt;boundary_precision&lt;/code&gt;, &lt;code&gt;missing_optional_input&lt;/code&gt;, &lt;code&gt;config_normalization&lt;/code&gt;, &lt;code&gt;state_sequence&lt;/code&gt;, &lt;code&gt;spec_semantics&lt;/code&gt;, and &lt;code&gt;clean_false_positive&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The important design choice is that P1c does not collapse the result into one score. Discovery, first-failure cost, localization, cause, fix-intent, false-positive, and mean-cost metrics can point to different weaknesses.&lt;/p&gt;

&lt;p&gt;That choice fits the original concern better than an average-only report. If a bug behaves like a difficult opponent, the average case is often the least interesting case.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the worst-case view exposed
&lt;/h2&gt;

&lt;p&gt;For the current primary P1b/P1c policy, &lt;code&gt;expected_utility_per_cost&lt;/code&gt;, the aggregate reading is a useful baseline: discovery &lt;code&gt;0.40&lt;/code&gt;, cause top-1 &lt;code&gt;0.55&lt;/code&gt;, fix-intent top-1 &lt;code&gt;0.40&lt;/code&gt;, and mean investigation cost &lt;code&gt;4.64&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Those averages need the bucket view next to them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bucket&lt;/th&gt;
&lt;th&gt;What P1c exposed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;state_sequence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The primary brittle bucket: discovery, location top-3, cause top-1, and fix-intent top-1 are all &lt;code&gt;0.0&lt;/code&gt;; first-failure cost is &lt;code&gt;14&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;config_normalization&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The next broad weak bucket: those four metrics are all &lt;code&gt;0.25&lt;/code&gt;; first-failure cost is &lt;code&gt;10.75&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;missing_optional_input&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Successful but expensive: the four success metrics are &lt;code&gt;1.0&lt;/code&gt; and first-failure cost is &lt;code&gt;1&lt;/code&gt;, but mean investigation cost is &lt;code&gt;9.75&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The current clean variants do not trigger false positives in the selected-bucket report or current stress profiles, but that does not show false-positive risk is solved outside this scaffold.&lt;/p&gt;

&lt;p&gt;This is why the P1c layer matters. It turns "the average looks acceptable" into "which class of case breaks the policy, and how?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Why call it a game at all?
&lt;/h2&gt;

&lt;p&gt;The word "game" should be handled carefully here. The current repository does not claim a formal player model, payoff model, minimax-optimal policy, or game-theoretic guarantee.&lt;/p&gt;

&lt;p&gt;The game-like idea comes from the original motivation: if bugs may hide where tests do not look, treat hard bugs or failure conditions as if they are selected by an opponent that wants to stay hidden, then ask which investigation policy still performs under that selection pressure.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;v0.1.0&lt;/code&gt;, that idea is still bounded. P1c selects worst cases and stress conditions inside a small injected benchmark; it does not generate new bugs, repair code, or claim real-world debugging accuracy.&lt;/p&gt;

&lt;p&gt;That boundary keeps the current result readable. Formal game-theoretic claims stay outside this version until the project has an explicit player model, action space, payoff, and evaluation result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;For an AI-assisted coding workflow, the design lesson is deliberately small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make investigation actions explicit.&lt;/li&gt;
&lt;li&gt;Attach costs to those actions.&lt;/li&gt;
&lt;li&gt;Compare policies under the same stopping rules and budget limits.&lt;/li&gt;
&lt;li&gt;Separate metadata-like evidence from execution-grounded evidence.&lt;/li&gt;
&lt;li&gt;Report worst-case buckets beside averages.&lt;/li&gt;
&lt;li&gt;Keep non-claims visible, especially when the prototype is small.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is easy to skip. It is also the part that makes the experiment reusable. A small benchmark can still teach something if it is honest about what it cannot show.&lt;/p&gt;

&lt;p&gt;Source version: &lt;a href="https://github.com/guriguri215-lang/bug-cause-inference-game/tree/v0.1.0" rel="noopener noreferrer"&gt;&lt;code&gt;guriguri215-lang/bug-cause-inference-game&lt;/code&gt; at &lt;code&gt;v0.1.0&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Disclosure: this article draft was prepared with AI assistance, and the project claims and numbers were checked against the linked repository version and DEV's AI-assisted article guidance.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>python</category>
      <category>abotwrotethis</category>
    </item>
    <item>
      <title>The Less I Type, the Better the Output: A Context Framework I Had Claude Design for AI Coding</title>
      <dc:creator>ty215</dc:creator>
      <pubDate>Sun, 05 Jul 2026 15:18:30 +0000</pubDate>
      <link>https://dev.to/ty215/the-less-i-type-the-better-the-output-a-context-framework-i-had-claude-design-for-ai-coding-534n</link>
      <guid>https://dev.to/ty215/the-less-i-type-the-better-the-output-a-context-framework-i-had-claude-design-for-ai-coding-534n</guid>
      <description>&lt;p&gt;Fable 5, a Claude model, became available to me for a limited window. I could have spent that window shipping features. Instead I had it build something that would outlast the model itself: a context framework for running AI coding across several models.&lt;/p&gt;

&lt;p&gt;The reason is a thing I keep circling back to when I work with coding agents. The less a human does by hand, the better the quality and the speed. Not because people are bad at the work, but because every manual step is a place to forget a constraint or approve something on autopilot.&lt;/p&gt;

&lt;p&gt;So this is a design write-up, not a benchmark post. I have not run this on a long project for long enough to put numbers on it. What I can tell you is that using it felt more efficient, and I can show you the exact failure modes it was built to kill. If you want a graph, this isn't that article. If you keep watching your agent walk off in the wrong direction and you keep rewriting the same prompt, keep reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pain I kept hitting
&lt;/h2&gt;

&lt;p&gt;Before any of this, my problem was simple and expensive. The agent would drift. It would go off on something that wasn't the point, burn tokens on it, and I would spend real time pulling it back on course.&lt;/p&gt;

&lt;p&gt;A lot of that traced back to me. When I write a task by hand, I leave out the parts I think are obvious. The agent doesn't share my "obviously." It fills the gap with its own assumption, does something I didn't ask for, and now there's rework. Sometimes the thing runs away entirely.&lt;/p&gt;

&lt;p&gt;The workaround I found almost by accident: don't write the prompt myself. Have a model write the prompt, then read it and check it before it runs. That was steadier. This framework is that habit turned into a system.&lt;/p&gt;

&lt;p&gt;Watching the same thing happen over and over, I could name four failure modes I wanted gone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the agent going off the rails&lt;/li&gt;
&lt;li&gt;too many pull requests and re-reviews that never end&lt;/li&gt;
&lt;li&gt;my own bias pulling the work along, so course-correction comes late&lt;/li&gt;
&lt;li&gt;approvals turning into rubber stamps once things get automated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing up front: the framework's own docs treat these four as design targets, and they label their effect numbers as hypotheses, not measurements. I'm keeping that line here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea: the human doesn't write long prompts
&lt;/h2&gt;

&lt;p&gt;The center of the whole thing is one sentence. The human does not write long prompts. A model writes the prompt for the next step into a file, and the human copies and pastes it.&lt;/p&gt;

&lt;p&gt;Day to day, the human does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;NEXT_ACTION.md&lt;/code&gt; to see what's next.&lt;/li&gt;
&lt;li&gt;Copy &lt;code&gt;NEXT_PROMPT.md&lt;/code&gt; and paste it into the model it names.&lt;/li&gt;
&lt;li&gt;Read only the "needs human confirmation" part, then approve or send it back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every model, when it finishes, is required to update &lt;code&gt;NEXT_ACTION.md&lt;/code&gt; and &lt;code&gt;NEXT_PROMPT.md&lt;/code&gt;. So the loop keeps going on its own. I'm not composing instructions each turn.&lt;/p&gt;

&lt;p&gt;The files sit next to the repo, not inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;project root&amp;gt;/
  AGENTS.md              # short pointer for whichever CLI starts here
  _ai_management/        # the framework files (kept out of git)
  repo/                  # the actual code you git init and push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That split matters: the code you publish stays clean, and the coordination files stay out of your commit history.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four failure modes, and the design response
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Going off the rails
&lt;/h3&gt;

&lt;p&gt;This was my worst one. The agent drifts, and course-correction eats the afternoon.&lt;/p&gt;

&lt;p&gt;The design response is boring on purpose. The spec is the single source of truth, and a model is not allowed to implement anything that depends on an open, undecided question. That's a hard stop, not a suggestion. Acceptance criteria can't be written as "it works correctly"; they have to name the input, the output, and the command that checks them. And each task is routed by how much damage a wrong move would cost, not by which model I happen to like. There are defined routes from light fixes up to research work, with a rule to pick the heavier route when you're unsure.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Too many PRs, reviews that never end
&lt;/h3&gt;

&lt;p&gt;Push "be critical" too hard and review turns into a game. The count of comments starts to look like the output, so a reviewer manufactures harmless nitpicks and sends the work back again and again.&lt;/p&gt;

&lt;p&gt;The response is a review discipline with a few hard rules. Every comment has to carry a concrete failure scenario: which input, in which state, produces which wrong result. If you can't write that, it goes in an "observations" box and does not block the decision. Only high and medium severity can send work back; low-severity-only means approve with notes. A re-review after a bounce is scoped to "did the previous points get fixed," plus new high-severity only, so a bounce can't be stretched into three. Two bounces, then the route changes instead of a third. A request to send work back also has to state repair cost against the damage of leaving it in; if the damage is smaller than the fix, it doesn't bounce. And the model that wrote the code doesn't get to be its final reviewer. The last review crosses model families.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. My own bias pulling the work along
&lt;/h3&gt;

&lt;p&gt;My input is a mix of two things: decisions I've actually made, and opinions I'm still thinking through. If the model treats a half-formed opinion as a decision, my under-baked idea gets built, and quality drops.&lt;/p&gt;

&lt;p&gt;The response is a prefix convention. Anything that starts with &lt;code&gt;決定:&lt;/code&gt; (decision:) is a decision. It's executed without debate and logged. Anything else is an opinion, and the model has to answer with a technical judgment, agree, agree with conditions, or disagree, and give its grounds before it acts. Flattering agreement is banned; it can't open with "you're absolutely right." It's also not required to disagree. If my idea is the best one, it says so in a line and takes it. When it does disagree and I override with &lt;code&gt;決定:&lt;/code&gt;, it runs the thing but logs "human decision over model objection" with the reason, so later we can check who was right. Purpose and priorities stay my call; facts and implementation correctness get checked against evidence, whoever said them.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rubber-stamp approvals
&lt;/h3&gt;

&lt;p&gt;When a person approves machine output, a bias creeps in: "it was generated, it's probably fine." Approval goes hollow. A bare "please confirm" makes it worse.&lt;/p&gt;

&lt;p&gt;The response is an approval gate format. No free-form "looks good?" A request has to be filled into a fixed block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Approval gate&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Approving: &lt;span class="nt"&gt;&amp;lt;one&lt;/span&gt; &lt;span class="na"&gt;line&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Risk: high / medium / low
&lt;span class="p"&gt;-&lt;/span&gt; Human checks (max 3): &lt;span class="nt"&gt;&amp;lt;which&lt;/span&gt; &lt;span class="na"&gt;file&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt; &lt;span class="na"&gt;look&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt; &lt;span class="na"&gt;what&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;OK&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="na"&gt;looks&lt;/span&gt; &lt;span class="na"&gt;like&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Unapproval conditions (don't approve if any one is true):
&lt;span class="p"&gt;  -&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;listed&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt; &lt;span class="na"&gt;verifiable&lt;/span&gt; &lt;span class="na"&gt;form&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt; &lt;span class="na"&gt;least&lt;/span&gt; &lt;span class="na"&gt;one&lt;/span&gt; &lt;span class="na"&gt;checkable&lt;/span&gt; &lt;span class="na"&gt;by&lt;/span&gt; &lt;span class="na"&gt;a&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Unverified: &lt;span class="nt"&gt;&amp;lt;what&lt;/span&gt; &lt;span class="na"&gt;the&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt; &lt;span class="na"&gt;couldn&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="na"&gt;t&lt;/span&gt; &lt;span class="na"&gt;verify&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt; &lt;span class="na"&gt;writing&lt;/span&gt; &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="na"&gt;none&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="na"&gt;needs&lt;/span&gt; &lt;span class="na"&gt;grounds&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Recommendation: approve / approve after scrutiny / don't approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The framework's labels are in Japanese; this is my English rendering of the same shape.) The unapproval conditions are the load-bearing part: the specific things that, if any one is true, mean do not approve, with at least one written so a command can verify it. If a model can't write those conditions, the task isn't ready, and it gets sent back on the spot. The final gate is written by the last model that reviewed the work, not the one that built it, because you go easy on your own output. And every so often, a low-risk "light approval" gets pulled at random for a full check, to see whether the light path has gone hollow.&lt;/p&gt;

&lt;p&gt;If you read the three above closely, they're one problem wearing three faces. The docs put it this way: evaluation gets bent by relationship instead of evidence. The single rule under all of it is the same shape each time. Agreement needs grounds, objection needs a failure scenario, approval needs unapproval conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised me, and where this stops
&lt;/h2&gt;

&lt;p&gt;The honest part. What surprised me wasn't a clever trick. I didn't expect a model to build something this large. It came back as a full folder of specs, logs, and templates.&lt;/p&gt;

&lt;p&gt;I also left one thing out on purpose. There's a benchmark stage in the design, and I chose not to run it. The project keeps qualitative notes only, no metrics. That's a real gap, and it's why I keep writing "felt" instead of "measured." I haven't run it long enough to claim a number.&lt;/p&gt;

&lt;p&gt;So read the four responses above as design intent that lined up with my experience, not as proven results. The failure modes are real, and I've hit every one of them. Whether this framework cuts them by some percentage, I can't tell you yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;If I compress the whole thing to one line, it's what I'd say first if a friend asked: cut the amount the human does by hand, and both quality and speed go up. Fewer manual steps means fewer places to slip.&lt;/p&gt;

&lt;p&gt;The other half is about what you keep for the human. Don't pull the human out of approval. Make approval cheap and honest instead: outputs the human can check quickly, a small amount to check, and an explanation attached. In this framework the human's approval concentrates at just three points: locking the spec, the final sign-off on a diff, and anything irreversible like a push.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: the framework described here was designed by Claude (Fable 5). This article was written with AI assistance, then edited and fact-checked by me; the design choices and the experience are mine. Also tagged #abotwrotethis.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>claude</category>
      <category>abotwrotethis</category>
    </item>
    <item>
      <title>Before the Algorithm: Building the Input Layer for My Poker Analysis Tool</title>
      <dc:creator>ty215</dc:creator>
      <pubDate>Sun, 28 Jun 2026 13:41:42 +0000</pubDate>
      <link>https://dev.to/ty215/before-the-algorithm-building-the-input-layer-for-my-poker-analysis-tool-4ape</link>
      <guid>https://dev.to/ty215/before-the-algorithm-building-the-input-layer-for-my-poker-analysis-tool-4ape</guid>
      <description>&lt;p&gt;In my first post about this project I described &lt;code&gt;repeated-poker-analysis&lt;/code&gt;: a small, abstract toolkit for studying repeated poker spots as a commitment-analysis problem. It is a research and learning project, not a poker solver and not real-money advice.&lt;/p&gt;

&lt;p&gt;That post ended on the analysis side, with a hand-built toy game where committing to a fixed strategy beats the one-shot baseline. I planned to spend the next stretch on the algorithm. In practice I spent most of it on input, validation, and saving — the layer the algorithm sits on. This post is about that part, and about the abstract model growing a bit at the same time.&lt;/p&gt;

&lt;p&gt;This describes the project on the &lt;code&gt;feat/single-hand-form-edit-cli&lt;/code&gt; branch at the time of writing, so details may move after it is merged.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed since the last post
&lt;/h2&gt;

&lt;p&gt;Last time, scenarios were small fixed examples and helper functions inside the code. To analyze anything new, I edited Python.&lt;/p&gt;

&lt;p&gt;Two things changed. First, a scenario is now a JSON file: the tool reads it, checks it, builds the abstract game, runs the existing analysis pipeline, and can export the result. Second, the abstract model itself widened — I added more scenario modes, so the JSON can describe slightly richer hand and action structure than the single toy spot from the first post.&lt;/p&gt;

&lt;p&gt;So this wasn't a stretch with no model work. It was widening the abstract model and the input formats together, while keeping both small enough to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the input layer came first
&lt;/h2&gt;

&lt;p&gt;Analysis output is only as trustworthy as its input. If a scenario is silently malformed — a probability that doesn't sum to one, a missing matrix cell — then any EV or deadline the tool reports is meaningless, and I might not notice.&lt;/p&gt;

&lt;p&gt;So before making the analysis bigger, I wanted the input to fail loudly when it's wrong instead of quietly producing a plausible-looking number. That meant firming up the input and validation layer first, even though it isn't the part I was looking forward to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a JSON scenario is
&lt;/h2&gt;

&lt;p&gt;A scenario is a JSON description of one small abstract river spot: the pot and bet sizes, the rake rule, how showdowns resolve, a baseline strategy, and the repeated-game parameters. JSON is the source of truth — every other representation in the tool is derived from it and checked back against it.&lt;/p&gt;

&lt;p&gt;Reading one through the current loader looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JSON scenario
 -&amp;gt; detect mode
 -&amp;gt; parse + build the abstract game
 -&amp;gt; (analysis pipeline, if requested)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON is parsed and built into the actual game object before anything trusts it, so a broken scenario is rejected at the door.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five scenario modes
&lt;/h2&gt;

&lt;p&gt;I added the modes in order, each one allowing a little more abstract structure than the last:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;single-hand&lt;/strong&gt; — one abstract hand bucket. Check/bet for one player, call/fold for the other. This is the mode the first post's toy game used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hero-range-only&lt;/strong&gt; — several Hero buckets with weights; the opponent still acts on a single information set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;showdown-matrix&lt;/strong&gt; — Hero and Villain buckets, with each matchup's result given directly as hero / villain / chop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;equity-matrix&lt;/strong&gt; — the same grid, but each cell is a Hero pot share between 0 and 1 instead of a discrete result. These numbers are supplied in the JSON; the tool does not evaluate real cards or compute equity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;river betting-tree&lt;/strong&gt; — one street with a few more actions: an IP stab after an OOP check, and a single IP raise line against an OOP bet. It's still a fixed one-street tree, with no re-raises, nested sizings, or street transitions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The abstraction level is still high. There's no real card-range parsing, no real card evaluation, no external solver import, no solver-grade GTO calculation, no multiple streets, and no arbitrary nested betting trees.&lt;/p&gt;

&lt;p&gt;I split this into five modes on purpose. The goal wasn't to make the model large quickly — it was to keep each step small enough that when something broke, I could tell which stage and which input shape caused it. So the model and input formats grew incrementally, but there's still groundwork to do before any of this becomes realistic analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built form models before the GUI
&lt;/h2&gt;

&lt;p&gt;I want a GUI eventually, so editing a scenario doesn't mean hand-writing JSON. But before building any screens, I built a &lt;em&gt;form model&lt;/em&gt; for each of the five modes.&lt;/p&gt;

&lt;p&gt;A form model is a flat, edit-friendly representation of a scenario — the shape a GUI form would bind to. It doesn't replace the JSON; the JSON stays the source of truth. The form model just gives a future screen something convenient to read from and write to.&lt;/p&gt;

&lt;p&gt;Two design choices mattered here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The validator returns field-level messages instead of throwing. While editing, a form is often temporarily broken — a probability that doesn't sum to one yet, a half-typed value — and a GUI needs to show that next to the field rather than crash.&lt;/li&gt;
&lt;li&gt;Invalid values are not quietly rounded to valid ones. If a field is wrong, it's surfaced as an error before saving, not silently "fixed." A scenario that passes validation is one that round-trips back through the parser and builder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building the form models for all five modes first let me check the data structures behind the screens before committing to any screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the inspect / roundtrip / edit CLIs check
&lt;/h2&gt;

&lt;p&gt;To confirm the form layer works without a GUI, there are three small developer-utility CLIs. They aren't user features; they're how I check the plumbing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;inspect_scenario_form.py&lt;/code&gt; reads a scenario through its form model, detects the mode, runs validation, and confirms it round-trips back through the parser and builder. It's a health check for the input layer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;roundtrip_scenario_form.py&lt;/code&gt; reads a scenario, runs it through the form model, and writes it back out — only if validation and the round-trip succeed. This is close to what a GUI "save" will eventually do.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;edit_scenario_form.py&lt;/code&gt; reads a scenario, edits a few fields, validates, and saves. It's the minimal version of "change a field and save it," and for now it's single-hand only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inspecting the first post's toy game looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python scripts/inspect_scenario_form.py examples/scenarios/nuts_chop_steal_bet98.json
Scenario form inspection
scenario_id: nuts_chop_steal_bet98
mode: single-hand
form: SingleHandScenarioForm
validation: ok
round_trip_parse: ok
round_trip_build: ok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a boring output, which is what I wanted: it confirms the read → validate → round-trip path holds before I build a screen on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with AI as the codebase grew
&lt;/h2&gt;

&lt;p&gt;I'm building this with AI assistance, and how I work with it changed as the code grew.&lt;/p&gt;

&lt;p&gt;I went into this stretch expecting to get a lot done over a weekend. To keep quality up, though, I started having the assistant explain code back to me, adding more tests, and running a review pass before moving on. That slowed feature work down, and the stretch ended at the form layer rather than further along.&lt;/p&gt;

&lt;p&gt;I don't think that was bad stalling. It's the scaffold I'll need to trust the analysis and the GUI later. The cost is that explaining, testing, and reviewing as I go uses far more tokens and takes longer than just generating code. It's still clearly faster than doing all of this alone from scratch — but on a project this size, the trade-off is something I now weigh.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests and verification
&lt;/h2&gt;

&lt;p&gt;There are now over 1,000 tests. I want to be careful about what that means: it does not mean I'm verifying a large poker solver. It means I've accumulated checks on a small abstract model, its input/output paths, and boundary conditions — for example, that all five modes parse, that a CLI writes JSON and nothing else, that it won't overwrite a file unless told to, and that it writes nothing when validation fails.&lt;/p&gt;

&lt;p&gt;That kind of check is most of the test count. It buys confidence in the plumbing, not in any real-world poker claim.&lt;/p&gt;

&lt;p&gt;The harder question is still open: as the project grows, the amount I'd need to verify grows faster than I can read. I haven't solved that. I expect I'll have to validate from the outside — design the checks to be as exhaustive as I can and run many configurations — rather than reading every line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current limitations
&lt;/h2&gt;

&lt;p&gt;At this stage the tool is still a small abstract toolkit. In particular, it does not have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a real card-range parser or real hand evaluation,&lt;/li&gt;
&lt;li&gt;any external solver import or solver-grade GTO output,&lt;/li&gt;
&lt;li&gt;an arbitrary game-tree editor, multiple streets, or re-raise / multi-sizing / nested betting trees,&lt;/li&gt;
&lt;li&gt;a GUI yet, or edit CLIs for the range / matrix / betting-tree modes (edit is single-hand only),&lt;/li&gt;
&lt;li&gt;anything resembling real-money strategy advice, or any model of human learning speed or psychology.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The EV numbers it produces are outputs of a deliberately small model, not poker advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I want to do next
&lt;/h2&gt;

&lt;p&gt;The near-term goal is to make the tool easier to run and watch — to get from "abstract model that passes its checks" to "I can load a scenario, change it, and see the result" without editing Python.&lt;/p&gt;

&lt;p&gt;Concretely, that means extending the edit flow beyond single-hand, and eventually a minimal GUI on top of the form models that are already in place. Alongside that, I still want to work out the verification approach for when the model grows, since that's the part I don't have a good answer for yet.&lt;/p&gt;

&lt;p&gt;This stretch was mostly groundwork. It's not the part I was looking forward to, but the algorithm work I do want to do will be easier to trust because of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/guriguri215-lang/repeated-poker-analysis" rel="noopener noreferrer"&gt;guriguri215-lang/repeated-poker-analysis&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Scenario format reference: &lt;a href="https://github.com/guriguri215-lang/repeated-poker-analysis/blob/main/docs/scenario_format_reference.md" rel="noopener noreferrer"&gt;docs/scenario_format_reference.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GUI / input design doc: &lt;a href="https://github.com/guriguri215-lang/repeated-poker-analysis/blob/main/docs/gui_input_design.md" rel="noopener noreferrer"&gt;docs/gui_input_design.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MVP walkthrough: &lt;a href="https://github.com/guriguri215-lang/repeated-poker-analysis/blob/main/docs/mvp_walkthrough.md" rel="noopener noreferrer"&gt;docs/mvp_walkthrough.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Assumptions and limitations: &lt;a href="https://github.com/guriguri215-lang/repeated-poker-analysis/blob/main/docs/assumptions_and_limitations.md" rel="noopener noreferrer"&gt;docs/assumptions_and_limitations.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>showdev</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Why I Built a Tiny Repeated-Game Poker Analysis Tool</title>
      <dc:creator>ty215</dc:creator>
      <pubDate>Sat, 27 Jun 2026 00:23:00 +0000</pubDate>
      <link>https://dev.to/ty215/why-i-built-a-tiny-repeated-game-poker-analysis-tool-3joa</link>
      <guid>https://dev.to/ty215/why-i-built-a-tiny-repeated-game-poker-analysis-tool-3joa</guid>
      <description>&lt;p&gt;Most poker solvers answer one question very well: given a single hand and a single decision tree, what is the equilibrium strategy? (Yes, there is subgame solving, node locking, and plenty more — but the default frame is still one hand, one equilibrium.)&lt;/p&gt;

&lt;p&gt;I kept getting stuck on a different one. What if the &lt;em&gt;same kind&lt;/em&gt; of spot shows up over and over, and a player can commit to a fixed strategy across those repetitions? In a few toy games I had a hunch, worked out by hand, that committing to a fixed strategy could change its value relative to the one-shot picture. I wanted a tool that could make that commitment value precise — to actually &lt;em&gt;analyze&lt;/em&gt; it rather than just believe it. (Whether any of this rises to a repeated-game equilibrium is a much stronger claim, and one I am deliberately not making here.)&lt;/p&gt;

&lt;p&gt;I'm still learning software engineering, so until recently I couldn't implement this — I was stuck reasoning about toy games on paper. AI tooling made the analysis feasible, so I finally started building it: &lt;code&gt;repeated-poker-analysis&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's a small research project: write one narrow model down, run small examples, and record what the model does and doesn't justify.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;repeated-poker-analysis&lt;/code&gt; is
&lt;/h2&gt;

&lt;p&gt;It is an experimental Python toolkit for small abstract poker games. The current MVP covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fixed Hero commitment candidates,&lt;/li&gt;
&lt;li&gt;exact Villain best-response diagnostics in small finite trees,&lt;/li&gt;
&lt;li&gt;candidate generation and filtering,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;T_deadline&lt;/code&gt;, an economic adaptation deadline,&lt;/li&gt;
&lt;li&gt;local &lt;code&gt;T_detect&lt;/code&gt;, an observable-distribution sensitivity estimate,&lt;/li&gt;
&lt;li&gt;analysis reports and Markdown summaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is small on purpose. It is not a full solver and it is not wired to real solver ranges. It starts from one toy game — a river spot — that is tiny enough to inspect and test by hand.&lt;/p&gt;

&lt;p&gt;That toy spot is one where showdown always chops but rake still bites. In a single-hand view, putting more money into a raked pot can be locally unattractive. Across repeated occurrences the same spot raises a commitment question: if one player refuses to fold in a fixed pattern, how does the other respond, and how fast would that response have to come for the commitment to stop being worth it?&lt;/p&gt;

&lt;p&gt;This is the question I wanted a tool to make precise — not a claim that any new equilibrium exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why repeated poker is tempting — and where the trap is
&lt;/h2&gt;

&lt;p&gt;Repeated games sound like a natural home for reputation, punishment, and adaptation, and poker has obvious repeated structure: similar river spots, similar blind-vs-blind situations, similar sizings, similar pools.&lt;/p&gt;

&lt;p&gt;Here is the trap I had to respect. If the number of repetitions is known, the game is fully observed, each spot is independent, and both players are perfectly rational, then a finite repeated game often collapses back toward the one-shot equilibrium by backward induction. "This spot happens five times" is &lt;em&gt;not&lt;/em&gt; by itself enough to claim a reputation equilibrium. That is the standard game-theory result, and it is the reason the project keeps the layers below separate.&lt;/p&gt;

&lt;p&gt;So the project keeps several ideas apart that are easy to blur:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a one-hand baseline strategy,&lt;/li&gt;
&lt;li&gt;a fixed Hero commitment candidate,&lt;/li&gt;
&lt;li&gt;Villain's exact best response to that fixed Hero strategy,&lt;/li&gt;
&lt;li&gt;an economic deadline for adaptation,&lt;/li&gt;
&lt;li&gt;a local estimate of how visible the change is,&lt;/li&gt;
&lt;li&gt;and the much stronger claim of a repeated-game equilibrium.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The MVP mostly lives in the commitment-analysis layer: if Hero is fixed to a candidate strategy in the supplied tree, what are Villain's exact best responses, and what happens to Hero EV under conservative tie handling?&lt;/p&gt;

&lt;h2&gt;
  
  
  What the MVP can do
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;(This describes the MVP on &lt;code&gt;main&lt;/code&gt; at the time of writing. I'm still changing it, so details may move.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It runs an end-to-end candidate-analysis pipeline on a small abstract game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;build tiny finite two-player game trees with rake,&lt;/li&gt;
&lt;li&gt;evaluate fixed Hero and Villain mixed strategies,&lt;/li&gt;
&lt;li&gt;enumerate exact Villain best responses for small trees,&lt;/li&gt;
&lt;li&gt;report Hero EV under worst- and best-case Villain best-response tie rules,&lt;/li&gt;
&lt;li&gt;generate simple Hero candidates from a baseline,&lt;/li&gt;
&lt;li&gt;filter candidates before comparison,&lt;/li&gt;
&lt;li&gt;compare candidate values against a baseline profile,&lt;/li&gt;
&lt;li&gt;compute &lt;code&gt;T_deadline&lt;/code&gt; and local &lt;code&gt;T_detect&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;render a Markdown summary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In plain terms, the analysis loop is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start from a baseline profile: a fixed Hero strategy and a fixed Villain strategy on the supplied tree. These are action probabilities at each information set — &lt;strong&gt;not hand ranges&lt;/strong&gt;; the tool does not model or import real solver ranges.&lt;/li&gt;
&lt;li&gt;Generate Hero candidates by shifting probability between two actions at a single Hero information set (a blind, systematic enumeration of small shifts — not a search aimed at hurting Villain).&lt;/li&gt;
&lt;li&gt;For each candidate, lock Hero to it and compute Villain's &lt;em&gt;exact&lt;/em&gt; best response. That yields Hero's worst-case EV after Villain adapts.&lt;/li&gt;
&lt;li&gt;Flag a candidate &lt;code&gt;robustly_profitable&lt;/code&gt; only when that post-response worst-case Hero EV is strictly higher than Hero's EV in the baseline profile. The point is not "positive EV" — it is "still better than the one-shot baseline even after the opponent best-responds."&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;T_deadline&lt;/code&gt; / &lt;code&gt;T_detect&lt;/code&gt; then add repeated-game timing on top of the candidates that survive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The main entry point is &lt;code&gt;run_candidate_analysis_pipeline&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scripts/check_mvp.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;nuts_chop_river&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build_nuts_chop_river&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_hero_strategy&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;candidate_library&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;baseline_villain_strategy&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;repeated_poker&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CandidateFilterConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CandidateGenerationConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;run_candidate_analysis_pipeline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_nuts_chop_river&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;baseline_hero&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;default_hero_strategy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;baseline_villain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;baseline_villain_strategy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_candidate_analysis_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;baseline_hero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;baseline_villain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;generation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;CandidateGenerationConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shift_amounts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="n"&gt;horizon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;profit_tolerance&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_selection_l1_distance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;detection_log_likelihood_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;detection_occurrence_probability_per_opportunity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;filtering&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;CandidateFilterConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;max_l1_distance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;min_required_observations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;markdown_summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is a diagnostic report for the model you supplied, not a poker recommendation. Here is an excerpt from the actual output of &lt;code&gt;examples/analysis_pipeline.py&lt;/code&gt; on the nuts-chop river toy game (I've trimmed the Configurations block and some columns; 8 candidates generated, 6 dropped by the filter, 2 compared):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;generated=8 kept=2 excluded=6
compared=2

&lt;span class="gu"&gt;## Candidate Analysis Summary&lt;/span&gt;

&lt;span class="gu"&gt;### Summary Counts&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; total: 2
&lt;span class="p"&gt;-&lt;/span&gt; eligible: 2
&lt;span class="p"&gt;-&lt;/span&gt; excluded: 0
&lt;span class="p"&gt;-&lt;/span&gt; minimum_villain_ev: 1
&lt;span class="p"&gt;-&lt;/span&gt; pareto_frontier: 2

&lt;span class="gu"&gt;### Candidate Rows&lt;/span&gt;

| candidate_id            | fixed_hero_ev | post_response_hero_ev_worst | robustly_profitable | t_detect | exclusion_reasons |
| ----------------------- | ------------- | --------------------------- | ------------------- | -------- | ----------------- |
| H1&lt;span class="se"&gt;\|&lt;/span&gt;check-&amp;gt;bet&lt;span class="se"&gt;\|&lt;/span&gt;shift=0.1 | 0.625         | -0.850                      | no                  | 278      | -                 |
| H1&lt;span class="se"&gt;\|&lt;/span&gt;bet-&amp;gt;check&lt;span class="se"&gt;\|&lt;/span&gt;shift=0.1 | 0.275         | -0.750                      | no                  | 294      | -                 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The baseline Hero EV in this run is +0.45. The column that matters is &lt;code&gt;robustly_profitable&lt;/code&gt;: it is &lt;code&gt;yes&lt;/code&gt; only when &lt;code&gt;post_response_hero_ev_worst&lt;/code&gt; exceeds that baseline. Here both candidates are &lt;code&gt;no&lt;/code&gt; (-0.85 and -0.75 are below +0.45). A candidate that clears the baseline is rare and can exist in constructed cases — the tool's job is to search the candidates and find it when it does. The next section is a hand-built spot where one does.&lt;/p&gt;

&lt;h2&gt;
  
  
  A toy game where the commitment beats the baseline
&lt;/h2&gt;

&lt;p&gt;I needed at least one example where the machinery clearly does what it is meant to: a known spot where committing to a fixed strategy leaves Hero better off than the one-shot baseline, &lt;em&gt;even after&lt;/em&gt; the opponent best-responds. This nuts-chop steal is that example, and I wrote a dedicated test for it (&lt;a href="https://github.com/guriguri215-lang/repeated-poker-analysis/blob/main/tests/test_nuts_chop_steal_commitment.py" rel="noopener noreferrer"&gt;&lt;code&gt;tests/test_nuts_chop_steal_commitment.py&lt;/code&gt;&lt;/a&gt;). Treat it as a check that the tool can detect the effect at all — not as the end goal, and not as a claim about real games. Outside this constructed spot I do not know which situations, if any, are profitable to commit in.&lt;/p&gt;

&lt;p&gt;The spot: a river where the board is already the nuts, so every showdown chops. There is no value betting — the only reason to bet (shove) is fold equity. Rake is below its cap, so a &lt;em&gt;called&lt;/em&gt; pot just bleeds chips to the house. With a small starting pot and a big shove, a single hand looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initial commitment = 1, initial pot = 2, bet = 98, rake = 5%, cap = 4

| Line         | Hero/IP EV | Villain/OOP EV |
|--------------|-----------:|---------------:|
| check-check  |      -0.05 |          -0.05 |
| bet-fold     |      -1.00 |          +1.00 |
| bet-call     |      -2.00 |          -2.00 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In one hand the caller folds: -1.00 (fold) beats -2.00 (call). So the one-shot subgame answer is &lt;strong&gt;OOP bets / IP folds&lt;/strong&gt; — a pure steal, since the board is a chop and there is no value in betting.&lt;/p&gt;

&lt;p&gt;Now lock IP to &lt;em&gt;always call&lt;/em&gt; and ask the tool for OOP's exact best response. The steal's only profit source (fold equity) is gone, a called pot is -2.00 for OOP, so OOP's exact best response flips to &lt;strong&gt;check&lt;/strong&gt; — and check-check is -0.05 for both. The test asserts exactly this: &lt;code&gt;solve_exact_response&lt;/code&gt; returns &lt;code&gt;{"OOP_river": "check"}&lt;/code&gt; once Hero is locked to call.&lt;/p&gt;

&lt;p&gt;And crucially, this clears the baseline: Hero's EV goes from -1.00 (the one-shot steal baseline) to -0.05 after OOP adapts — still negative, but strictly better than the baseline, which is exactly the &lt;code&gt;robustly_profitable&lt;/code&gt; condition. That is the whole point of the project stated in one example: &lt;strong&gt;the one-shot subgame answer (bet/fold) is not the answer under the fixed commitment I wanted to test (check/check).&lt;/strong&gt; The commitment to call removes the opponent's only incentive to bet. (Whether this constitutes a repeated-game &lt;em&gt;equilibrium&lt;/em&gt; is the stronger claim I am deliberately not making — this is a commitment-analysis result, not an equilibrium proof.)&lt;/p&gt;

&lt;p&gt;The tool also puts a number on &lt;em&gt;how long&lt;/em&gt; that commitment stays worth it. With baseline Hero EV = -1.00 (steal), pre-adaptation = -2.00 (locked call while OOP still bets), post-adaptation = -0.05 (OOP has switched to check), &lt;code&gt;T_deadline&lt;/code&gt; comes out as &lt;code&gt;floor(1 + 19N/39)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| N (horizon) | T_deadline |
|------------:|-----------:|
|          10 |          5 |
|          20 |         10 |
|          50 |         25 |
|         100 |         49 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The honest caveat: this is a tiny, hand-built tree, and the EVs are ones I can check by hand — that is exactly why I trust &lt;em&gt;this&lt;/em&gt; result more than anything else in the repo. It is not evidence about real games; it is evidence that the model and the code agree on one constructed example built to validate the effect.&lt;/p&gt;

&lt;p&gt;Verification on my machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;python -m pytest tests/test_nuts_chop_steal_commitment.py -v&lt;/code&gt; → 15 passed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;python -m pytest -q&lt;/code&gt; → 500 passed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;python scripts/check_mvp.py&lt;/code&gt; → passes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git diff --check&lt;/code&gt; → clean&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I worked with AI
&lt;/h2&gt;

&lt;p&gt;I supplied the algorithm and the poker model. Codex wrote the implementation instructions and reviewed the results; Claude Code wrote the code. I checked Codex's prompts and corrected wrong premises, but I did not review the code line by line — I relied on the Codex/Claude review loop and the test suite (currently 500 passing tests).&lt;/p&gt;

&lt;p&gt;Two things from that process are worth recording:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The assistant kept drifting toward the general case. For the commitment analysis I wanted Hero fully fixed and only Villain's exact best response computed, but it repeatedly tried to set up CFR — wasted machinery when Hero is fixed. Stopping it led to a side question I hadn't considered: CFR with one side frozen looks like a fixed-environment learning problem. I'm noting that as a question, not a result.&lt;/li&gt;
&lt;li&gt;Explaining the toy game to the model was harder than explaining it to a person — it over-generalized and assumed things that don't apply (e.g. Villain value-bets on a board that is already the nuts). I ended up brushing each spec up in a chat first, then handing the cleaned version to the coding agent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A note on terms the code keeps separate: &lt;code&gt;T_deadline&lt;/code&gt; is economic (how late Villain can adapt while the locked policy still beats the baseline); &lt;code&gt;T_detect&lt;/code&gt; is visibility (how many local observations before the candidate's action distribution looks distinguishable from baseline). They are different questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best-response ties matter.&lt;/strong&gt; If Villain has several best responses with identical Villain EV, Hero's EV can still differ across them. Returning one arbitrary response would hide that risk, so the MVP reports both &lt;code&gt;ev_h_worst&lt;/code&gt; and &lt;code&gt;ev_h_best&lt;/code&gt; across the tie set. (Verified: &lt;code&gt;BestResponseResult&lt;/code&gt; exposes both and the action variation across optimal pure strategies.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small examples are not a weakness.&lt;/strong&gt; The nuts-chop river benchmark is tiny on purpose: easier to hand-check, harder to mistake for a real-money recommendation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current limitations
&lt;/h2&gt;

&lt;p&gt;The main one: &lt;strong&gt;the code has not had an independent human code review.&lt;/strong&gt; Tests pass, but I haven't read the implementation line by line and nobody else has either. Rather than rely on reading the code, I plan to validate it from the outside — design the verification to be as exhaustive as I can make it, run simulations across many configurations, and check that the results hold up. Whether static or property-based checking can give that coverage is something I'm still working out.&lt;/p&gt;

&lt;p&gt;The narrower limits: it is not a full solver, does not import real solver ranges yet, does not solve large no-limit games, and does not do STT / ICM / preflop push-fold yet. The exact response engine enumerates Villain pure strategies, so it is meant for small abstract trees only — there is an explicit &lt;code&gt;max_pure_strategies&lt;/code&gt; ceiling, default 100,000. Candidate generation is simple: finite shifts from a baseline, not a continuous strategy space.&lt;/p&gt;

&lt;p&gt;Most importantly: positive EV &lt;em&gt;inside this model&lt;/em&gt; does not guarantee profitable play. The model can be wrong if the abstraction, action tree, rake rule, ranges, or adaptation assumptions are wrong.&lt;/p&gt;

&lt;p&gt;This is not gambling, bankroll, financial, or legal advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;The toy game confirmed the effect, so next I want to extend the tool: analyze with hand ranges rather than abstract action probabilities, and model the opponent adapting gradually (e.g. a Bayesian update of their response over repetitions) instead of switching to an exact best response in one step. Alongside that, I want to firm up the outside-in verification described above before trusting results on new spots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/guriguri215-lang/repeated-poker-analysis" rel="noopener noreferrer"&gt;guriguri215-lang/repeated-poker-analysis&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MVP walkthrough: &lt;a href="https://github.com/guriguri215-lang/repeated-poker-analysis/blob/main/docs/mvp_walkthrough.md" rel="noopener noreferrer"&gt;docs/mvp_walkthrough.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Assumptions and limitations: &lt;a href="https://github.com/guriguri215-lang/repeated-poker-analysis/blob/main/docs/assumptions_and_limitations.md" rel="noopener noreferrer"&gt;docs/assumptions_and_limitations.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Publication policy: &lt;a href="https://github.com/guriguri215-lang/repeated-poker-analysis/blob/main/docs/publication_policy.md" rel="noopener noreferrer"&gt;docs/publication_policy.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I used AI assistance throughout this project and to draft this article. The division of labor was deliberate: I supplied the algorithm and the poker model, Codex handled instructions and review, and Claude Code wrote the code; I checked the prompts and relied on automated review and tests for the implementation. This article was also drafted with AI help and then rewritten to reflect my own decisions, mistakes, and open questions. Technical claims are marked where I have verified them against the code myself; where I say something is provisional or unreviewed, that is literally true.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
