<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tech on Basil Labib</title><link>https://basillabib.com/series/tech/</link><description>Recent content in Tech on Basil Labib</description><generator>Hugo</generator><language>en-us</language><managingEditor>gs454236@gmail.com (Basil Labib)</managingEditor><webMaster>gs454236@gmail.com (Basil Labib)</webMaster><copyright>© 2020–2026 Basil Labib</copyright><lastBuildDate>Sun, 11 Oct 2026 18:23:49 +0530</lastBuildDate><atom:link href="https://basillabib.com/series/tech/index.xml" rel="self" type="application/rss+xml"/><item><title>Can discrete Ricci flow signal capability transition in Transformers?</title><link>https://basillabib.com/post/can-discrete-ricci-flow-signal-capability-transition-in-transformers/</link><pubDate>Sun, 11 Oct 2026 18:23:49 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/can-discrete-ricci-flow-signal-capability-transition-in-transformers/</guid><description>&lt;h1 id="abstract"&gt;Abstract&lt;/h1&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-1a92097c" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-1a92097c" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/2.png" alt="Headline result. A: the layer coefficient through grokking, per transition (thin) and mean (thick), against test accuracy. B: the same coefficient for a grokking run and both training controls. C: the calibrated excursion for every run in the study, grouped." loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-1a92097c" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/2.png" alt="Headline result. A: the layer coefficient through grokking, per transition (thin) and mean (thick), against test accuracy. B: the same coefficient for a grokking run and both training controls. C: the calibrated excursion for every run in the study, grouped." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Headline result. A: the layer coefficient through grokking, per transition (thin) and mean (thick), against test accuracy. B: the same coefficient for a grokking run and both training controls. C: the calibrated excursion for every run in the study, grouped.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;&lt;span class="dropcap"&gt;&lt;span class="dropcap-initial"&gt;R&lt;/span&gt;esearchers&lt;/span&gt; have shown that the feature geometry of a feedforward neural network evolves as discrete &lt;a href="https://differentialgeometri.wordpress.com/2022/01/13/an-illustrated-introduction-to-the-ricci-flow-third-version/" target="_blank" rel="noopener noreferrer"&gt;Ricci flow&lt;/a&gt;&#10; where a node&amp;rsquo;s curvature anti-correlates with the expansion of its neighbourhood across layers (Hehl et. al. 2025). Apart from establishing a connection between machine learning training dynamics and loss landscapes with differential geometry, this result also presents new heuristical methods for early stopping and efficient model architecture selection informed by geometric feature evolution.&lt;/p&gt;&#10;&lt;p&gt;In this blog post, I asked the question: does the same geometric evolution results hold when the techniques are applied to a decoder-only transformer architecture (Vaswani et. al. 2017)? Based on my limited resources and time, I was able to achieve partial success where I show that indeed some aspects of the feature manifold evolve as Ricci flow, whereas some do not. Application of the techniques as presented in the paper presented non-trivial difficulties when applied to the transformer. This post elaborates how I overcome those issues, presents the results with some plausible explanation and notes the limitations and future work.&lt;/p&gt;&#10;&lt;h1 id="introduction"&gt;Introduction&lt;/h1&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-4904cca1" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-4904cca1" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/0.png" alt="Stylised representation of the evolution of the data manifold and the corresponding discrete graphs. Source: Hehl et. al. 2025" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-4904cca1" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/0.png" alt="Stylised representation of the evolution of the data manifold and the corresponding discrete graphs. Source: Hehl et. al. 2025" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Stylised representation of the evolution of the data manifold and the corresponding discrete graphs. Source: Hehl et. al. 2025&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;A neural network transforms the input data manifold through complex non-linear transformations. This sequence of transformation may be viewed as an evolution or a &lt;em&gt;flow&lt;/em&gt; on the geometry of the data point cloud. Hehl et. al. studied this phenomenon and showed that for feedforward network trained on both real and synethetic datasets on the task of binary classification exhibit manifold transformation consistent with discrete Ricci flow where the surface smoothens into highly curvatured surfaces corresponding to the specific class.&lt;/p&gt;&#10;&lt;p&gt;Since the manifold itself is difficult to visualise for a dataset, the authors built discrete graphs at each layer using techniques such as k-nearest neighbour and r-distance graphs. Moreover, they assigned each node a discrete scalar curvature and studied how the curvature changed with respect to the neighbourhood across the layers.&lt;/p&gt;&#10;&lt;p&gt;We asked if this phenomenon transfers to transformers also? And if yes, can the geometry move actually signal a capability transition? We used grokking on modular arithmetic as the motivating problem since it is fairly well-understood and is not resource-intensive to learn. Grokking refers to the observation of sharp decline in network loss, representing moments when the network &amp;ldquo;learns&amp;rdquo; or achieves &amp;ldquo;epiphany&amp;rdquo; in task comprehension and performance.&lt;/p&gt;&#10;&lt;p&gt;The transfer from a simple feedforward neural network training for binary classification to auto-regressive transformer blocks was not straightforward. Some of the difficulties faced were:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Simple MLP are represented as complete transformation of the form, &lt;span class="has-mathjax"&gt; \( x \to g(x) \) &lt;/span&gt;&#10;. Transformers actually &amp;ldquo;modify&amp;rdquo; the residual stream instead of transforming it. This is better represented as &lt;span class="has-mathjax"&gt; \( y \to y + h(y) \) &lt;/span&gt;&#10; which means that consecutive layers carry non-negligible feature remnants from past streams.&lt;/li&gt;&#10;&lt;li&gt;Transformer sublayers alternate in kinds, namely attention and MLP blocks &lt;span class="has-mathjax"&gt; $y = \phi_1 \circ \phi_2 \circ \cdots (x)$ &lt;/span&gt;&#10; rather than simple chained MLPs &lt;span class="has-mathjax"&gt;$y = \phi \circ \phi \circ \cdots (x) $&lt;/span&gt;&#10;.&lt;/li&gt;&#10;&lt;li&gt;The representations are per-token across the context length. Therefore, interpreting the idea of a scalar curvature at layer $l$ only makes sense if we fix a read-out position along the context.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="key-results"&gt;Key results&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;We found that the layer Ricci coefficient indeed tracks the transition across layers.&lt;/li&gt;&#10;&lt;li&gt;The local Ricci coefficient measured at a node through training stays around zero in transformers at every depth.&lt;/li&gt;&#10;&lt;li&gt;Some part of the above discrepancy can be explained by the presence of residual connections in transformer blocks. For this, we observed local $\rho$ differ by +0.13 to +0.21 covering about two thirds of the feedforward-to-transformer gap. This suggests that comparing architectures that differ in skip connections may not be a good idea.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h1 id="preliminary-background"&gt;Preliminary background&lt;/h1&gt;&#10;&lt;h2 id="discrete-curvature-on-a-feature-graph"&gt;Discrete curvature on a feature graph&lt;/h2&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-5ca68753" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-5ca68753" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/1.png" alt="A curve with both large and small curvatures. Source: Gab Khan" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-5ca68753" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/1.png" alt="A curve with both large and small curvatures. Source: Gab Khan" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;A curve with both large and small curvatures. Source: Gab Khan&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;As mentioned before, we pick a subset of activation at each layer &lt;span class="has-mathjax"&gt; \( X_l = \{ \textbf{x}_i \}_{i=1}^n \) &lt;/span&gt;&#10; and build an undirected graph $G_k(X)$ based on the k-nearest neighbour approach. There are many metrics mentioned in Hehl et. al. to formalise the idea of curvature (See Ollivier-Ricci and Forman-Ricci curvatures). We present the augmented Forman-Ricci curvature of an edge which is computationally more efficient to compute:&lt;/p&gt;&#10;&lt;p&gt;$$&#10;AF_l(u,v) = 4 = deg_l(u) - deg_l(v) + 3 | N_l(u) \cap N_l(v)|&#10;$$&lt;/p&gt;&#10;&lt;p&gt;where $N(u)$ represents the set of neighbour nodes of $u$ and the scalar curvature of the node $\kappa$ is just the mean of curvature of its incident edges.&lt;/p&gt;&#10;&lt;p&gt;Across layers, as the same neighbourhood of a node expands, it would mean that the nodes are more spread apart. This can be formalised as an average of the differential shortest path distance between two nodes across the two graphs at layers $l+1$ and layer $l$.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$&#10;\eta_l(x) = \frac{1}{deg_l(x)} \Sigma_{y \sim x_l} [d_{l+1}(x, y) - d_{l} (x, y)] = \frac{1}{deg_l(x)} \Sigma_{y \sim x_l} d_{l+1}(x,y) - 1&#10;$$&#10;&lt;/div&gt;&#10;&#10;since y in same layer as x implies $d_l(x,y) = 1$. A node&amp;rsquo;s $\eta$ is large when its layer $l$ neighbours are pushed far apart in layer $l+1$.&lt;/p&gt;&#10;&lt;h2 id="the-local-and-layer-ricci-coefficients"&gt;The local and layer Ricci coefficients&lt;/h2&gt;&#10;&lt;p&gt;Ricci flow contracts positively curved regions and expands negatively curved regions so there is an anti-correlation between $\eta$ and $\kappa$. This can either be observed as a Pearson coefficient between $\kappa$ and $\eta$ at each layer $l$, namely $\rho_{layer}(l)$ or at a specific node across $T$ depth transitions, namely $\rho_{local}(x)$.&lt;/p&gt;&#10;&lt;p&gt;For feedforward networks, the authors found that $\rho_{local} \in [-0.40, -0.76]$ and $\approx 0$ for untrained networks.&lt;/p&gt;&#10;&lt;h2 id="grokking-to-represent-capability-transition"&gt;Grokking to represent capability transition&lt;/h2&gt;&#10;&lt;p&gt;We study the problem of modular addition $c = (a+b) \mod p$ where a small transformer is trained with training set fraction pairs over few hundred steps. &lt;a href="https://www.neelnanda.io/grokking-paper" target="_blank" rel="noopener noreferrer"&gt;Nanda et. al.&lt;/a&gt;&#10; showed that the network abruptly generalizes and that the underlying mechanism for grokking involves principled circuits that can be cleanly interpreted as algorithmic steps. This problem is suitable for studying capability transition due to its fairly well-understood nature.&lt;/p&gt;&#10;&lt;h2 id="where-to-read-a-transformers-layers"&gt;Where to read a transformer&amp;rsquo;s &amp;ldquo;layers&amp;rdquo;?&lt;/h2&gt;&#10;&lt;p&gt;Unlike a MLP, the transformer&amp;rsquo;s output is a residual stream per-token. Our sequence is &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt; We read the prediction at the final position and log the residual stream at each sublayer boundary, namely&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;resid_pre_0, resid_mid_0, resid_post_0, resid_pre_1, ..., resid_post_L-1 &lt;/code&gt;&lt;/p&gt;&#10;&lt;h1 id="experimental-setup"&gt;Experimental setup&lt;/h1&gt;&#10;&lt;p&gt;&lt;strong&gt;Task&lt;/strong&gt;: We used modular addition $c = a + b \mod p$, the input sequence was $a, b, =$, and the final position was read as model output.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Model&lt;/strong&gt;: We used a decoder-only transformer, 4 heads, $d_{head} = 32$, $d_{mlp} = 512$, no Layer Norm, full batch AdamW, $lr = 10^{-3}$, weight decay 1.0, $\beta = (0.9, 0.98)$, 30,000 steps. $d_{model} \in (64, 128, 256) at L=4$. Total parameter counts were 0.41M / 0.82M / 1.64M. $L = (1, 2, 4, 8)$. We used various seed configurations across runs and swept accordingly.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Hardware&lt;/strong&gt;: All experiments on a M4 Max (36 GB) using MPS.&lt;/p&gt;&#10;&lt;h1 id="results"&gt;Results&lt;/h1&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-4a3ad8ab" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-4a3ad8ab" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/2.png" alt="A: the layer coefficient through grokking, per transition (thin) and mean (thick), against test accuracy. B: the same coefficient for a grokking run and both training controls. C: the calibrated excursion for every run in the study, grouped." loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-4a3ad8ab" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/2.png" alt="A: the layer coefficient through grokking, per transition (thin) and mean (thick), against test accuracy. B: the same coefficient for a grokking run and both training controls. C: the calibrated excursion for every run in the study, grouped." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;A: the layer coefficient through grokking, per transition (thin) and mean (thick), against test accuracy. B: the same coefficient for a grokking run and both training controls. C: the calibrated excursion for every run in the study, grouped.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;!--&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-079e488f" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-079e488f" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/3.png" alt="" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-079e488f" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/3.png" alt="" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10;&lt;/figure&gt;&#10;--&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-1119f0cb" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-1119f0cb" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/5.png" alt="Curvature against mechanistic progress measures computed from the same run&amp;rsquo;s weights. A: the three phases read off the weights - accuracy, the embedding&amp;rsquo;s Fourier concentration on the five key frequencies, and the weight norm. B: the layer coefficient (faint: raw; bold: 7-checkpoint rolling median) with the four events marked. Most of the drop precedes saturation; the coefficient then deepens further through the cleanup window and bottoms out just after it." loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-1119f0cb" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/5.png" alt="Curvature against mechanistic progress measures computed from the same run&amp;rsquo;s weights. A: the three phases read off the weights - accuracy, the embedding&amp;rsquo;s Fourier concentration on the five key frequencies, and the weight norm. B: the layer coefficient (faint: raw; bold: 7-checkpoint rolling median) with the four events marked. Most of the drop precedes saturation; the coefficient then deepens further through the cleanup window and bottoms out just after it." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Curvature against mechanistic progress measures computed from the same run&amp;rsquo;s weights. A: the three phases read off the weights - accuracy, the embedding&amp;rsquo;s Fourier concentration on the five key frequencies, and the weight norm. B: the layer coefficient (faint: raw; bold: 7-checkpoint rolling median) with the four events marked. Most of the drop precedes saturation; the coefficient then deepens further through the cleanup window and bottoms out just after it.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;One of the surprisings results is the above figure which shows that the mean layer coefficient would lead the rise in test accuracy but it actually lags behind by around 900 steps. We found that about half of the geometric reorganization happens when test accuracy has fairly stabilized. Across 15 runs, a median of 50% of the coefficient changes after 99% test accuracy is reached.&lt;/p&gt;&#10;&lt;!--&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-ae6b3ca0" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-ae6b3ca0" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/6.png" alt="" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-ae6b3ca0" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/6.png" alt="" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10;&lt;/figure&gt;&#10;--&gt;&#10;&lt;h2 id="coefficients-should-be-interpreted-differentially"&gt;Coefficients should be interpreted differentially&lt;/h2&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-d33fee1c" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-d33fee1c" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/4.png" alt="A: the local coefficient post-saturation for every well-conditioned configuration, with leave-one-transition-out ranges; the grey band is the range reported for trained feedforward networks. B: matched plain versus residual MLPs at initialization." loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-d33fee1c" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/ricci-flow/4.png" alt="A: the local coefficient post-saturation for every well-conditioned configuration, with leave-one-transition-out ranges; the grey band is the range reported for trained feedforward networks. B: matched plain versus residual MLPs at initialization." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;A: the local coefficient post-saturation for every well-conditioned configuration, with leave-one-transition-out ranges; the grey band is the range reported for trained feedforward networks. B: matched plain versus residual MLPs at initialization.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Hehl et. al. reported the local coefficient as near zero for untrained networks. However, this does not hold for our setting and therefore, the numbers above should be interpreted differntially rather than on absolute terms.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Configuration&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Architecture&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;span class="has-mathjax"&gt; \( \rho_{local} \) at init &lt;/span&gt;&#10;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;span class="has-mathjax"&gt; \( \rho_{layer} \) at init &lt;/span&gt;&#10;&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;transformer, $L=4$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;transformer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.158$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.322$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;transformer, $L = 8$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;transformer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$ -0.232$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.358$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;transformer, $L=4, d=64$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;transformer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.255$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.355$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MLP, $L=4$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MLP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.435$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.224$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MLP, $L=8$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;MLP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.637$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.312$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="effects-of-residual-connections"&gt;Effects of residual connections&lt;/h2&gt;&#10;&lt;p&gt;Why is there a feedforward-to-transformer gap in the first place? Can we explain it? The obvious differing mechanism is the residual stream so we constructed similar MLPs with identical depth, width, init scheme, and only differing whether the block transforms input as $h \leftarrow \sigma(Wh)$ or as $h \leftarrow h + \sigma(Wh)$.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Depth&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Transitions&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;span class="has-mathjax"&gt; \( \rho_{local} \) plain &lt;/span&gt;&#10;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Residual&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Shift&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;span class="has-mathjax"&gt; \( \rho_{layer} \) plain &lt;/span&gt;&#10;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Residual&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Shift&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.450 \pm 0.055$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.318 \pm 0.031$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$\mathbf{+0.133}$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.293 \pm 0.042$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.226 \pm 0.018$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$+0.067$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.588 \pm 0.034$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.361 \pm 0.030$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$\mathbf{+0.226}$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.343 \pm 0.031$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.237 \pm 0.016$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$+0.106$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;16&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;15&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.638 \pm 0.018$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.430 \pm 0.026$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$\mathbf{+0.208}$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.387 \pm 0.037$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-0.296 \pm 0.007$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$+0.091$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Residual connections account for 0.227 of the 0.356 gap (about two thirds). The unexplained part may be transformer specific like attention mixing, single position read-out, no LayerNorm.&lt;/p&gt;&#10;&lt;p&gt;The local coefficient represents an absolute level change across transitions at a particular node which is already contaminated at init time due to architectural differences so as expected, it does not transfer. However, the layer coefficient is a quantiy measuring change between layers so the constant init offset cancels. This suggests that architectures differing in skip connections may not make sense to be compared.&lt;/p&gt;&#10;&lt;h1 id="discussion-and-limitations"&gt;Discussion and Limitations&lt;/h1&gt;&#10;&lt;p&gt;As discussed, $\rho_{local}$ correlates the curvature change around a fixed datapoint across depth. The additive residual stream and skip connections results in a negative result for this metric. The residual MLPs that we train separately confirms that about two-thirds of the unexplained difference can be explained by a constant offset at init time and suggests that measuring absolute local $\rho$ values across incomparable architectures is not a good idea.&lt;/p&gt;&#10;&lt;p&gt;Due to how we defined $\rho_{layer}$ as a change between layers, it remains immune to constant offset at init time. Geometric statistics measuring cross-data behaviour can survive residual connections but not cross-depth behaviours.&lt;/p&gt;&#10;&lt;p&gt;Even the layer coefficient results are tepid as they lag behind grokking phase for our task. Obviously, this is just one task on a very small model, future work should explore expanding this scope to more architectures, bigger model sizes, and run more ablation studies.&lt;/p&gt;&#10;&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;&#10;&lt;p&gt;We started out wondering if the feature geometry evolution dynamics transfer to transformers as cleanly as they exhibit in simple MLPs as demonstrated by our motivating paper. The final results are a mix of some positives and some negatives with our main motivation of developing feature geometry based training heuristics renders null as we find that there is a lag in geometry evolution and generalization, conditional on the one task we study in this post.&lt;/p&gt;&#10;&lt;p&gt;Moreover, the original framing of an across-depth metric does not make sense for an architecture with skip connections and an additive residual stream. This should motivate more work towards developing alternative metrics to study the feature geometry evolution dynamics in transformers and other sister architectures.&lt;/p&gt;&#10;&lt;h1 id="ai-usage-disclosure"&gt;AI usage disclosure&lt;/h1&gt;&#10;&lt;p&gt;This post is entirely written by me. All experiments were designed, implemented and ran by Claude Code. The figures were generated by Claude. All experiments ran under my oversight.&lt;/p&gt;&#10;&lt;h1 id="how-to-cite-this-work"&gt;How to cite this work&lt;/h1&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Labib, Basil, 2026 October. Can discrete Ricci flow signal capability transition in transformers? Basil Labib&amp;#39;s blog. https://basil08.github.io/post/can-discrete-ricci-flow-signal-capability-transition-in-transformers/&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="references"&gt;References&lt;/h1&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Hehl, Moritz, Max von Renesse, and Melanie Weber. &amp;ldquo;Neural feature geometry evolves as discrete Ricci flow.&amp;rdquo; arXiv preprint arXiv:2509.22362 (2025).&lt;/li&gt;&#10;&lt;li&gt;Nanda, Neel, et al. &amp;ldquo;Progress measures for grokking via mechanistic interpretability.&amp;rdquo; arXiv preprint arXiv:2301.05217 (2023).&lt;/li&gt;&#10;&lt;li&gt;Gabriel Khan, &amp;ldquo;AN Illustrated Introduction to the Ricci Flow, Third Version&amp;rdquo; &lt;a href="https://differentialgeometri.wordpress.com/2022/01/13/an-illustrated-introduction-to-the-ricci-flow-third-version/" target="_blank" rel="noopener noreferrer"&gt;https://differentialgeometri.wordpress.com/2022/01/13/an-illustrated-introduction-to-the-ricci-flow-third-version/&lt;/a&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Understanding System One Models Using Interpretability Tools</title><link>https://basillabib.com/post/understanding-system-one-models-using-interpretability-tools/</link><pubDate>Sun, 04 Oct 2026 21:44:41 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/understanding-system-one-models-using-interpretability-tools/</guid><description>&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: This post is for educational and purely research purposes. I acknowledge that some of the results may be used to construct adversarial attacks on System One models. However, I believe that the builders of these models are already aware of these attacks and a patch or defense would appear sooner than any meaningful attack may occur. If that is not the case, then this post generates evidence towards prioritising that area of defense research building more inclusive guardrails.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;&lt;span class="dropcap"&gt;&lt;span class="dropcap-initial"&gt;I&lt;/span&gt;n this post,&lt;/span&gt; I study non-autoregressive decision models or so-called System One models which are specially designed architectures for cheap, calibrated decision-making, intended as control modules embedded inside larger agentic systems. This is an exploratory study conducted over the weekend applying all forms of interpretability tools to better understand Sys1 model behaviour and to suggest design choices for more efficient or safer deployment practices.&lt;/p&gt;&#10;&lt;p&gt;Please reach out to me at gs454236 [AT] gmail [DOT] [COM] for constructive criticisms, questions, or thoughts.&lt;/p&gt;&#10;&lt;h1 id="what-are-system-one-models-and-why-should-you-care"&gt;What are System One Models and Why Should You Care?&lt;/h1&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-00f45d91" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-00f45d91" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/23.png" alt="System one model architecture block diagram. Credits: Unzip.dev" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-00f45d91" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/23.png" alt="System one model architecture block diagram. Credits: Unzip.dev" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;System one model architecture block diagram. Credits: Unzip.dev&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;System One models, named so after Daniel Kahnemann&amp;rsquo;s popularization of the phrase as a subconscious thinking module within the brain as opposed to the more verbalized System two component, are a class of non-autoregressive decision-making language models that output only a calibrated probability distribution over a predefined set of choices given a text input formatted as a predefined schema. The appeal of such models stem from their ability to give sub-second decision making them an order of magnitude faster than conventional language models as well as cheaper (since you can bypass the whole test-time inference cost). These models came into the spotlight with &lt;a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev" target="_blank" rel="noopener noreferrer"&gt;TypeSafe AI&amp;rsquo;s announcement of Jev-1.13&lt;/a&gt;&#10;, however, the claims are &lt;a href="https://mer.vin/news/laya-the-33ms-open-source-decision-model-beating-jev/" target="_blank" rel="noopener noreferrer"&gt;disputed as an Indian engineer claims&lt;/a&gt;&#10; to have built the architecture an year earlier already.&lt;/p&gt;&#10;&lt;p&gt;With recent studies on using Jev for LLM-as-judge, an application area notoriously difficult for language models due to uncalibrated decision across batches and prohibitive latency to be useful for current long-running agentic tasks, these models still remain underexplored as a new model architecture.&lt;/p&gt;&#10;&lt;p&gt;To the best of my knowledge, this is the first attempt to apply mechanistic interpretability tools developed by researchers across the years and try to pry open the brains of Sys1 models in order to understand them better and in the process, to make them better and safer too.&lt;/p&gt;&#10;&lt;h1 id="setup"&gt;Setup&lt;/h1&gt;&#10;&lt;p&gt;In order to facilitate whitebox interpretability, we&lt;sup&gt;1&lt;/sup&gt; selected Jev-9B, an open student model of Jev-1.13. It is a frozen Qwen3.5-9B model with a LoRA adapter trained on Jev&amp;rsquo;s probability outputs. Using this architecture allowed us to compare base Qwen with Jev student with just disabling the adapter. we also used Tev1-4B, Tev1-0.8B, both open models from Together AI. We prioritized open models of varying sizes to have a variant study as well as I wanted to see applicability of interpretability tools on these models and identify blind spots. Some models to explore are CLM-8B, Clef, as well as Jev-1.13 itself.&lt;/p&gt;&#10;&lt;p&gt;All the experiments were written and run by Claude Code. I supplied the research questions, provided human oversight as well as guided key design decisions. This blog post is written by me, entirely. The diagrams are generated by Claude. The full experiment took over 12 hours on my Mac M4 Max. I did not rent any cloud GPU.&lt;/p&gt;&#10;&lt;h1 id="methodology"&gt;Methodology&lt;/h1&gt;&#10;&lt;p&gt;As noted above, we mainly focused on open student models of 0.8B, 4B, and 9B sizes as well as their Qwen base models to serve as control. We constructed four datasets, some from Jev&amp;rsquo;s training corpus and held-out families as well as some constructed scenarios across various epistemic modalities (ambiguous, missing evidence, etc.), an injection dataset constituting prompt-based attacks as well as pairs with one number change in the prompt state.&lt;/p&gt;&#10;&lt;p&gt;We employed pretty much all the tools out there under the sun as the goal was also to see what worked and what didn&amp;rsquo;t on these seemingly new class of model organisms. I trained linear probes, decision lens, used activation patching, SAEs, crosscoders, and MAttr to quantify model internals, beliefs versus the model output. I used the model&amp;rsquo;s output confidence as baseline whereever applicable and good &amp;lsquo;ol bag-of-words classifier on raw text.&lt;/p&gt;&#10;&lt;p&gt;In particular, I was interested in the following questions (findings and discussions follow in their respective sections):&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;At which layer does the decision form? Can we use a logit lens approach to find formative layers?&lt;/li&gt;&#10;&lt;li&gt;Does the distillation impart new knowledge to student models? What does distillation change exactly?&lt;/li&gt;&#10;&lt;li&gt;Is the model internally consistent with its output when it comes to reporting errors?&lt;/li&gt;&#10;&lt;li&gt;What are the ASR for common prompt injection techniques on Sys1 models?&lt;/li&gt;&#10;&lt;li&gt;Do the model modalities share a common intensity direction? Can it be steered?&lt;/li&gt;&#10;&lt;li&gt;Are System One models really easier to interpret?&lt;/li&gt;&#10;&lt;li&gt;Can we exploit model internals to build efficient guardrails?&lt;/li&gt;&#10;&lt;li&gt;Does the model force a correct answer when there is none?&lt;/li&gt;&#10;&lt;li&gt;Are System One Models option shuffle invariant?&lt;/li&gt;&#10;&lt;li&gt;How small can a System One adapter be?&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h1 id="when-does-decision-form"&gt;When does decision form?&lt;/h1&gt;&#10;&lt;p&gt;We applied a decision lens (similar to a logit lens) to all 32 layers of the Qwen base model as well as activation patching to find the most causative layer when the decision is flipped.&lt;/p&gt;&#10;&lt;p&gt;We found that across the three model decision primitives (choice, score, noul) Jev-9B consistently formed the final answer post layer 24. Across 40 pairs, we found all big models fix the decision gradually over the latter layers &amp;ndash; calculated as the median fraction of answer change by patching each layer.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-efad694d" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-efad694d" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/0.png" alt="Decision lens on Jev-9B" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-efad694d" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/0.png" alt="Decision lens on Jev-9B" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Decision lens on Jev-9B&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-f73c4195" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-f73c4195" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/1.png" alt="Activation patching to find the depth at which decision is formed" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-f73c4195" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/1.png" alt="Activation patching to find the depth at which decision is formed" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Activation patching to find the depth at which decision is formed&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h1 id="does-distillation-impart-new-knowledge"&gt;Does distillation impart new knowledge?&lt;/h1&gt;&#10;&lt;p&gt;As we focused on distilled models on open base models, I was curious to understand if distilling added new knowledge to the base model or if it was merely learning a transformed output format. Current evidence points to the latter as we trained linear probes across all layers.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-224e6d95" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-224e6d95" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/2.png" alt="Schema output similarity between Jev and Jev-9B" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-224e6d95" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/2.png" alt="Schema output similarity between Jev and Jev-9B" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Schema output similarity between Jev and Jev-9B&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-7acd60da" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-7acd60da" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/3.png" alt="AUROC curve; linear probe on Qwen base" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-7acd60da" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/3.png" alt="AUROC curve; linear probe on Qwen base" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;AUROC curve; linear probe on Qwen base&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;We also performed linear CKA across layers to study how the hidden states differ. We used Matryoskha Attribution or MAttr to find adapter components that restore most of the model behaviour. MAttr learns weight significance by changing weights from base to trained under a random budget.&lt;/p&gt;&#10;&lt;p&gt;For Jev-9B, the adapter changes about 3% of the weight norm but rotates the final layer complete (0.23). Tev class models differ in the middle but the final layers are almost identical to initial layers.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-da1bcbc1" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-da1bcbc1" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/4.png" alt="linear CKA across layers; Jev-9B, Tev1 0.8B and 4B" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-da1bcbc1" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/4.png" alt="linear CKA across layers; Jev-9B, Tev1 0.8B and 4B" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;linear CKA across layers; Jev-9B, Tev1 0.8B and 4B&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-5d85af38" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-5d85af38" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/5.png" alt="MAttr, gradient, size change, and random ranking methods versus components restored; Model: Jev-9B" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-5d85af38" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/5.png" alt="MAttr, gradient, size change, and random ranking methods versus components restored; Model: Jev-9B" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;MAttr, gradient, size change, and random ranking methods versus components restored; Model: Jev-9B&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;&lt;strong&gt;Why care anyway?&lt;/strong&gt; I think the biggest takeaway is that adapters can be much smaller (1.6M instead of 40M) making it far easier to train custom adapters on a per-consumer, per-usecase basis. In particular, engineers should prioritize the middle layers. Results on model reformating existing knowledge indicates using a better base model might yield a stronger model.&lt;/p&gt;&#10;&lt;h1 id="can-a-probe-predict-mistakes-better-than-the-models-output"&gt;Can a probe predict mistakes better than the model&amp;rsquo;s output?&lt;/h1&gt;&#10;&lt;p&gt;I was curious to understand if a probe on the hidden state can predict the model&amp;rsquo;s error better than the model&amp;rsquo;s output probabilities. It turns out that for bigger models, the probabilities are a good proxy and the probes fall short.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-29693add" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-29693add" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/6.png" alt="Probe on model error probability versus model output" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-29693add" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/6.png" alt="Probe on model error probability versus model output" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Probe on model error probability versus model output&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h1 id="how-robust-are-distilled-system-one-models-to-prompt-injection-attacks"&gt;How robust are distilled System one models to prompt injection attacks?&lt;/h1&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-d91093de" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-d91093de" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/7.png" alt="An overview of the injection bench with reference class state object and the five attack types illustrated with an example." loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-d91093de" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/7.png" alt="An overview of the injection bench with reference class state object and the five attack types illustrated with an example." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;An overview of the injection bench with reference class state object and the five attack types illustrated with an example.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-0261c4fa" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-0261c4fa" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/8.png" alt="Attack success rate by style and model" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-0261c4fa" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/8.png" alt="Attack success rate by style and model" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Attack success rate by style and model&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Does distillation make the issue worse?&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-a2f132f5" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-a2f132f5" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/9.png" alt="Attack success rate for Qwen base and Jev-9B" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-a2f132f5" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/9.png" alt="Attack success rate for Qwen base and Jev-9B" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Attack success rate for Qwen base and Jev-9B&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Surprisingly, ablating the hijacking features made the ASR slightly worse than baseline. This is most likely because the SAE has selected for the particular scenario at the decision token and not the underlying mechanism.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-50f6f1f8" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-50f6f1f8" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/10.png" alt="Effects of ablating hijacking features found via SAEs" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-50f6f1f8" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/10.png" alt="Effects of ablating hijacking features found via SAEs" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Effects of ablating hijacking features found via SAEs&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h1 id="do-the-model-modalities-share-a-common-intensity-direction-can-it-be-steered"&gt;Do the model modalities share a common intensity direction? Can it be steered?&lt;/h1&gt;&#10;&lt;p&gt;We noticed that the model has three decision primitives &amp;ndash; score (select a number within a range), choice (select an option from a set), and noul (a binary yes/no probability).&lt;/p&gt;&#10;&lt;p&gt;Is it possible to steer one of these answers by putting pressure on another? In other words, does the model make decisions from some &amp;ldquo;shared context&amp;rdquo;?&lt;/p&gt;&#10;&lt;p&gt;Under the distilled model setup, this seems to be true. Distillation creates a partially shared direction which can influence ratings by steering the &amp;ldquo;yes&amp;rdquo; direction.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-e478b6c4" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-e478b6c4" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/11.png" alt="Cosine similarity between &amp;lsquo;yes&amp;rsquo; and &amp;lsquo;high score&amp;rsquo; directions. Needs more investigation." loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-e478b6c4" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/11.png" alt="Cosine similarity between &amp;lsquo;yes&amp;rsquo; and &amp;lsquo;high score&amp;rsquo; directions. Needs more investigation." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Cosine similarity between &amp;lsquo;yes&amp;rsquo; and &amp;lsquo;high score&amp;rsquo; directions. Needs more investigation.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-c7dd8376" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-c7dd8376" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/12.png" alt="Applying steering pressure to yes direction raises the score by upto 0.27 levels. Random is control." loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-c7dd8376" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/12.png" alt="Applying steering pressure to yes direction raises the score by upto 0.27 levels. Random is control." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Applying steering pressure to yes direction raises the score by upto 0.27 levels. Random is control.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h1 id="are-system-one-models-easier-to-interpret"&gt;Are System One models easier to interpret?&lt;/h1&gt;&#10;&lt;p&gt;One of the appeals of System one models are that they finish their compute in one single pass as opposed to an autoregressive model. Does this single pass nature make them easier to interpret?&lt;/p&gt;&#10;&lt;p&gt;We tried learning an SAE reconstructing the decision state as well as try to find a subcircuit using MAttr. The results are mixed. The trained SAE is indeed smaller but the decision circuit is more dense than the base model.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-2e7fc79c" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-2e7fc79c" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/13.png" alt="Jev-9B&amp;rsquo;s decision state is way more compressible. Y-axis measures reconstruction loss for base and Jev-9B. Trained on 375K tokens, BatchTopK SAE." loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-2e7fc79c" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/13.png" alt="Jev-9B&amp;rsquo;s decision state is way more compressible. Y-axis measures reconstruction loss for base and Jev-9B. Trained on 375K tokens, BatchTopK SAE." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Jev-9B&amp;rsquo;s decision state is way more compressible. Y-axis measures reconstruction loss for base and Jev-9B. Trained on 375K tokens, BatchTopK SAE.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-17f45091" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-17f45091" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/14.png" alt="MAttr components needed to keep the decision for number-flip pairs dataset. Base versus Jev-9B." loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-17f45091" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/14.png" alt="MAttr components needed to keep the decision for number-flip pairs dataset. Base versus Jev-9B." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;MAttr components needed to keep the decision for number-flip pairs dataset. Base versus Jev-9B.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h1 id="can-we-exploit-model-internals-to-build-better-guardrails"&gt;Can we exploit model internals to build better guardrails?&lt;/h1&gt;&#10;&lt;p&gt;Although this sounds like an enticing idea, initial enthusiasm was dissolved when we tested the guardrails on fresh attacks.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-48813975" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-48813975" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/15.png" alt="Just blanking two-layer field tokens seems to drop ASR from 37% to 3.6%" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-48813975" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/15.png" alt="Just blanking two-layer field tokens seems to drop ASR from 37% to 3.6%" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Just blanking two-layer field tokens seems to drop ASR from 37% to 3.6%&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-d6c0dd7e" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-d6c0dd7e" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/16.png" alt="ASR by token position that was wiped clean." loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-d6c0dd7e" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/16.png" alt="ASR by token position that was wiped clean." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;ASR by token position that was wiped clean.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-ad98df8e" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-ad98df8e" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/17.png" alt="ASR on new scenarios. Ouch." loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-ad98df8e" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/17.png" alt="ASR on new scenarios. Ouch." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;ASR on new scenarios. Ouch.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h1 id="does-the-model-force-a-correct-answer-when-there-is-none"&gt;Does the model force a correct answer when there is none?&lt;/h1&gt;&#10;&lt;p&gt;Doing a softmax over the raw logits yield some probability distribution. If we remove the correct answer, does the model shift the mass to another incorrect answer or to &amp;ldquo;Don&amp;rsquo;t know&amp;rdquo;/&amp;ldquo;Right answer is missing&amp;rdquo; option?&lt;/p&gt;&#10;&lt;p&gt;We trained a small probe on the hidden state as well as noted the raw score. Both agree with each other, meaning the raw max-logit may be interpreted as a fitness score to the output.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-a601efc5" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-a601efc5" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/18.png" alt="Score and probabilities for Jev-9B for ablated correct answer" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-a601efc5" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/18.png" alt="Score and probabilities for Jev-9B for ablated correct answer" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Score and probabilities for Jev-9B for ablated correct answer&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h1 id="are-system-one-models-option-shuffle-invariant"&gt;Are System One models option-shuffle invariant?&lt;/h1&gt;&#10;&lt;p&gt;Case 1: Prompt1, choose between Opt1, Opt2, Opt3&#10;Case 2: Prompt1, choose between Opt2, Opt3, Opt1&lt;/p&gt;&#10;&lt;p&gt;Does the model output the correct option in both cases? We found that under some margin tolerance, the model flips the answer under the shuffling transformation. Moreover, we also patched the decision token to find if the decision binds to a particular letter. We found that initially the model tracks the option and then post layer 22 binds to the option letter after the decision has formed.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-98baa5d4" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-98baa5d4" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/19.png" alt="Flip rates per shuffle. Jev-9B" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-98baa5d4" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/19.png" alt="Flip rates per shuffle. Jev-9B" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Flip rates per shuffle. Jev-9B&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-eb2f1924" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-eb2f1924" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/20.png" alt="Patched decision token across layers." loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-eb2f1924" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/20.png" alt="Patched decision token across layers." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Patched decision token across layers.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h1 id="how-small-can-a-system-one-model-adapter-be"&gt;How small can a System One model adapter be?&lt;/h1&gt;&#10;&lt;p&gt;I ask this question in our specific setup since we are using a student model. I think there is already some indication in terms of parameter count but in terms of rank, we trained a dedicated rank-16 and rank-1 MLP adapter. Here are some interesting results for a rank-1 adapter with some controls and the assocaited learning curves.&lt;/p&gt;&#10;&lt;p&gt;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-fee4fe77" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-fee4fe77" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/21.png" alt="Rank-1 MLP adapter on layer 6-14 (0.44M parameters) is about 67% close to Jev-9B. The rank-16 (40M parameters) adapter closes only 63%." loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-fee4fe77" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/21.png" alt="Rank-1 MLP adapter on layer 6-14 (0.44M parameters) is about 67% close to Jev-9B. The rank-16 (40M parameters) adapter closes only 63%." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Rank-1 MLP adapter on layer 6-14 (0.44M parameters) is about 67% close to Jev-9B. The rank-16 (40M parameters) adapter closes only 63%.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-1fd4af81" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-1fd4af81" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/22.png" alt="The KL divergence to Jev-9B on 150 held-out questions as a function of training data size (upto 1600 rows). We only trained on a fraction of what Jev-9B is itself distilled on (608K rows)" loading="lazy" class="zoomable-image" style="max-width:600px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-1fd4af81" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sys1models/22.png" alt="The KL divergence to Jev-9B on 150 held-out questions as a function of training data size (upto 1600 rows). We only trained on a fraction of what Jev-9B is itself distilled on (608K rows)" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;The KL divergence to Jev-9B on 150 held-out questions as a function of training data size (upto 1600 rows). We only trained on a fraction of what Jev-9B is itself distilled on (608K rows)&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;h1 id="limitations-and-future-work"&gt;Limitations and Future Work&lt;/h1&gt;&#10;&lt;p&gt;I think the biggest limitation of the above study is that it is only indicative of potential behaviour and does not quantify associated metrics or behaviour. This is due to the intentional small-scale of the experiments conducted on a tight compute budget and more as an intellectual curiosity project.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;We did not use the NTU, decision-injection-bench, or Check Point datasets. Those were gated, could have made some effort :P&lt;/li&gt;&#10;&lt;li&gt;The training runs were fractional as compared to baselines which means that numbers are only directionally true and needs to be validated.&lt;/li&gt;&#10;&lt;li&gt;We only exclusively focused on open weight models for their appeal to be interpreted. This may or may not be a drawback. Other models to explore are the Jev-1.13 model itself, Stanford&amp;rsquo;s CLM-8B, and Cloudflare&amp;rsquo;s Clef, kev, any2jev, shgao, among others.&lt;/li&gt;&#10;&lt;li&gt;The attacks were synthetic and constructed. Any respectable jailbreaking attempt should cover the standard jailbreaking suites.&lt;/li&gt;&#10;&lt;li&gt;The MAttr code was implemented from paper due to lack of an author reference implementation. This may or may not affect the final results and/or correctness of the results.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;&#10;&lt;p&gt;System one models are definitely going to be around, not on the surface but as a potential driving backbone for many agentic systems that will increasingly continue to dominate most of our lives in the coming days. I think the need to deeply understand these models cannot be understated.&lt;/p&gt;&#10;&lt;p&gt;This post summarizes one of the first attempts to apply tools developed by the mechanistic interpretability community to pry open these new class of models. This is not a conclusive study but should inform researchers and industry practitioners towards some helpful directions to pursue as well as to implement better guardrails and integrate these systems so they are safer and more efficient to use.&lt;/p&gt;&#10;&lt;h1 id="to-cite-this-work"&gt;To cite this work&lt;/h1&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Basil. &amp;#34;Understanding System One Models Using Interpretability Tools.&amp;#34; (October 2026)&#10;*Basil Labib&amp;#39;s Blog*, basil08.github.io, &#10;http://basil08.github.io/post/understanding-system-one-models-using-interpretability-tools/&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="references"&gt;References&lt;/h1&gt;&#10;&lt;p&gt;Please note: This is AI-generated section. Please verify before citing.&lt;/p&gt;&#10;&lt;p&gt;A. Models (models, data, tooling)&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;MAttr: Arora et al., Matryoshka Attribution, arXiv 2609.25518 (Sept_2026).&lt;/li&gt;&#10;&lt;li&gt;Zoo (HF): autotrust/JEV-9B, autotrust/JEV-27B, togethercomputer/Tev1-{0.8B,4B}-experimental (+ togethercomputer/tev1 repo for the prompt format), Contrastive-LM/CLM-v0.1-8B, jaredpalmer/kev-0.5b, huaweifeng/any2jev-qwen3-0.6b, shgao/rsi-jev-v*-qwen3.5-2b, Cloudflare/clef*&lt;/li&gt;&#10;&lt;li&gt;Corpus: SargeDev/jev-distill-corpus-v3 for the 608K distilled Jev-1.13 outputs&lt;/li&gt;&#10;&lt;li&gt;Backbone: Qwen Team, Qwen3 Technical Report, arXiv 2505.09388&lt;/li&gt;&#10;&lt;li&gt;Gated DeltaNet (the GDN layers at L14/L22): Yang, Kautz, Hatamizadeh, Gated Delta Networks, arXiv 2412.06464&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;B. Training recipe&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Hinton et al., Distilling the Knowledge in a Neural Network, 1503.02531&lt;/li&gt;&#10;&lt;li&gt;Hu et al., LoRA, 2106.09685&lt;/li&gt;&#10;&lt;li&gt;Shuttleworth et al., LoRA vs Full Fine-tuning: An Illusion of Equivalence, 2410.21228&lt;/li&gt;&#10;&lt;li&gt;Epstein (1969), A Scoring System for Probability Forecasts of Ranked Categories, J. Appl. Meteor.&lt;/li&gt;&#10;&lt;li&gt;Guo et al., On Calibration of Modern Neural Networks, 1706.04599&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;C. What fine-tuning changes&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Jain et al., Mechanistically Analyzing the Effects of Fine-Tuning on Procedurally Defined Tasks, 2311.12786&lt;/li&gt;&#10;&lt;li&gt;Prakash et al., Fine-Tuning Enhances Existing Mechanisms: Entity Tracking, 2402.14811&lt;/li&gt;&#10;&lt;li&gt;Lee et al., A Mechanistic Understanding of Alignment Algorithms (DPO/toxicity), 2401.01967&lt;/li&gt;&#10;&lt;li&gt;Kornblith et al., Similarity of Neural Network Representations Revisited (CKA), 1905.00414&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;D. Lenses, probes, patching&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;nostalgebraist (2020), interpreting GPT: the logit lens, LessWrong&lt;/li&gt;&#10;&lt;li&gt;Belrose et al., Eliciting Latent Predictions with the Tuned Lens, 2303.08112&lt;/li&gt;&#10;&lt;li&gt;Alain &amp;amp; Bengio, Linear Classifier Probes, 1610.01644&lt;/li&gt;&#10;&lt;li&gt;Hewitt &amp;amp; Liang, Control Tasks for Probes, 1909.03368.&lt;/li&gt;&#10;&lt;li&gt;Belinkov, Probing Classifiers: Promises, Shortcomings, and Advances, 2102.12452&lt;/li&gt;&#10;&lt;li&gt;Meng et al., ROME / causal tracing, 2202.05262&lt;/li&gt;&#10;&lt;li&gt;Zhang &amp;amp; Nanda, Towards Best Practices of Activation Patching, 2309.16042&lt;/li&gt;&#10;&lt;li&gt;Heimersheim &amp;amp; Nanda, How to Use and Interpret Activation Patching, 2404.15255&lt;/li&gt;&#10;&lt;li&gt;Geiger et al., Causal Abstractions of Neural Networks, 2106.02997, plus DAS, 2303.02536&lt;/li&gt;&#10;&lt;li&gt;Schuster et al., CALM, 2207.07061, and Din et al., Jump to Conclusions, 2303.09435&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;E. Uncertainty&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Kadavath et al., Language Models (Mostly) Know What They Know, 2207.05221&lt;/li&gt;&#10;&lt;li&gt;Burns et al., Discovering Latent Knowledge (CCS), 2212.03827&lt;/li&gt;&#10;&lt;li&gt;Azaria &amp;amp; Mitchell, The Internal State of an LLM Knows When It&amp;rsquo;s Lying, 2304.13734&lt;/li&gt;&#10;&lt;li&gt;Marks &amp;amp; Tegmark, The Geometry of Truth, 2310.06824&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;F. Attribution and circuit discovery&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Kramár et al., AtP*, 2403.00745&lt;/li&gt;&#10;&lt;li&gt;Syed et al., Attribution Patching Outperforms ACDC, 2310.10348&lt;/li&gt;&#10;&lt;li&gt;Conmy et al., ACDC, 2304.14997&lt;/li&gt;&#10;&lt;li&gt;Bhaskar et al., Finding Transformer Circuits with Edge Pruning, 2406.16778&lt;/li&gt;&#10;&lt;li&gt;Wang et al., IOI circuit, 2211.00593&lt;/li&gt;&#10;&lt;li&gt;Braun et al., Attribution-based Parameter Decomposition, 2501.14926, and Bushnaq et al., Stochastic Parameter Decomposition, 2506.20790&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;G. Dictionaries (SAEs and crosscoders)&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Bricken et al. (2023), Towards Monosemanticity, transformer-circuits.pub&lt;/li&gt;&#10;&lt;li&gt;Cunningham et al., Sparse Autoencoders Find Highly Interpretable Features, 2309.08600&lt;/li&gt;&#10;&lt;li&gt;Templeton et al. (2024), Scaling Monosemanticity, transformer-circuits.pub&lt;/li&gt;&#10;&lt;li&gt;Gao et al., Scaling and Evaluating Sparse Autoencoders (TopK, downstream-loss eval), 2406.04093&lt;/li&gt;&#10;&lt;li&gt;Bussmann, Leask, Nanda, BatchTopK SAEs, 2412.06410&lt;/li&gt;&#10;&lt;li&gt;Bussmann et al., Matryoshka SAEs, 2503.17547 (name and ordering idea shared with MAttr)&lt;/li&gt;&#10;&lt;li&gt;Lindsey et al. (2024), Sparse Crosscoders for Cross-Layer Features and Model Diffing, transformer-circuits.pub&lt;/li&gt;&#10;&lt;li&gt;Minder et al., Overcoming Sparsity Artifacts in Crosscoders… (latent scaling ν, the L1 artifact), 2504.02922&lt;/li&gt;&#10;&lt;li&gt;Kissane et al. (2024), SAEs (Usually) Transfer Between Base and Chat Models, Alignment Forum&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;H. Steering (RQ3)&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Turner et al., Activation Addition, 2308.10248&lt;/li&gt;&#10;&lt;li&gt;Rimsky et al., Contrastive Activation Addition, 2312.06681&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I. Prompt injection and persuasion&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Greshake et al., Not What You&amp;rsquo;ve Signed Up For, 2302.12173&lt;/li&gt;&#10;&lt;li&gt;Yi et al., BIPIA, 2312.14197&lt;/li&gt;&#10;&lt;li&gt;Chen et al., StruQ, 2402.06363&lt;/li&gt;&#10;&lt;li&gt;Wallace et al., The Instruction Hierarchy, 2404.13208&lt;/li&gt;&#10;&lt;li&gt;Zeng et al., How Johnny Can Persuade LLMs…, 2401.06373&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;J. Multiple-choice and option order (RQ14, S3-D)&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Zheng et al., LLMs Are Not Robust Multiple Choice Selectors, 2309.03882&lt;/li&gt;&#10;&lt;li&gt;Pezeshkpour &amp;amp; Hruschka, LLMs Sensitivity to the Order of Options, 2308.11483&lt;/li&gt;&#10;&lt;li&gt;Wiegreffe et al., Answer, Assemble, Ace, 2407.15018. They also find symbol binding in late layers, which matches your L23 result, so this one is worth citing directly.&lt;/li&gt;&#10;&lt;li&gt;Lieberum et al., Does Circuit Analysis Interpretability Scale? (Chinchilla MC), 2307.09458&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h1 id="footnotes"&gt;Footnotes&lt;/h1&gt;&#10;&lt;p&gt;1: From now onwards, &amp;ldquo;we&amp;rdquo; should refer to me and Claude.&lt;/p&gt;&#10;</description></item><item><title>The Lament of a Young ML Researcher</title><link>https://basillabib.com/post/the-lament-of-a-young-ml-researcher/</link><pubDate>Tue, 08 Sep 2026 00:42:57 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/the-lament-of-a-young-ml-researcher/</guid><description>&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;I wish there was a way to know you&amp;rsquo;re in the good old days, before you&amp;rsquo;ve actually left them.&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;~Andy, The Office, S9E23&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;&lt;span class="dropcap"&gt;&lt;span class="dropcap-initial"&gt;I&lt;/span&gt;n 2026, around&lt;/span&gt; twenty thousand papers were submitted to &lt;a href="https://iclr.cc" target="_blank" rel="noopener noreferrer"&gt;ICLR&lt;/a&gt;&#10;, which is a premier (A-star tier in nerd jargon) conference for machine learning and related research topics. Thousands of researchers submit resarch papers, which are peer-reviewed in a &lt;a href="https://www.exordo.com/blog/double-blind-peer-review" target="_blank" rel="noopener noreferrer"&gt;double-blind syste&lt;/a&gt;&#10; (neither the authors know who the reviewers are nor the reviewers know who the authors are) and form the basis for what direction the larger ML community decides to work on next. The conference attracts both veterans and young researchers teeming with energy to publish the next “big thing”. There are &lt;a href="https://sorav.compiler.ai/cs_venue_ranked_list.html" target="_blank" rel="noopener noreferrer"&gt;thousands&lt;/a&gt;&#10; of such &lt;a href="https://artiwari.profiles.iiti.ac.in/CORE%20Computer%20Science%20Conference%20Rankings.pdf" target="_blank" rel="noopener noreferrer"&gt;conferences&lt;/a&gt;&#10; organized on topics ranging from theory of mind to chaos theory and the next weirdest thing you can think of.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-c2407850" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-c2407850" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/lament/0.png" alt="Figure 1: ICLR submission trends. Source: OpenAccept.org" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-c2407850" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/lament/0.png" alt="Figure 1: ICLR submission trends. Source: OpenAccept.org" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Figure 1: ICLR submission trends. Source: OpenAccept.org&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h1 id="no-bots-wont-do-your-research-for-you"&gt;No bots won&amp;rsquo;t do your research for you&lt;/h1&gt;&#10;&lt;p&gt;In the past few years, ICLR and other venues have seen an exponential surge in submissions for a number of reasons. Most notorious of them are, of course, language models. Combined with coding agents and end-to-end autoresearch bots, the cost to produce a “paper-looking” pseudo research artifact is practically zero. I call them pseudo-research artifacts because most modern agents are still only good as assistants, not autonomous researchers. AI companies are exaggerating agents&amp;rsquo; use for research because that’s what pays their mounting infrastructure capex. &lt;a href="https://x.com/_TobiasLee/status/2038467322751615264" target="_blank" rel="noopener noreferrer"&gt;AI “influencers”&lt;/a&gt;&#10; build &lt;a href="https://github.com/karpathy/autoresearch" target="_blank" rel="noopener noreferrer"&gt;cheap scaffolds&lt;/a&gt;&#10; that claim to &lt;a href="https://www.hpc-ai.com/customer/low-cost-autoresearch" target="_blank" rel="noopener noreferrer"&gt;do autoresearch&lt;/a&gt;&#10; for as low as $25. The sad truth is, it&amp;rsquo;s all bullshit. Having used them extensively on multiple projects, I find agents are token burners because they lack epistemic context; they may sample from vast probability distributions over millions of research texts, but they lack “taste”.&lt;/p&gt;&#10;&lt;p&gt;That doesn’t stop younglings from bombarding fake-ass “research” to venues like ICLR, which is &lt;a href="https://blog.iclr.cc/2026/03/31/a-retrospective-on-the-iclr-2026-review-process/" target="_blank" rel="noopener noreferrer"&gt;straining the ICLR review process&lt;/a&gt;&#10;, leading to suboptimality in the review process and hindering actual research progress. The fact that the coveted “quants” like Jane Street set up stalls and sneakily siphon off the brightest minds from these conferences also adds to the hype and mystery around them.&lt;/p&gt;&#10;&lt;p&gt;Like all things in nature, I believe research quality also follows the normal distribution. This means only about the 98th percentile of research is beyond 2 standard deviations from the average. That’s the tiny minority of consequential, moon-shot, maverick research that advances society. This means that there’s a load of crap. One can produce an “evals” paper and make a new benchmark for as little as $100. I don’t even want to comment on how broken and sorry the state of evals is right now, but with each new paper comes new methodology and introduces new metrics. There’s a benchmark for &lt;a href="https://aug5th.substack.com/p/the-bach-benchmark" target="_blank" rel="noopener noreferrer"&gt;LLMs generating music&lt;/a&gt;&#10; to &lt;a href="https://arxiv.org/html/2605.18430v1" target="_blank" rel="noopener noreferrer"&gt;LLMs manipulating objects in AutoCAD&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;p&gt;How many papers were accepted? Around five thousand. That’s a &lt;a href="https://openaccept.org/c/ai/iclr/" target="_blank" rel="noopener noreferrer"&gt;25% acceptance rate&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;h1 id="cheating-review-with-llms"&gt;Cheating review with LLMs&lt;/h1&gt;&#10;&lt;p&gt;Like all human things, the review process is famously &lt;a href="https://www.reddit.com/r/MachineLearning/comments/1sj76a2/just_did_an_analysis_on_iclr_2025_vs_2026_scores/" target="_blank" rel="noopener noreferrer"&gt;noisy and random&lt;/a&gt;&#10;. At the same time, it is the backbone of credible ML research. I would argue it’s even more important than the act of writing and submitting a paper itself. Peer review separates performative fluff from actual research that pushes the frontier of human knowledge. To read a paper and deeply engage with it is almost like reading the mind of another researcher, which is intimate in a non-sexual way. Using LLMs breaks the sanctity of the process and discounts the hard work and thought put into the paper. When a researcher knows their merit is as likely to be acknowledged as random chance, merit dies.&#10;Hold your horses, young man&lt;/p&gt;&#10;&lt;h1 id="left-hanging"&gt;Left hanging&lt;/h1&gt;&#10;&lt;p&gt;Gone are the days when you could hypothetically sit with a stack of 10 papers over an afternoon coffee and be positively assured you&amp;rsquo;d be intellectually kindled, learn something new, and have your curiosity&amp;rsquo;s appetite whetted.&lt;/p&gt;&#10;&lt;p&gt;The modern young researcher, with all their zeal, is first expected to answer the gargantuan question of “where to begin”? They cannot imagine rekindling their curiosity. They must sift through the noise, adapt, and learn to call bullshit, developing the impossible skill of an &amp;ldquo;eye” for plausible good research even before they learn a thing or two about the topic at hand. They must master human psychology before they can write an equation or write a piece of code.&lt;/p&gt;&#10;&lt;h1 id="the-hopelessness-of-the-situation"&gt;The hopelessness of the situation&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Ignorance is bliss&lt;br&gt;&#10;~Cypher, The Matrix&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;The ICLR interface is notoriously bad for browsing and discovering new research, so people have scrapped the platform and built better interfaces around it. I &lt;a href="https://papercopilot.com/paper-list/iclr-paper-list/iclr-2026-paper-list/" target="_blank" rel="noopener noreferrer"&gt;went to one&lt;/a&gt;&#10; and searched for “theory”. 500 papers turned up in the search. My heart sank. An average researcher like myself would require a day to fully engage with a paper, building intuition, understanding the code, perhaps trying to implement bits and pieces, reviewing past work, and so on. That’s more than a year of just trying to understand what the &lt;em&gt;state of ML is&lt;/em&gt;. But wait, the &lt;em&gt;next edition&lt;/em&gt; of ICLR will be due in 365 days, and the research debt would keep piling up. You know the fiendish part of this situation? Alongside the 500 papers, it was written “2%”. It meant I was only looking at 2% of the ~20K papers submitted to ICLR this year.&lt;/p&gt;&#10;&lt;p&gt;Okay, you must be silly to read all 500 papers, Basil. Apply the rule of the 98th percentile. That’s around 10 papers. Tractable. But wait, that doesn’t give you diversity. Doesn’t build the “research muscles,” so you expand it to 10%, which is around 50 papers. That’s around two months of effort, which is, to euphemistically put it, two months of a polished, cogwork machine of utmost discipline consuming research for upwards of 6-8 hours a day. &lt;em&gt;Long Sigh&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;But wait, remember the “2%”. We are only dealing with theory papers here. What about systems? Multimodal training? Continual learning? Spectral theory? evals? What about the 98% of research?&lt;/p&gt;&#10;&lt;p&gt;And my dear friend, this is just one conference. There’s NeurIPS, ICML, IROS, CVPR, EVVC, AAMAS, and 5 other “A-tier” conferences.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-b365474d" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-b365474d" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/lament/1.jpg" alt="The elephant in the room. Picture courtesy: Google, meme is by yours truly." loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-b365474d" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/lament/1.jpg" alt="The elephant in the room. Picture courtesy: Google, meme is by yours truly." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;The elephant in the room. Picture courtesy: Google, meme is by yours truly.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;I haven’t even factored in the fact that as a researcher one is supposed to…well, actually do research. We have only been concerned so far with keeping up to speed with the current interests and trends. The fact is, even if we forgo our own research and become cogwork machines, we will only be able to keep up with at most 2% of research at one conference, or about 0.2% of ML research, assuming we only believe the universe of all A-tier conferences consists of 10 elements, which already costs us 3 months or around 25% of the year.&lt;/p&gt;&#10;&lt;p&gt;An average researcher will know only a negligible amount of research. Forget about deeply engaging with it.&lt;/p&gt;&#10;&lt;h1 id="ossification-of-research-interests"&gt;Ossification of research interests&lt;/h1&gt;&#10;&lt;p&gt;What does this lead to? Ossification of research expertise and taste. Researchers tend to specialize rather than diversify. Is it good? Is it bad? Who knows. One camp would argue that deeper expertise with decades of experience in a narrow subfield or even topic is required for a breakthrough, while the other camp (to which I belong, btw) would argue that research is a breadth-first search with many cross-edges and benefits when ideas cross-pollinate between seemingly far-away research disciplines. I mean, look at the history of human science and discovery. The legends and the giants were all polymaths – Da Vinci, Gauss, Bernoulli, Lagrange, Leibniz – they studied the natural sciences but were also masters of mathematics, philosophy, chemistry, biology, astronomy, statecraft, linguistics, and whatnot.&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;There is pretty bad news for a modern-day polymath aspirant.&lt;/p&gt;&#10;&lt;p&gt;What has the shape of research become? Dendritic protuberances cluster around local regions in the research state space where most researchers now tend to spend their entire lives. Only occasionally does some braveheart push forward, or some old research get rediscovered, and the frontier advances. In my opinion, the most pressing need of the hour is more ambitious, frontier research.&lt;/p&gt;&#10;&lt;p&gt;TODO: INSERT ILLUSTRATION OF SHAPE OF RESEARCH&lt;/p&gt;&#10;&lt;p&gt;Which begs the question: what should happen to change the incentives that have nurtured this structure? Has the flood of research itself been the Achilles&amp;rsquo; heel for fostering good researchers who produce quality work? I don’t think so. Remember the normal. We need to change its shape.&lt;/p&gt;&#10;&lt;h1 id="the-paper-black-hole"&gt;The “paper” black hole&lt;/h1&gt;&#10;&lt;p&gt;Alright, all this aside, I would want to present a bitter truth to young researchers like myself. Thought of another way: what are the chances that a young paper is actually read? Assuming there are 5K accepted papers from all 10 conferences, that’s around 50K papers. Assume there are 10 subfields and the researcher only publishes in one of them, so that’s around 5000 papers. What are the chances that the paper is read?&lt;/p&gt;&#10;&lt;p&gt;So, given that you magically survive the random lucky draw of acceptance with a 25% chance and the normal distribution of “merit” in research quality, here is the cumulative table Gemini generated:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Distance from Center (Mean)&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Benchmark Percentage&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Unit Allocation&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Cumulative Percentile Range&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Far Left (Extremely Low: -3σ to -2σ)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.14%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;107 units&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Up to 2.27%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Left Side (Below Average: -2σ to -1σ)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;13.59%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;680 units&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.27% to 15.87%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Center-Left (Average/Low-Mid: -1σ to 0σ)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;34.13%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1,707 units&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;15.87% to 50.00%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Center-Right (Average/High-Mid: 0σ to +1σ)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;34.13%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1,707 units&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;50.00% to 84.13%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Right Side (Above Average: +1σ to +2σ)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;13.59%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;680 units&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;84.13% to 97.73%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Far Right (Extremely High: +2σ to +3σ)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.14%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;107 units&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;97.73% to 99.87%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Outliers (Extreme tails: Beyond ±3σ)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.27%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;13 units&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.00% to 0.13% and 99.87% to 100%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;So, to get a reasonable number of peers to discover your research, you&amp;rsquo;d need to be in the 99.87th percentile+ category. Assuming we swallow our pride and relax the assumptions, the next best thing is to be in the 97th percentile bucket, with around 100 other papers competing for our peers&amp;rsquo; attention. &lt;em&gt;Long sigh&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Caveat&lt;/strong&gt;: This model makes a lot of assumptions about how research propagates and that researchers are rational creatures who only start reading from the top, and completely discounts the fact that search engines exist, which can resurface research from the average and so on. But I think it’s still a decent model to be aware of if one wants to make it, whatever that means.&lt;/p&gt;&#10;&lt;h1 id="solutions"&gt;Solutions&lt;/h1&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Where do we go now?&lt;br&gt;&#10;~Axl Rose, Sweet Child O’ Mine&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;The point of research is to seek and discover truth, to ask a non-trivial question about nature and try to seek an answer. I know it&amp;rsquo;s sounds a whole lot corny but it sounded better in my head, trust me. It should not be Goodharted by paper count to get tenure or bag that million-dollar package or whatever. I know paper quality is hard to quantify and there are no good quantifiable proxies for good research, but oh well.&lt;/p&gt;&#10;&lt;p&gt;As a young researcher, one must be ruthless about one’s choices when it comes to consuming research. There’s literally a ton of garbage out there. We must also strive to build better retrieval systems that go beyond TF-IDF, reranking, or even modern generative retrievals.&lt;/p&gt;&#10;&lt;p&gt;Incentivize good research. I know it’s easier said than done because such arrangements go against the principle of efficient capital allocation, but we need people who can do maverick, moon-shot research without concerns for tenure or grant cycles.&lt;/p&gt;&#10;&lt;p&gt;I haven’t thought much about how to fix the rape of the review system and to maintain the integrity of the process, but some people seem to be considering a &lt;a href="https://openreview.net/pdf?id=6IiZXiqP3Q" target="_blank" rel="noopener noreferrer"&gt;credit-based system&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;h1 id="footnotes"&gt;Footnotes&lt;/h1&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Well not all of them were all of these things but you get the idea.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Day 1 of n: Reading (and Implementing) Ilya's 30 ML Papers</title><link>https://basillabib.com/post/day-1-of-n-reading-and-implementing-ilyas-30-ml-papers/</link><pubDate>Wed, 02 Sep 2026 19:01:50 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/day-1-of-n-reading-and-implementing-ilyas-30-ml-papers/</guid><description>&lt;p&gt;Source material:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a href="https://gist.github.com/matijagrcic/ae8353eb1e6be84a7c85d9fdc2f9631f" target="_blank" rel="noopener noreferrer"&gt;https://gist.github.com/matijagrcic/ae8353eb1e6be84a7c85d9fdc2f9631f&lt;/a&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://luisto.fi/blog/im-planning-to-read-ilya-sutskevers-30/" target="_blank" rel="noopener noreferrer"&gt;https://luisto.fi/blog/im-planning-to-read-ilya-sutskevers-30/&lt;/a&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://aman.ai/primers/ai/top-30-papers/" target="_blank" rel="noopener noreferrer"&gt;https://aman.ai/primers/ai/top-30-papers/&lt;/a&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.turingpost.com/p/ilya-sutskever-reading-list" target="_blank" rel="noopener noreferrer"&gt;https://www.turingpost.com/p/ilya-sutskever-reading-list&lt;/a&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Also supplemented by Essential Deep Learning papers. See &lt;a href="https://papers.baulab.info" target="_blank" rel="noopener noreferrer"&gt;https://papers.baulab.info&lt;/a&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;The lists differ considerably due to no single source of truth and the papers are not arranged in order of attack.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="my-approach"&gt;My Approach&lt;/h2&gt;&#10;&lt;p&gt;My approach will be different in the sense that I will not just read the papers but try to grok them, implement as much as possible (with as little shameless copy-pasting from LLMs) and go into small tree search around the topic anchored by the mother paper. One day one paper, 30 papers. Let’s go.&lt;/p&gt;&#10;&lt;h2 id="group-by-paper-category"&gt;Group by paper category&lt;/h2&gt;&#10;&lt;p&gt;Papers are roughly grouped into:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Architecture papers&lt;/li&gt;&#10;&lt;li&gt;Theory papers&lt;/li&gt;&#10;&lt;li&gt;Engineering papers&lt;/li&gt;&#10;&lt;li&gt;Applications papers&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;details&gt;&#10; &lt;summary&gt;Category-wise grouping of papers&lt;/summary&gt;&#10;&lt;pre&gt;&lt;code&gt;### Architecture&#10;1. Attention is all you need&#10; - Go deeper into types of attention mechanisms. Most critiques don’t like attention; why? Why is it so effective then?&#10;2. NTM&#10;3. RNNs&#10; - Variations include LSTMs, GRUs&#10; - See Olah, Karpathy blogposts&#10; 4. Pointer Networks&#10; 5. CNN&#10;&#10;### Theory&#10;&#10;1. Scaling Laws&#10;2. MDL&#10;3. Komogrov Complexity&#10;&#10;### Engineering&#10;1. GPipe&#10;&#10;### Applications&#10;1. Seq2Seq&#10;2. Protein folding&#10;3. AI4Science - see GDM research&#10;4. AI4Math&#10;5. Automated research&#10;6. Translation&#10;7. Speech to Text and family of models &#10;&lt;/code&gt;&lt;/pre&gt;&#10;&lt;/details&gt;&#10;&lt;h2 id="papers-arranged-by-increasing-epistemic-knowledge-for-beginners"&gt;Papers arranged by increasing epistemic knowledge (for beginners)&lt;/h2&gt;&#10;&lt;p&gt;TBD&lt;/p&gt;&#10;&lt;h1 id="day-1-lstms-rnns-and-complexodynamics"&gt;Day 1: LSTMs, RNNs, and Complexodynamics&lt;/h1&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Re-read Chris Olah’s &lt;a href="https://colah.github.io/posts/2015-08-Understanding-LSTMs/" target="_blank" rel="noopener noreferrer"&gt;excellent exposition of LSTM&lt;/a&gt;&#10;.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Explore my implementation in &lt;a href="https://github.com/basil08/deeplearning/blob/main/rnn/lstm.ipynb" target="_blank" rel="noopener noreferrer"&gt;this notebook&lt;/a&gt;&#10; (boiler plate code is LLM-generated).&lt;/p&gt;&#10;&lt;p&gt;This time I took the time to implement it using basic PyTorch. I remember my last ambitious (and somewhat stupid) attempt at writing autograd “from scratch” failed marvelously and had to be abandoned with the bitter lesson (pun not intended) that I am, after all, a poor programmer. Anyways, keeping my ego aside, this time, I took the path of least resistance, focused on the main problem - namely implementing the architecture from just the blogpost text - mind you I have never read any paper on LSTM before, and worry about details like dataloaders and training loop later.&lt;/p&gt;&#10;&lt;p&gt;This strategy paid off well on average as I quickly ran my code and started seeing loss collapse to zero on the second epoch! The loss was stable and barely moving. Obviously the model was not learning anything. And sure enough, Claude pointed out that I have forgotten to return the variable that is supposed to be trained and moreover, it was a class variable rather than a local variable that holds the hidden state and long-term memory register! Yeah. I felt ashamed. My ego was humbled, but it was only the beginning.&lt;/p&gt;&#10;&lt;p&gt;I was asking Gemini for code snippets for “boring” bits and pieces such as DataLoader, normalization, and inference code which accelerated the process considerably and made sure I didn’t waste time on less important aspects. I think this strategy is very good and I will try to use it more in the coming days as I implement other architectures as well. I also noted that large chunks of the ML pipeline is just boilerplate code (training loop, data prep, evals, inference, etc.) and should be packaged into neat reusable modules. Who knows I might build a small library at the end of this learning project.&lt;/p&gt;&#10;&lt;p&gt;Anyways, Claude pointed out glaring structural and systemic flaws in my assumptions and design decisions. For example, I was using a word-based tokenizing scheme that split on spaces (I know, don’t roll your eyes; I wanted to start simple, and I was going to implement BPE), and Claude said to use a character-based tokenizing scheme because the corpus was too small. Similarly, it prepped the data into seq_len chunks of a sliding window, essentially blowing up the training dataset. It also suggested (and wrote the code) for an evaluation subroutine for validation. I was following the conventional epoch-based training loops; it suggested using steps since the corpus was too big and there is essentially no new information to learn across epochs. It also did some back-of-the-envelope calculations to tune some key hyperparams and suggested increasing hidden dim and other practical, insightful nuggets. However, at this scale, Claude probably already has the optimal hyperparams memorized lol. I also naively implemented argmax over the softmax-ed logits. Claude suggested I move to temperature-based sampling to avoid getting into repeated phrase loops &amp;ndash; very valid point btw.&lt;/p&gt;&#10;&lt;p&gt;The model trained alright. It generated text that, funnily, represented Shakespearean writing. Pretty cool stuff. I trained on my Mac. On default dim=128, it took around 30 mins to train for 20K steps across the whole corpus (~5M training pairs).&lt;/p&gt;&#10;&lt;p&gt;This exercise made me realise when people say how much of a dark science practically training ML models is. There are so many hyperparams to tune and there is no scientific grounding save a few heuristics and just accumulated industry wisdom. It’s like shooting an arrow in the dark and hoping it lands on the target. In my opinion, people who train models with trillions of parameters across distributed clusters are nothing short of modern-day wizards!&lt;/p&gt;&#10;&lt;p&gt;Sample Prompt:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;I know not why I am so sad&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Sample Output: &lt;code&gt;max_chars=100&lt;/code&gt; and &lt;code&gt;temperature=0.5&lt;/code&gt;; some hyperparams (&lt;code&gt;dim=256&lt;/code&gt;, &lt;code&gt;batch=64&lt;/code&gt;, &lt;code&gt;seq_len=128&lt;/code&gt;)&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Raw: [&amp;lsquo;I&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;k&amp;rsquo;, &amp;rsquo;n&amp;rsquo;, &amp;lsquo;o&amp;rsquo;, &amp;lsquo;w&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;rsquo;n&amp;rsquo;, &amp;lsquo;o&amp;rsquo;, &amp;rsquo;t&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;w&amp;rsquo;, &amp;lsquo;h&amp;rsquo;, &amp;lsquo;y&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;I&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;a&amp;rsquo;, &amp;rsquo;m&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;rsquo;s&amp;rsquo;, &amp;lsquo;o&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;rsquo;s&amp;rsquo;, &amp;lsquo;a&amp;rsquo;, &amp;rsquo;d&amp;rsquo;, &amp;lsquo;\n&amp;rsquo;, &amp;lsquo;A&amp;rsquo;, &amp;rsquo;n&amp;rsquo;, &amp;rsquo;d&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;y&amp;rsquo;, &amp;rsquo;e&amp;rsquo;, &amp;rsquo;t&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;rsquo;l&amp;rsquo;, &amp;lsquo;i&amp;rsquo;, &amp;lsquo;v&amp;rsquo;, &amp;rsquo;e&amp;rsquo;, &amp;rsquo;d&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;y&amp;rsquo;, &amp;lsquo;o&amp;rsquo;, &amp;lsquo;u&amp;rsquo;, &amp;lsquo;r&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;rsquo;l&amp;rsquo;, &amp;rsquo;e&amp;rsquo;, &amp;lsquo;a&amp;rsquo;, &amp;rsquo;s&amp;rsquo;, &amp;rsquo;t&amp;rsquo;, &amp;lsquo;,&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;y&amp;rsquo;, &amp;rsquo;e&amp;rsquo;, &amp;lsquo;a&amp;rsquo;, &amp;lsquo;k&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;a&amp;rsquo;, &amp;lsquo;g&amp;rsquo;, &amp;lsquo;a&amp;rsquo;, &amp;lsquo;i&amp;rsquo;, &amp;rsquo;n&amp;rsquo;, &amp;lsquo;,&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;I&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;rsquo;t&amp;rsquo;, &amp;lsquo;a&amp;rsquo;, &amp;lsquo;k&amp;rsquo;, &amp;rsquo;e&amp;rsquo;, &amp;lsquo;\n&amp;rsquo;, &amp;rsquo;t&amp;rsquo;, &amp;lsquo;h&amp;rsquo;, &amp;rsquo;e&amp;rsquo;, &amp;rsquo;s&amp;rsquo;, &amp;rsquo;e&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;rsquo;t&amp;rsquo;, &amp;lsquo;w&amp;rsquo;, &amp;lsquo;a&amp;rsquo;, &amp;lsquo;i&amp;rsquo;, &amp;rsquo;n&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;c&amp;rsquo;, &amp;lsquo;o&amp;rsquo;, &amp;lsquo;r&amp;rsquo;, &amp;lsquo;r&amp;rsquo;, &amp;lsquo;u&amp;rsquo;, &amp;lsquo;p&amp;rsquo;, &amp;rsquo;t&amp;rsquo;, &amp;rsquo;s&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;rsquo;s&amp;rsquo;, &amp;lsquo;o&amp;rsquo;, &amp;lsquo;,&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;rsquo;s&amp;rsquo;, &amp;lsquo;i&amp;rsquo;, &amp;lsquo;r&amp;rsquo;, &amp;lsquo;,&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;w&amp;rsquo;, &amp;lsquo;i&amp;rsquo;, &amp;rsquo;t&amp;rsquo;, &amp;lsquo;h&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;a&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;g&amp;rsquo;, &amp;lsquo;r&amp;rsquo;, &amp;lsquo;a&amp;rsquo;, &amp;lsquo;c&amp;rsquo;, &amp;rsquo;e&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;o&amp;rsquo;, &amp;lsquo;f&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;lsquo;h&amp;rsquo;, &amp;lsquo;i&amp;rsquo;, &amp;rsquo;s&amp;rsquo;, &amp;rsquo; &amp;lsquo;, &amp;rsquo;s&amp;rsquo;, &amp;lsquo;p&amp;rsquo;, &amp;rsquo;e&amp;rsquo;, &amp;rsquo;e&amp;rsquo;]&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;I know not why I am so sad\nAnd yet lived your least yeak again, I take\nthese twain corrupts so, sir with a grace of his spee&amp;hellip;&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;And then I guess we&amp;rsquo;ll never know what Shakespearean my model was musing over. Also interestingly note non-English words like &amp;ldquo;yeak&amp;rdquo;. Pretty impressed that the model learned English words and basic grammar semantics.&lt;/p&gt;&#10;&lt;ol start="2"&gt;&#10;&lt;li&gt;I also skimmed through some parts of the &lt;a href="https://arxiv.org/abs/1706.03762" target="_blank" rel="noopener noreferrer"&gt;Attention paper&lt;/a&gt;&#10; but didn’t deep dive today due to lack of time.&lt;/li&gt;&#10;&lt;li&gt;I also read through &lt;a href="https://scottaaronson.blog/?p=762" target="_blank" rel="noopener noreferrer"&gt;Aaronson’s post on complexodynamics&lt;/a&gt;&#10; - weird idea but nice to think about. Still need to get my head around the sophistication metric.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Trial by Fire: Tenure as Head of Technology at Rendezvous ‘25</title><link>https://basillabib.com/post/trial-by-fire-tenure-as-head-of-technology-at-rendezvous-25/</link><pubDate>Wed, 05 Nov 2025 12:41:54 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/trial-by-fire-tenure-as-head-of-technology-at-rendezvous-25/</guid><description>&lt;p&gt;Disclaimer: This post may contain expletives.&lt;/p&gt;&#10;&lt;p&gt;&lt;span class="dropcap"&gt;&lt;span class="dropcap-initial"&gt;T&lt;/span&gt;his post&lt;/span&gt; chronicles my experiences as head of technology at &lt;a href="https://rdv-iitd.org/" target="_blank" rel="noopener noreferrer"&gt;Rendezvous ‘25&lt;/a&gt;&#10;, the annual cultural fest of IIT Delhi and lays out how events unfolded in roughly chronological order, what challenges we faced, how we overcame them, how I managed a team, and finally, delivered to keep the awesome tech culture going at RDV. For those of you who didn’t get a pronite pass and are cursing me right now, please read on.&lt;/p&gt;&#10;&lt;p&gt;This post is written as a review document for myself to come to later and revisit the technical challenges, social challenges, and indeed the intense drama of it all. I think the lessons drawn from this experience are timeless and apply to any intersection of humans and technology (More on this later, if you’re confused). This post is a heartfelt appreciation post for those who stood by my side during these times and it is also a warning to those who did not heed my warnings. More importantly, this post is for the thoughtful so they may learn something from this hard-earned wisdom.&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;&#10;&lt;h2 id="the-beginning"&gt;The beginning&lt;/h2&gt;&#10;&lt;p&gt;For the uninitiated, Rendezvous or RDV is the annual cultural fest of IIT Delhi and witnesses more than 10K footfall from around the country to the IIT Delhi campus, hosts more than 100+ events, competitions, and is a 4 day mega-event. The entire fest is organised by students. The hierarchy has the CTMs at the top and are generally final year students followed by coordinators and executives. There are over 35 verticals ranging from marketing, publicity, events, and so on. The tech vertical is responsible for maintaining detailed records of registrations, entry, gate passes, event passes, concert night booking and security management. It facilitates the RDV campus ambassador program (CAP) with a task-based tracker system. It also develops and maintains the official RDV website which serves as the face of the fest in cyberspace. As you can imagine, this vertical facilitates tracking, accountability, and complements other verticals like security, publicity, marketing, and just about every other vertical.&lt;/p&gt;&#10;&lt;h2 id="timelines"&gt;Timelines&lt;/h2&gt;&#10;&lt;p&gt;This year, RDV was scheduled from 27th to 30th Sep. The notification for the CTM applications were floated during last week of July and I filled the form out of curiosity. I wanted to get more hands-on experience in leading a tech team and ship products that people actually use - RDV Tech is one of the best places to get this valuable experience in IIT Delhi. Sure enough, I was selected along with a junior and we started out recruiting the team. We had around 60 days to do everything. &lt;em&gt;Everything&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h2 id="building-a-team"&gt;Building a team&lt;/h2&gt;&#10;&lt;p&gt;We decided to keep a lean team with a flat hierarchy to reduce communication overhead. In practice, the tech team is involved in a workflow at the very end and other verticals just assume that everything is possible in tech (At least, that’s how it works in college fests). This meant that we needed shorter feedback cycles to deliver and keeping a 3-tiered team just won’t cut it. When other verticals had more than 20 or even 30 people, the tech team finally came down to 5 people. Yes, just five people.&lt;/p&gt;&#10;&lt;h3 id="the-team-mantra"&gt;The team mantra&lt;/h3&gt;&#10;&lt;p&gt;The mantra I followed was to get only A players, pack as much talent as possible in as few people as possible. This required multiple cycles of hiring and firing and I am glad I did this - an excellent team is absolutely non-negotiable. A lot of what we achieved this time was only possible because of my team. I had to fire around five to six people before I settled on a team of five people who showed up everyday, did the work, and more importantly, enjoyed doing the work. Among these five people, I had two “core” juniors who were exceptionally diligent and sincere (You know who you are :)). I often used to notice how people worked - some of them just grudgingly fed the entire spec to GPT and expected a UI out of magic while some genuinely enjoyed what they were doing - looking at network tab and response code to figure out what’s wrong, using GPT to their advantage and not as a barrier, and those who didn’t tell me “This is not working” but who told me “This is not working because of X and I know how to fix it”. These are the kind of people one should vehemently seek and nurture.&lt;/p&gt;&#10;&lt;p&gt;I remember quite vividly what one of my team member told me in my first meet (slightly paraphrased): “&lt;em&gt;Waise toh meine socha tha ki RDV ka kaam nahi karunga magar mein log dekh kar kaam karta hun aur aap mujhe acche lage&lt;/em&gt;” (I thought I would leave RDV team but I found your personality appealing, that’s why I decided to stay). &lt;em&gt;wink wink&lt;/em&gt;&lt;/p&gt;&#10;&lt;h2 id="overview-of-systems-slightly-technical"&gt;Overview of Systems (Slightly technical)&lt;/h2&gt;&#10;&lt;p&gt;I have been extolling the tech team a lot but what exactly does the tech team do?&lt;/p&gt;&#10;&lt;p&gt;The RDV tech infrastructure is developed from scratch by the RDV team and combines years of experience passed on from previous CTMs. We reused a lot of the infrastructure set up by last year’s team, managed, and modified a lot of the services to cater to the growing needs of the team. The RDV tech infra is a microservices-based design comprising multiple services like the main website, CA portal, admin portal, pronite portal, a URL proxy service, a mailing server, and a centralised OAuth authentication service. We also maintain a mobile app for scanning gate and concert passes for tracking admissions. All services are hosted and managed on AWS and feature multiple layers of rate limits. We use AWS firewall (WAF) as well as cloudflare’s rate limit rules, and involve Google ReCAPTCHA service for selecting high traffic stages in our workflows (such as authentication, and pass booking) to eliminate bot attack attempts.&lt;/p&gt;&#10;&lt;p&gt;Some new things we tried this time:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Gate passes and Event level passes&lt;/li&gt;&#10;&lt;li&gt;Integration of marketing deliverables in gate pass generation workflows&lt;/li&gt;&#10;&lt;li&gt;Integration of additional biometric data as per security guidelines&lt;/li&gt;&#10;&lt;li&gt;Integration of an ML pipeline for automated task verification&lt;/li&gt;&#10;&lt;li&gt;Complete rehaul of the CA Admin portal&lt;/li&gt;&#10;&lt;li&gt;A new mailing server, and a temporary service monitor dashboard&lt;/li&gt;&#10;&lt;li&gt;Stress testing of high traffic and mission-critical services using Locust&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="early-days---setting-things-up"&gt;Early Days - Setting things up&lt;/h2&gt;&#10;&lt;p&gt;After I got access to the AWS and Github org, I started poking around the existing services. The last CTM had done a fantastic job of organising a lot of these things already such as dockerising services, setting up environments on AWS Beanstalk. This was even my first time managing AWS and I knew I would be managing the entire infrastructure some time in the future so I knew I had to master it. Some loss of sleep hours and I was mostly familiar with the basic tools of the trade for there is no better way to learn how to douse a fire than when there is one on your tail.&lt;/p&gt;&#10;&lt;p&gt;If you are connecting AWS RDS using PgAdmin, remember to turn ssl_mode to require. This one configuration took way too long to figure out. And indeed, good DevOps is all a matter of correct configurations. I quickly started making SOPs for deployment and cleanup. See an example SOP below:&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;[Redacted]&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;In all this, &lt;a href="https://www.perplexity.ai/" target="_blank" rel="noopener noreferrer"&gt;Perplexity&lt;/a&gt;&#10; helped a lot in speeding up the search process and retrieving only relevant info in an easy to digest format. I think for devs, Perplexity just makes so much more sense than wading through 10 SO threads before you start getting an idea of what’s going wrong. I got a &lt;a href="http://cursor.com/" target="_blank" rel="noopener noreferrer"&gt;Cursor Pro&lt;/a&gt;&#10; subscription and gave it to the team even though they were initially reluctant (coding purists). However, later on, they told me that Cursor had, in fact, helped them quickly prototype designs, especially UI stuff, which just kept changing a lot - a typical feature of fests.&lt;/p&gt;&#10;&lt;h3 id="deploying-my-first-app-on-aws"&gt;Deploying my first app on AWS&lt;/h3&gt;&#10;&lt;p&gt;I remember deploying my first environment on EBS took around an hour - creating IAMs with proper roles, checking and double-checking each role’s permissions, setting env variables, configuring VPC subnets, configuring load balancers, configuring firewalls, configuring the database and the service user and double-checking their permissions, configuring third party services like 2Factor for mobile OTP service (Great service at reasonable price and very cooperative person, 10/10 recommend), instance types, and codepipelines for continuous deployment. Once again, I was reminded that there is no room for mistakes and there is no room for assumption.&lt;/p&gt;&#10;&lt;p&gt;Along with learning myself, I had to manage communications with the larger team and had to deliver on the CA Portal in a matter of a week since it would be used a month or so leading up to the main fest. I remember the minor exams were also coming up and I had some other prior commitments that I had to tackle as well. As usual, the design team did a sloppy job and we had to improvise on our own to make the design functional. It is partly not their fault as the designer had never used the CA portal or knew about its workflows and what kind of issues the users might face.&lt;/p&gt;&#10;&lt;p&gt;The CAP took two days and at least 10 attempts to deploy partly due to poorly understood GPT written code, some overconfidence on part of the developer, and an extra front slash in the environment URL configuration. The path &lt;code&gt;BASE_URL/route&lt;/code&gt; should have been used instead of &lt;code&gt;BASE_URL/route/&lt;/code&gt;. Eventually, the team figured it out and I’m sure learned a lot in the process. There were &lt;em&gt;countless&lt;/em&gt; such issues that forced us to think analytically and often under time constraints.&lt;/p&gt;&#10;&lt;h3 id="work-culture"&gt;Work culture&lt;/h3&gt;&#10;&lt;p&gt;As I already said, the initial days were tumultuous in terms of team composition, I had to remove people almost on a daily basis but eventually the team settled down and the first thing I did was set up a work culture. I signalled it simply by showing up &lt;em&gt;every single day&lt;/em&gt; at the exact &lt;em&gt;same time&lt;/em&gt; at the &lt;em&gt;same location&lt;/em&gt; and asking everyone to do the same. This move meant that the RDV tech team meant business, that the team lead was not messing around, that the team had to actively take up issues and open PR against them. Surprisingly, almost all of my team were sophomores who had little idea about what git even was and so I had to painstakingly (but patiently) teach them about git workflows.&lt;/p&gt;&#10;&lt;p&gt;INSERT: meeting at 9pm at sac message&lt;/p&gt;&#10;&lt;p&gt;Eventually, the daily 9pm meets at SAC, often extending till 5am in the morning (!) turned out to be great for team bonding as intense coding sessions and discussions gave way to gossip and romance tales, sometimes over regular night mess treats. I think that money was well-spent in establishing trust as I established myself as a senior who was not just interested in getting work done but who was genuinely interested in the flourishing and well-being of my juniors.&lt;/p&gt;&#10;&lt;h3 id="dealing-with-liabilities"&gt;Dealing with liabilities&lt;/h3&gt;&#10;&lt;p&gt;My Co-CTM was turning out to be more of a liability than an asset: not showing up, having to follow up multiple times, no interest in the work, no responsibility or accountability. It turned out to be so bad that work started suffering and I had to report him. The larger team decided to let them go but then I practised compassion and asked them not to. Ironically, my compassion came back to bite me later but more on this later. All I learnt from this experience was that not everyone is meant to be a leader and being too compassionate is probably not okay. Spending even one more word on this is useless just as I decided spending a minute of thought would be back at that time.&lt;/p&gt;&#10;&lt;h3 id="time-for-reflection"&gt;Time for reflection&lt;/h3&gt;&#10;&lt;p&gt;Leading a tech team also exposes an opportunity to understand and discover oneself. I started noticing things about how I manage things, interact with others, and questioning everything. I have an eye for detail, understand people psychology (an indispensable but rare asset for engineers), and am slightly OCD-ed. I remember getting really pissed off and tensed over a single link not working on the mobile version of the footer of the main website.&lt;/p&gt;&#10;&lt;p&gt;As the team got busy with assimilating the existing codebase and writing more code, I concerned myself with administrative things and the budget in particular. We needed at least 1000 USD going by last year’s cost and we got exactly 0 USD from the team. Multiple reminders to the concerned verticals were watered down or ignored at worst. Out of my self dignity, I stopped constantly reminding them. We had some leftover credits from last year and I decided to manage with that. It would mean rigorously optimising all services, adopting a lean strategy, and cutting down on frivolous costs (Ironically, we ended up wasting 200 USD on a pilot app that was of no value eventually due to carelessness on my co-CTM’s part).&lt;/p&gt;&#10;&lt;h3 id="good-software--reduce-human-cognitive-load"&gt;Good software = Reduce human cognitive load&lt;/h3&gt;&#10;&lt;p&gt;Designing the CA Portal and the accompanying Admin portal was a masterclass in developing production-grade software that thousands of people used. It also meant that even a single word in the instructions would wreak confusion on thousands. The leverage we had was insane. One thing we learnt was that keeping the target audience in mind while designing the app was most important. We had to ensure each page transition was as simple as possible for cognitive kindness and easier comprehension. If we were careless, users got confused and we had to explain things on a one-on-one basis. We even had to dumb down “clear cache” to “clear browser cookies” and then eventually to “use a different device”. One piece of advice for the future team will be to ensure good cookie management on the frontend. Even with accessibility, mobile-first approaches, there were one-off issues with devices because some had ad-blockers or pop-up blockers installed and just weird edge-cases that we had not anticipated on an average user. That is the thing, right? One is not developing for the average user, one is developing for &lt;em&gt;all users&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;Our previous OTP service had some issues so we started searching for a new one. We found 2Factor.in which turned out to be more reliable than I expected. Due to time and resource constraints, we didn&amp;rsquo;t do DLT registration and went ahead with a default mobile carrier that 2Factor helpfully provided. We did registration trend analysis to find peak demands and last year’s volume data to pick a cost-effective plan.&lt;/p&gt;&#10;&lt;h3 id="the-first-crisis"&gt;The first crisis&lt;/h3&gt;&#10;&lt;p&gt;Inspite of all precautions and SOPs I could write, it is inevitable that things would fail and I got a taste of it right on the day of the first major run of the CA Portal with the internal team. The idea was to onboard the internal team to track their progress on the task and do a makeshift identification system where we tweak their internal IDs to mark them as RDV team members. At this point, we had three types of users on the platform:&#10;non-IITD external users - treated as a typical CA, full task management, approval system, and points tracking&#10;IITD, non-RDV user - also treated as a typical CA but kinda redundant&#10;IITD, RDV users - treated as a typical CA but have to complete specially marked tasks only.&lt;/p&gt;&#10;&lt;p&gt;One advice for the future team will be to implement a Task Name and some kind of tagging system for accommodating one-off publicity team requirements.&lt;/p&gt;&#10;&lt;p&gt;Anyways, back to the story, the entire team of ~600 have to be onboarded on the platform on Sep 26th and I start getting reports of the authentication system failing. I initially dismiss such reports because there is generally some error on their end so my reaction time is longer to classify reports of a particular kind as a genuine issue. By the time I realised there’s an issue, the team was already gearing up to onboard the platform. I realised there would be massive chaos, so I ran to fix the issue. The issue turned out to be the failing reCAPTCHA service which had exhausted the free trial limit. I laughed so hard. I added my credit card and shifted everything to a paid account. Even last year’s CTM reported that the same thing happened to him two days before the fest.&lt;/p&gt;&#10;&lt;h3 id="the-big-mistake"&gt;The big mistake&lt;/h3&gt;&#10;&lt;p&gt;I was aware that this year’s RDV will be under strict administrative supervision due to fund misappropriation allegations last year. This also meant less funds in general. To help the larger team, I agreed to onboard a third party service (say, DumbCompany) for the pass booking workflow. The pronite pass booking workflow is one of the most critical parts of the tech infra and we have a custom server catering to it. DC was a total black box for me. Their code was closed source and there was no obvious security measure that you expect to see on a ticketing platform. Anyways, we started collaborating and it translated to a lot of grunt work on my end such as copy pasting data from one sheet to another, creating redundant event objects on their platform since they did not have user categorisation. They didn’t even have user access control!!! I approved the risk since I was too blinded by “helping the larger team” inspite of clear instructions from last year’s CTM to not integrate any stupid platform with tech. In any case, their entire system was compromised on day 2 of the fest but we are getting ahead of things here.&lt;/p&gt;&#10;&lt;p&gt;One great thing I did, however, was that I approved only a partial collaboration with DC since I didn’t trust them one bit. This meant that the booking step would still be done by RDV tech and only the pass distribution would be handled by DC. This was an attempt to reduce blast radius in case of an emergency. We had no idea what information they had in their QR passes (!). Since this was a first time collaboration, a lot of the things had to makeshift as I highlighted above and all of the burden came down to me which I silently endured. They didn’t even have an API!!!! The entire system was opaque with no documentation, no statistics, no insights into what’s going on. It felt like being built over a weekend by a bunch of sophomores and definitely not a service that you roll out to cater to twenty thousand users.&lt;/p&gt;&#10;&lt;h3 id="marketing-team"&gt;Marketing team&lt;/h3&gt;&#10;&lt;p&gt;You guys need to understand that tech is not child’s play. Everything has to be properly configured, designed, tested, and secured before releasing it to people. This is because people love to “hack” websites and claim they hacked “RDV”. The tech RDV has evolved over the years is built to prevent every such nonsense. Adding new tasks during registration workflows at the last minute (increasing average registration time from 30 seconds to 1+ minutes) only meant lower registration rates - a simple psychological point that was lost on the marketing team.&lt;/p&gt;&#10;&lt;p&gt;As the days of the fest drew closer, conversations with DC got more intense since they had no infra to cater to RDV. As I was one of the two CTMs from the PG program, I expected feeling left out. But I started feeling treated as an outsider and the vertical at large was being neglected. The idea of not feeling welcomed didn’t bode well with me but I ignored it. I knew what I was building and I didn’t expect non-tech people to appreciate or even understand it at all.&lt;/p&gt;&#10;&lt;h3 id="the-insults-and-slanders"&gt;The Insults and Slanders&lt;/h3&gt;&#10;&lt;p&gt;Due to last year’s fund misappropriation allegations, the admin had mandated that the CTM team sign an undertaking to personally bear the cost of any deficits in the fest’s expenditure. On request for this year’s budget sheet, I was once again ignored by the marketing team. That’s when I decided to post a message on the CTM group saying that I will shut down the entire tech infra if I do not get the sheet by morning. I posted the message at 3 am and went to sleep. The sly thing about this arrangement was that the declaration was given to us at 10pm a day before the fest and our access cards were conditional on signing the declaration. Needless to say, I was furious. After countless nights of toiling fruitlessly for an event, I felt I was being insulted in plain sight now. There were some bitter messages posted about me by certain people in the group. I was hurt but chose to ignore - after all, how can you empty an already hollow soul? Some screenshots of the chat as proof:&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;[Redacted]&lt;/em&gt;&lt;/p&gt;&#10;&lt;h2 id="day-1"&gt;Day 1&lt;/h2&gt;&#10;&lt;p&gt;I woke up on day 1 and checked my phone and you won’t believe what I saw. A SHATTERED SCREEN! My phone was dead. I had 3 days of the ultimate test for 2 months of my team’s work. I got one of my junior’s phones as an immediate aid but lost all MFA setup on my old phone. Somehow I managed to setup auth for AWS and cloudflare on the new phone. My contacts were gone. It was a mess. I lost all my photos which included some of my most treasured memories. I was heartbroken but too numb to realise the implications back then.&lt;/p&gt;&#10;&lt;p&gt;Day 0 pronite bookings were a breeze. I expected this because no major artists were performing so demand were low. This also didn’t push the system enough for me to diagnose that I had actually forgotten to apply the firewall to the load balancer attached to the Pronite portal application instance…Oops.&lt;/p&gt;&#10;&lt;p&gt;When a million requests swarmed the system for day 1 bookings and I saw the system struggle to keep up, I quickly realised that all those sleepless nights of thankless work was for this moment, it wasn’t for some dumb POR or ego satisfaction, it was to feel the adrenaline rush when your engineering systems are put to the test in the wild. After initial hiccups with the WAF, it went smoothly.&lt;/p&gt;&#10;&lt;p&gt;Due to the dumb as fuck design of DC, I had to send data to the company in google sheets (yes, I know, DUMB). It was time-consuming and error prone and obviously not how things are done. Sure enough, I missed a sheet and a group of MSc students didn’t get any passes. Moreover, an important callout informing the end user about all slots filled was in white text colour and hence didn’t show up on a white background - “white on white bug”. I laughed. Good UI design is so damn important.&lt;/p&gt;&#10;&lt;p&gt;“It is working on my machine” is not a valid solution, designing apps for hundreds of user demands thinking laterally - it&amp;rsquo;s about empathising with conditions and experiences that you can never live by yourself.&lt;/p&gt;&#10;&lt;h2 id="what-happened-on-day-2"&gt;What happened on Day 2&lt;/h2&gt;&#10;&lt;p&gt;DC had just exposed an endpoint to show QR codes on the portal. That dumb endpoint required us to do intensive cryptographic calculation on our end. We pushed the update at 10 am and the pass booking slots were from 12 noon. I noticed that passes were not getting booked. Fuck.&lt;/p&gt;&#10;&lt;p&gt;I called the previous CTM and we got on a call to figure out the issue together. It turned out to be a memory hogging issue. Apparently, the new code had saturated all vRAM on the servers. Kudos to Shanky for diagnosing the issue in due time. I throttled the instance RAM by upgrading from small to medium and adjusted the quotas. We were fully booked.&lt;/p&gt;&#10;&lt;h3 id="the-setup"&gt;The setup&lt;/h3&gt;&#10;&lt;p&gt;The entry for day 2 was supposed to start from 5pm. We anticipated around 2000 students and 1000 guests, faculty, staff, sponsors, and so on. That’s a crowd of 3000 people. Delhi Police forces and the IITD security team along with bouncers were deployed. I could feel the adrenaline building. We were in talk with CSC to ensure that the external sender was whitelisted because if people didn’t get the QR code from DC on their emails, it would be a disaster.&lt;/p&gt;&#10;&lt;p&gt;Now, how DC’s system worked was that the issuer and scanner had the same login credentials. THERE WAS NO USER ACCESS CONTROL. The security team had decided to share the issuer credentials with freshers who will be scanning the passes at the venue entrance. This, in principle, meant that an inquisitive fresher could gain access to issuing passes if they could enter the OTP before it expired on the issuer portal. That’s exactly what happened.&lt;/p&gt;&#10;&lt;h3 id="the-discovery"&gt;The discovery&lt;/h3&gt;&#10;&lt;p&gt;Around 3pm, one of my team members noticed something curious. There were hundreds of emails being sent to the director’s email id. &lt;em&gt;Hundreds&lt;/em&gt;. The dumb people at DC marketed their ticketing system as a unique token for each user, then how can there be 600+ tokens issued to the same email id? I was infuriated. But even more than that, I was trying to figure out the implication of this. Clearly, some one has gotten access to the issuer portal, there was no access or event log for tracing, there was only one admin account with all permission. This was a doomed situation.&lt;/p&gt;&#10;&lt;h3 id="the-countermeasures"&gt;The countermeasures&lt;/h3&gt;&#10;&lt;p&gt;We immediately called DC’s representative and he basically raised his hands saying that he has “no control” over the system. I was beyond infuriated. I had to keep calm. I had to navigate this situation. The first thing I did was pull the plug on all DC issued QR codes. We sent out an email via the dean around 4pm saying that all such QRs are invalid. We replaced the DC scanner with our native scanner. We generated our fresh tokens and made it live on the booking portal itself under the Profile section. That would take care of the already booked users but we still had ~1000 people who were given passes due to their privilege like team members, staff, and so on. My fantastic team of 2 people whipped up an endpoint for batch QR generation and we manually distributed the QR codes to these people.&lt;/p&gt;&#10;&lt;p&gt;At 5:30 pm, the queues were hundreds of people long. The first scan failed. My heart sank. At that moment, I felt my second adrenaline rush of the day. I knew at the back of my mind that this subsystem was entirely untested on day 1, if this fails at this point there are thousands of people waiting to get entry. Memories of the stampede during my second year of RDV flashed at the back of my mind. Fuck. I checked the credentials and realised that the scanning schedule had to be updated. We did that and the first QR scanned. I cannot express my relief when I saw that green tick show up. Our system was deployed in production without testing and it worked. I was super proud of my team.&lt;/p&gt;&#10;&lt;h3 id="what-could-have-gone-worse"&gt;What could have gone worse?&lt;/h3&gt;&#10;&lt;p&gt;If the adversary was a bit more clever, they could have scraped the entire IITD directory and sent an email to all ten thousand members with a QR code. The system wouldn’t have complained because there was no damn upper limit - there was no uniqueness check even. A cleverly orchestrated attack would probably masquerade this as distributed across various user categories so detecting would be harder and by the time it could be figured out, things would be too late. That would mean ten thousand people showing up anticipating an entry into a venue of only three thousand capacity. A sure-shot recipe for a stampede. Thankfully, the attacker was not that sophisticated which makes me think there were either first or second year students.&lt;/p&gt;&#10;&lt;h3 id="we-got-lucky"&gt;We got lucky&lt;/h3&gt;&#10;&lt;p&gt;We were super lucky to discover this attack early enough to invalidate everything from DC and shift to our system in time. I am super proud of my team doing the heavy lifting within &lt;em&gt;one hour&lt;/em&gt;. Talk about shipping cycles and saving the day.&lt;/p&gt;&#10;&lt;h3 id="reflections"&gt;Reflections&lt;/h3&gt;&#10;&lt;p&gt;During this whole time, me and my team were holed up at the PFC studio at SAC along with the marketing coordinator, CTM (the same person who had written bitter words to me in the group a day before the fest), and the overall coordinator. We also had to explain everything to the professors and the deans. I could see life getting sucked out of the coordinator who had onboarded DC as calls started pouring in from the deans. I could sense his inexperience and honestly, I felt pity for him. I was naive and uneducated in the way of the world at some point too. I hope he learnt something that day. My respect for elders also increased manifolds that day. I realised how many times they have knowingly and unknowingly saved our asses when we fucked up. Except in this case, I was the one saving the situation. Retrospecting, that split second decision to invalidate all DC issued QR code feels unprecedented and honestly, unreal. Maybe a bit reckless as well, given we hadn’t tested our native system at all. I felt like Icarus, either flying into the sun or burning to the sea. We soar.&lt;/p&gt;&#10;&lt;h3 id="i-didnt-get-my-pass-what-should-i-do"&gt;I didn’t get my pass, what should I do?&lt;/h3&gt;&#10;&lt;p&gt;To everyone who didn’t get a pass, read this carefully. There are 10K members in the IITD community and the venue has a cap of 3000. Even if we were fully booked, 70% of people just won’t get a pass. That’s why there&amp;rsquo;s at most one criteria to ensure at least 60% of people get to experience at least one concert. This is not tech’s fault. That’s the sad reality of it.&lt;/p&gt;&#10;&lt;p&gt;If you have ever taken a computer networks course, you will understand that there are a number of random factors that affect how requests are ordered and go from client to server. The pass booking system is 100% fair in the sense that everyone has a fair chance of &lt;em&gt;requesting&lt;/em&gt; for a pass. Whether that request reaches the server depends on your local network topology, your average bandwidth, your local latency which means how many other clients are connected to your router and how many hops it takes from your router to reach the server&lt;sup&gt;2&lt;/sup&gt;. In that sense, the pass booking process is essentially a fair lottery for all which is how it should be. So, if you didn’t get a pass, tough luck.&lt;/p&gt;&#10;&lt;h2 id="day-3"&gt;Day 3&lt;/h2&gt;&#10;&lt;p&gt;The next day, the overall coordinator told me that the guy who had written bitter words about me had apologised. I forgave him.&lt;/p&gt;&#10;&lt;p&gt;While I was busy with these, another set of events were unfolding on Reddit. Apparently, people who didn’t get a pass were writing hate messages about me on the r/IITDelhi. I received many concealed hate messages IRL and some of my outside juniors tried to humiliate me by using under-handed scorns for invalidating the QRs because that’s what they saw on the outside. I smiled and shrugged it off. Even if I explained what we had prevented, they probably wouldn&amp;rsquo;t have the aptitude to understand.&lt;/p&gt;&#10;&lt;p&gt;My “co-CTM” who had effectively stopped working but had marketed himself as a RDV CTM started receiving a lot of flak on his social media from people not getting their passes. Karma, I guess.&lt;/p&gt;&#10;&lt;p&gt;The final day was a breeze. DC was gone for good. I was confident in my system. My team worked late to incorporate small cosmetic changes like color-coding passes for various categories as per feedback received from the on-ground team. These were good suggestions. In all this chaos, I missed experiencing the fest, of course. My last RDV as a student. I was only looking forward to the qawwali night which I couldn’t attend. Last year, Nizami Bandhu performed and it was surreal. It’s okay. Sacrifice is the cost of leading as I knew from Literati days as well.&lt;/p&gt;&#10;&lt;p&gt;My parents were in Delhi and we had a bus booking to go to Ajmer at 10pm on Day 3 (!) for a 5 day trip. I know. Crazy. Moreover, I had to buy a new phone before that. I landed up in Select Citywalk and bought a Nothing 3a from Croma. I also bought my new Mac from the adjacent Apple store since that was the last day for the offer (I got almost 50K worth of value from the offer!). I had tried to buy the laptop two days back but couldn’t due to credit limits. I think it was written that I would buy my first Mac with contributions from my parents and my father’s card. I must be one lucky guy.&lt;/p&gt;&#10;&lt;p&gt;I took my parents to the Day 3 concert. &lt;a href="https://www.instagram.com/ankittiwari/?hl=en" target="_blank" rel="noopener noreferrer"&gt;Ankit Tiwari&lt;/a&gt;&#10; was performing. My parents saw the crowd, saw the energy, saw the vibe and I told them “This is what I have been working for”. I forgot the fatigue, the stress, and the adrenaline wearing out. It was all worth it in that moment. I am super grateful I got my parents to experience an RDV concert.&lt;/p&gt;&#10;&lt;h3 id="some-numbers-because-an-engineers-life-is-incomplete-without-it"&gt;Some numbers (because an engineer’s life is incomplete without it)&lt;/h3&gt;&#10;&lt;p&gt;What did we deliver exactly? I looked at the Cloudflare numbers after the fest and I was shocked. We served 130K+ unique visitors and served around 13M+ requests. Entry scanning records show more than 10K visitors though this is a lower bound. We have 20K user registrations. All of this within the span of 60 days across 10+ services.&lt;/p&gt;&#10;&lt;h3 id="system-summary"&gt;System summary&lt;/h3&gt;&#10;&lt;p&gt;The following section is copied from my &lt;a href="https://www.linkedin.com/feed/update/urn:li:activity:7387794074851938304/" target="_blank" rel="noopener noreferrer"&gt;Linkedin post&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;p&gt;Few architecture designs:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Two tiered caching and rate limiting via Cloudflare and AWS. Google RECAPTCHA integration for anti-bot measures.&lt;/li&gt;&#10;&lt;li&gt;AWS CloudFront CDN for optimised website experience.&lt;/li&gt;&#10;&lt;li&gt;Core OAuth Service featuring multiple social media login options and integration with Kerberos, IITD&amp;rsquo;s internal auth system.&lt;/li&gt;&#10;&lt;li&gt;AWS WAFs, Load balancers, and EBS housed in their own VPC for scalable and secure web applications.&lt;/li&gt;&#10;&lt;li&gt;VLM on AWS Bedrock for automated task verification.&lt;/li&gt;&#10;&lt;li&gt;Event and Pronite passes scanned via in-house mobile app for real-time update on venue occupancy for crowd management.&lt;/li&gt;&#10;&lt;li&gt;Siloed distributed services to reduce blast radius.&lt;/li&gt;&#10;&lt;li&gt;Simulations run on Locust to estimate service performance under stress.&lt;/li&gt;&#10;&lt;li&gt;CloudPipeline integration for one push builds via Github CI/CD.&lt;/li&gt;&#10;&lt;li&gt;RDS Postgres (Database), S3 (Storage), SES (Emails), SNS (SMS), and SEO optimization using sitemaps and Google Rich Results.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="challenges-advice-and-lessons"&gt;Challenges, Advice, and Lessons&lt;/h2&gt;&#10;&lt;p&gt;If you have made it this far, thank you! What did we learn from all this? Here are my key takeaways for the future team:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Do NOT fuck around with tech - current system is designed taking into account years of learning, no “3rd party integration” unless every single line of their code is audited by the RDV tech team. Even then, just don’t.&lt;/li&gt;&#10;&lt;li&gt;Please give a budget to the tech vertical.&lt;/li&gt;&#10;&lt;li&gt;Make a capable Tech CTM near year. The system is more than capable to handle RDV workloads (tested for 2 years), it needs to be configured properly though.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="personal-challenges"&gt;Personal challenges&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Every vertical had two CTMs except tech. All workload on me.&lt;/li&gt;&#10;&lt;li&gt;Thankless working, less bonding with larger CTM team. Most of them were juniors I didn’t know. I knew only 2 or 3 (you know who you are :))&lt;/li&gt;&#10;&lt;li&gt;Extreme sacrifices made - I could have published papers in ML conferences, could have spent more time freelancing, could have prepped for placements, could have focused more on learning and upskilling but a word is a word and it must be kept even if it meant dying for it.&lt;/li&gt;&#10;&lt;li&gt;It is extremely hard to communicate tech stuff to non-tech people. These people will never understand what goes on to make things work - they just take things for granted (for instance, integrating the Gemini sponsor task which entailed opening a pop-up window but most users had pop-up blockers installed so it “didn’t work”)&lt;/li&gt;&#10;&lt;li&gt;I lost my RFID ID card. I had to register a complaint and pay a fine.&lt;/li&gt;&#10;&lt;li&gt;Potentially lost chance for a romance. There was this girl during RDV who texted. I couldn’t meet her due to my obligations. Anyways, she blocked me when I texted back after the fest so like whatever.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;blockquote class="pullquote"&gt;&#10; Resiliency and hard work can overcome all odds. And a good team. It is most crucial to have a good team. This was, in all respects, a trial by fire.&#10;&lt;/blockquote&gt;&#10;&#10;&lt;h2 id="footnotes"&gt;Footnotes&lt;/h2&gt;&#10;&lt;p&gt;1: I really want to emphasize “hard” here.&lt;/p&gt;&#10;&lt;p&gt;2: The quant-minded amongst you might think of reducing this latency by moving your request “closer to the source”. I think this won’t work since all requests are re-routed through a proxy service and shaving a few hops in the IITD network may not be enough to get that edge. But if someone wants to try this out, happy to help. I am actually curious to see someone pull this off and report actual numbers.&lt;/p&gt;&#10;</description></item><item><title>Implement a Minimax Agent for Optimal Tic Tac Toe Play</title><link>https://basillabib.com/post/implement-a-minimax-agent-for-optimal-tic-tac-toe-play/</link><pubDate>Wed, 22 Oct 2025 18:46:47 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/implement-a-minimax-agent-for-optimal-tic-tac-toe-play/</guid><description>&lt;!-- &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10;&#10;&#10;&#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10;&#10; &lt;div class="gist-snippet-container"&gt;&#10; &lt;div class="gist-header"&gt;tictactoe.py&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-py" data-lang="py"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Max depth explored: &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Nodes explored: &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodes_explored&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Total branches: &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_branches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Average branching factor: &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_branching_factor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;===== END AGENT COMPUTATION SUMMARY ====&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_branches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodes_explored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_node_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; Assumption, we are at max nodes on odd numbered levels&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; we are on min nodes on even numbered levels&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; Output: (val, pos)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;moves&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_available_moves&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# pdb.set_trace()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodes_explored&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evaluate_board_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# at the leaf, there are no valid moves as there are no children of leaf&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_branches&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;make_move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;O&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodes_explored&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_winner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;O&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evaluate_board_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute_node_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;undo_move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# odd valued levels are max levels &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# even valued levels are min levels&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate_board_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Game&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_winner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_winner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;O&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_draw&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_internalstate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;make_move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;game&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Game&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;iter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# play against human&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;can_move&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# assume human is always X&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# computer is always O&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Move number: &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;inp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Enter move&amp;gt; &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;make_move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Error: &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ve&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_internalstate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_winner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Congratulations, you won!&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_winner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Oops, better luck next time!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# time for computer to move...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute_move&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt; &lt;span class="c1"&gt;# no more available moves&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;make_move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;O&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_winner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Congratulations, you won!&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_winner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Oops, better luck next time!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_internalstate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;O&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;iter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_draw&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;It&amp;#39;s a draw!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;div class="gist-footer"&gt;&#10; &lt;a href="https://gist.github.com/bd389bddea4df1d7a46f6a1a264b0e5b"&gt;View full gist &amp;rarr;&lt;/a&gt;&#10; &lt;/div&gt;&#10; &lt;/div&gt;&#10;&#10; --&gt;&#10;&lt;blockquote class="pullquote"&gt;&#10; &lt;p&gt;I hear and I forget. I see and I remember. I do and I understand.&lt;/p&gt;&#10;&lt;p&gt;~Confucius&lt;/p&gt;&#10;&#10;&lt;/blockquote&gt;&#10;&#10;&lt;p&gt;&lt;span class="dropcap"&gt;&lt;span class="dropcap-initial"&gt;I&lt;/span&gt; implemented&lt;/span&gt; a (very) simple agent to optimally play the game of tic tac toe (or noughts and crosses). In this post, I will outline the algorithm I used, how I modelled the problem, and walk you through the code. Feel free to mess around with the code &lt;a href="https://gist.github.com/basil08/bd389bddea4df1d7a46f6a1a264b0e5b" target="_blank" rel="noopener noreferrer"&gt;here&lt;/a&gt;&#10;. It is surprising how the core search algorithm is just around 20 lines of code.&lt;/p&gt;&#10;&lt;h1 id="motivation"&gt;Motivation&lt;/h1&gt;&#10;&lt;p&gt;Well, I am self-learning the AI course offered by my university which is taught by an &lt;a href="https://www.cse.iitd.ac.in/~mausam//index.html" target="_blank" rel="noopener noreferrer"&gt;exceptional professor&lt;/a&gt;&#10;. It is rare that I find myself wanting to code every algorithm taught in class let alone show up to class. I guess I am just too choosy when it comes to learning&lt;sup&gt;1&lt;/sup&gt;. Anyhow, I decided to implement the minimax algorithm to solve tic-tac-toe because it seems fun, tractable, and a good way to brush up recursion programming. I spent around 1.5 hours coding and an hour debugging the whole thing (with some help from dear ChatGPT).&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-aaaebe57" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-aaaebe57" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/tictactoe/t3gametree.png" alt="A typical Tic Tac Toe game tree" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-aaaebe57" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/tictactoe/t3gametree.png" alt="A typical Tic Tac Toe game tree" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;A typical Tic Tac Toe game tree&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h1 id="tic-tac-toe-for-dummies"&gt;Tic Tac Toe for dummies&lt;/h1&gt;&#10;&lt;p&gt;This section is probably preposterous but for the sake of completeness, here is how tic-tac-toe is played. It is a 2-player turn based game where each player places their piece “X” or “O” on a 3x3 grid. The first player to make a horizontal, vertical, or diagonal combination wins. It is what is called a deterministic, two-player, zero sum, fully observable game in game theory parlance. These kinds of games can be optimally solved theoretically and can be solved in practice if you just throw enough compute at some naive algorithm. One such naive but foundational algorithm is minimax.&lt;/p&gt;&#10;&lt;h1 id="what-is-the-minimax-algorithm"&gt;What is the minimax algorithm?&lt;/h1&gt;&#10;&lt;p&gt;The core abstraction that we will work with is called a &lt;a href="https://en.wikipedia.org/wiki/Game_tree" target="_blank" rel="noopener noreferrer"&gt;game tree&lt;/a&gt;&#10;. It is an abstract object which formalizes all possible ways that a game can proceed. We represent game states as nodes of this tree and represent reachable game states as children based on some valid move. Of course, some states are better than others from the player’s perspective. How can we formalise this idea? We assign values to each node in the game tree representing how good it is to be in that state. This is convenient because it allows us to apply utility maximization to compute best moves. A little thought will convince you that if you can compute all possible ways a game can be played and pick the best moves based on utility maximization, you have indeed found the optimal game play. Nobody can beat you.&lt;/p&gt;&#10;&lt;p&gt;How do we compute these node values? Assume we are thinking from the computer’s perspective. We need to output a move from the available set of moves given the game state. If I give you the value of the game state &lt;em&gt;if you played&lt;/em&gt; one of the moves, you can tell me the best move (just pick the move with the max value). We recursively construct this tree implicitly in the code by counterfactually reasoning about “what if? scenarios” - “What if X played at position 3, then O can play at positions 5, 6, 7. But if it plays at 6 then X wins, so it must play at 5 to block X” and similar arguments.&lt;/p&gt;&#10;&lt;p&gt;This should give you an idea about the adversarial nature of minimax. The values for the computers are &lt;em&gt;exactly the opposite of the values&lt;/em&gt; for the human. If we looked at the game trees constructed by the two players from a given game state, they would be &lt;em&gt;exactly same&lt;/em&gt; except that all the values would be inversed. The minimax algorithm formalises this notion by maximizing values for its own actions and minimizing values for the opponent’s actions. We’ll see how it’s implemented in code later. The big assumption here is that your opponent is also a rational agent trying to maximise its own utility.&lt;/p&gt;&#10;&lt;h1 id="modelling-tictactoe-as-adversarial-search"&gt;Modelling TicTacToe as Adversarial search&lt;/h1&gt;&#10;&lt;p&gt;What do we really need to make this agent? Well, we need to represent the game state somehow. This is implemented as a simple 9 element list of strings in Python where “-” represents an empty cell.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;game: list[“X” | “O” | “-”]&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We need a function to decide whether we have a winner. This is implemented by the &lt;code&gt;get_winner(game)&lt;/code&gt; function which is really just a dumb bunch of if-elses due to the simple and small number of win conditions for TicTacToe.&lt;/p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10;&#10;&#10;&#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10;&#10; &lt;div class="gist-snippet-container"&gt;&#10; &lt;div class="gist-header"&gt;tictactoe.py&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-py" data-lang="py"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_winner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# verticals&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# horizontals&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# diagonals&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;div class="gist-footer"&gt;&#10; &lt;a href="https://gist.github.com/bd389bddea4df1d7a46f6a1a264b0e5b"&gt;View full gist &amp;rarr;&lt;/a&gt;&#10; &lt;/div&gt;&#10; &lt;/div&gt;&#10;&#10;&#10;&lt;p&gt;We need functions which can transition the game state, namely &lt;code&gt;game.make_move(position, piece)&lt;/code&gt; and its inverse &lt;code&gt;game.undo_move(position)&lt;/code&gt;. Simple and cute functions.&lt;/p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10;&#10;&#10;&#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10;&#10; &lt;div class="gist-snippet-container"&gt;&#10; &lt;div class="gist-header"&gt;tictactoe.py&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-py" data-lang="py"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Uh oh, cannot make move, cell already occupied!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;undo_move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;div class="gist-footer"&gt;&#10; &lt;a href="https://gist.github.com/bd389bddea4df1d7a46f6a1a264b0e5b"&gt;View full gist &amp;rarr;&lt;/a&gt;&#10; &lt;/div&gt;&#10; &lt;/div&gt;&#10;&#10;&#10;&lt;p&gt;We also need a function which will allow us to evaluate how good a board state is. How can we do this? Well, assume if there are no more moves left, what can happen now? It’s either a draw or we have a winner. Accordingly, we can return 0, 1 (if the computer wins) or -1 (if the human wins). That’s for the leaves, you say, but what about the non-leaf nodes? Well, we simply recurse on the children on the current node and return the best value (minimizing or maximising based on the level parity which represents whose turn it is).&lt;/p&gt;&#10;&lt;h1 id="implementation-details-and-caveats"&gt;Implementation details and caveats&lt;/h1&gt;&#10;&lt;p&gt;I’ll not go through every line of code but I’ll note some interesting caveats and bugs I came across while implementing this. There’s one obvious limitation in the code. It doesn’t let you choose your piece. The human is always “X” and always the first to move. Maybe one of you can raise a PR!&lt;/p&gt;&#10;&lt;p&gt;Initially, I was always checking for the termination condition for the recursion when there were no available moves left which meant at level 9 but I realised that games can also end at the higher level. That’s when I modified the agent’s &lt;code&gt;compute_move&lt;/code&gt; function to check for wins or losses just after it makes a counterfactual move. If it does, then we can just return and save ourselves from recursing!&lt;/p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10;&#10;&#10;&#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10;&#10; &lt;div class="gist-snippet-container"&gt;&#10; &lt;div class="gist-header"&gt;tictactoe.py&lt;/div&gt;&#10; &lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-py" data-lang="py"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_node_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; Assumption, we are at max nodes on odd numbered levels&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; we are on min nodes on even numbered levels&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; Output: (val, pos)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;moves&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_available_moves&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# pdb.set_trace()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodes_explored&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evaluate_board_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# at the leaf, there are no valid moves as there are no children of leaf&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_branches&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;make_move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;O&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodes_explored&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_winner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;X&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;O&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evaluate_board_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compute_node_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agentgame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;undo_move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# odd valued levels are max levels &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# even valued levels are min levels&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10; &lt;div class="gist-footer"&gt;&#10; &lt;a href="https://gist.github.com/bd389bddea4df1d7a46f6a1a264b0e5b"&gt;View full gist &amp;rarr;&lt;/a&gt;&#10; &lt;/div&gt;&#10; &lt;/div&gt;&#10;&#10;&#10;&lt;p&gt;One more subtle bug which ChatGPT helped me identify was the value function was computing the values and moves of the current node but was somehow returning the moves of the children. Well, you can never be too careful in recursion hell.&lt;/p&gt;&#10;&lt;p&gt;Even after reading the code again and again, I still found that the agent was playing stupidly. I suspected that this was due to my stupidity (!) which indeed, it was. I was calling the agent at each game state with an incremental level value which essentially turned it into its own adversary and it was flipping between it’s own friend and foe. LOL.&lt;/p&gt;&#10;&lt;h1 id="results-and-demo-run"&gt;Results and demo run&lt;/h1&gt;&#10;&lt;p&gt;I also felt like collecting some statistics to see how large the search trees were. On my laptop, search was instantaneous and the maximum nodes explored were around 60K at the start of the game. The search tree drastically reduces with each level as the number of valid moves decreases.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;==== AGENT COMPUTATION SUMMARY ====&#10;Making move 0 with value 0&#10;Max depth explored: 9&#10;Nodes explored: 60112&#10;Total branches: 55504&#10;Average branching factor: 0.9233430928932659&#10;===== END AGENT COMPUTATION SUMMARY ====&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here’s a simplified game run. I chose this because the computer actually beat me as I wasn’t paying attention and it forced me into a game state to guarantee its win. Bewildering stuff!&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;&#10;❯ python tictactoe.py&#10;Move number: 1&#10;- - -&#10;- - -&#10;- - -&#10;Enter move&amp;gt; 1&#10;==== AGENT COMPUTATION SUMMARY ====&#10;Making move 0 with value 0&#10;Max depth explored: 9&#10;Nodes explored: 69088&#10;Total branches: 63904&#10;Average branching factor: 0.9249652616952293&#10;===== END AGENT COMPUTATION SUMMARY ====&#10;Move number: 2&#10;O X -&#10;- - -&#10;- - -&#10;Enter move&amp;gt; 2&#10;==== AGENT COMPUTATION SUMMARY ====&#10;Making move 3 with value 1&#10;Max depth explored: 7&#10;Nodes explored: 1336&#10;Total branches: 1228&#10;Average branching factor: 0.9191616766467066&#10;===== END AGENT COMPUTATION SUMMARY ====&#10;Move number: 3&#10;O X X&#10;O - -&#10;- - -&#10;Enter move&amp;gt; 6&#10;==== AGENT COMPUTATION SUMMARY ====&#10;Making move 4 with value 1&#10;Max depth explored: 7&#10;Nodes explored: 44&#10;Total branches: 44&#10;Average branching factor: 1.0&#10;===== END AGENT COMPUTATION SUMMARY ====&#10;Move number: 4&#10;O X X&#10;O O -&#10;X - -&#10;Enter move&amp;gt; 5&#10;==== AGENT COMPUTATION SUMMARY ====&#10;Making move 8 with value 1&#10;Max depth explored: 7&#10;Nodes explored: 3&#10;Total branches: 3&#10;Average branching factor: 1.0&#10;===== END AGENT COMPUTATION SUMMARY ====&#10;Oops, better luck next time!&#10;O X X&#10;O O X&#10;X - O&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="further-improvements"&gt;Further improvements&lt;/h1&gt;&#10;&lt;p&gt;There are &lt;em&gt;so many&lt;/em&gt; improvements you can try out. TicTacToe with minimax is like the baseline launchpad to explore adversarial games.&lt;/p&gt;&#10;&lt;p&gt;Alpha-beta pruning is an obvious optimization although its advantage cannot be felt with such a small game tree (relative to laptop performance).&lt;/p&gt;&#10;&lt;p&gt;You can try to look at stochastic games and implement expectiminimax. Or try one of the many &lt;a href="https://en.wikipedia.org/wiki/Tic-tac-toe_variants" target="_blank" rel="noopener noreferrer"&gt;Tic Tac Toe variants&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;p&gt;If you’re feeling ambitious, you can model a completely different game like Poker or Backgammon and implement your own agent.&lt;/p&gt;&#10;&lt;h1 id="references"&gt;References&lt;/h1&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a href="https://www.cse.iitd.ac.in/~mausam/courses/col333/autumn2025/" target="_blank" rel="noopener noreferrer"&gt;COL333 course Prof Mausam IIT Delhi&lt;/a&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;AIMA Russell et. al. 4th edition Chapter 6 (Adversarial Search)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h1 id="footnotes"&gt;Footnotes:&lt;/h1&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Which is a good thing! To actively resist spending time on useless learning is an optimal strategy to acquire most useful knowledge.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Implementing Kerberos Authentication System from Scratch</title><link>https://basillabib.com/post/implementing-kerberos-authentication-system-from-scratch/</link><pubDate>Sun, 29 Sep 2024 06:58:24 +0100</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/implementing-kerberos-authentication-system-from-scratch/</guid><description>&lt;h3 id="introduction"&gt;Introduction&lt;/h3&gt;&#10;&lt;p&gt;Sometime back, I started exploring network security systems and stumbled across &lt;a href="https://www.cse.iitd.ernet.in/%7Esiy107537/sil765/readings.html" target="_blank" rel="noopener noreferrer"&gt;this nice course page&lt;/a&gt;&#10;&lt;sup&gt;1&lt;/sup&gt; from my institute. I thought it would be a nice complement to the introductory computer networks course I am taking this semester. I started reading this fascinating &lt;a href="https://web.mit.edu/Kerberos/dialogue.html" target="_blank" rel="noopener noreferrer"&gt;short design principles article&lt;/a&gt;&#10; on the Kerberos system. It was so encapsulating that I spent an entire afternoon drawing pictures in my notebook. I decided to implement the ideas given in the article from scratch. In other words, I decided to build my own Kerberos authentication system.&lt;/p&gt;&#10;&lt;p&gt;This blog post outlines the process and the reasoning. We’ll build the system from the ground up, that is, we will propose a system, then point out its flaws, and try to redesign the system to overcome those faults. Along the way, we’ll meet a lot of ideas and end up with a much deeper understanding of &lt;em&gt;why&lt;/em&gt; Kerberos is designed the way it is.&lt;sup&gt;2&lt;/sup&gt; (The &lt;a href="https://github.com/basil08/smolKerberos" target="_blank" rel="noopener noreferrer"&gt;source code&lt;/a&gt;&#10; is available on Github).&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-e2e8e683" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-e2e8e683" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/kerberos/4.png" alt="Authentication model" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-e2e8e683" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/kerberos/4.png" alt="Authentication model" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Authentication model&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h3 id="motivation"&gt;Motivation&lt;/h3&gt;&#10;&lt;p&gt;As we live in a world with limited resources, we need to distribute resources fairly among users. Irrespective of the distribution policy, it is imperative to know who is requesting a resource, that is, we need to confidently say that any resource request supposed to come from a user is actually coming from them. Symmetrically, we need to ensure that the resource/service that the user believes it is requesting is really the resource/service.&lt;sup&gt;3&lt;/sup&gt;&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Question: How do we authenticate that an entity is really the entity it claims to be?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;At the heart of any authentication process is a simple idea of shared secrets. Entity A can authenticate entity B if they share a secret no one knows about. For entity A to verify that an entity is B, A only needs to ask for the secret from that entity. If the set of secrets is sufficiently large, it would be virtually impossible for a rogue entity to impersonate B. This mechanism can be even more foolproof if the secret is changed &lt;em&gt;at every authentication&lt;/em&gt;.&lt;sup&gt;4&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;Kerberos implements this abstraction for hosts connected through an unreliable network. In an unreliable network, we assume the existence of malicious actors who may observe and capture any data packets shared between hosts.&lt;/p&gt;&#10;&lt;p&gt;But before we begin building Kerberos itself, we need to build the network model and define few other primitives.&lt;/p&gt;&#10;&lt;h3 id="the-user-aka-the-client"&gt;The user AKA the client&lt;/h3&gt;&#10;&lt;p&gt;The user interacts with the system through a client program. The client abstracts the details of building data packets, sending/receiving packets from other servers, and parsing those packets while gracefully (well, relatively) handling any errors. The client is implemented in the &lt;code&gt;client.py&lt;/code&gt; module.&lt;/p&gt;&#10;&lt;h3 id="the-resource-server"&gt;The resource server&lt;/h3&gt;&#10;&lt;p&gt;I have implemented a &lt;code&gt;ResourceServer&lt;/code&gt; class which represents any resource / service that a user is interested to get / use. Our holy grail for this post will be to get this resource from the resource server as a user.&lt;/p&gt;&#10;&lt;h3 id="cryptography"&gt;Cryptography&lt;/h3&gt;&#10;&lt;p&gt;Kerberos utilises a symmetric key cryptography scheme (at least in this formulation). What this means is that any encryption/decryption happens iff the key is known to both parties &lt;em&gt;beforehand&lt;/em&gt;. How is the key mutually agreed upon between the two parties? That’s a question we will repeatedly visit through this post.&lt;/p&gt;&#10;&lt;p&gt;Initially, I thought I could use &lt;a href="https://en.wikipedia.org/wiki/XOR_cipher" target="_blank" rel="noopener noreferrer"&gt;XOR encryption&lt;/a&gt;&#10; for the sake of this toy application. As it turned out, I spent much of my time debugging annoying unicode boundary errors. Worried that I wasn’t building the actual system, I decided to go old-school. And what’s more old-school than good old Caesar cipher? One funny thing about this algorithm is that it’s laughable and easy to break. If you stare long enough at the ciphertext, you can probably guess what the cleartext might be. Be as it may, it serves our purpose well, so we’ll roll with it. I present the pseudo-code for the encryption and decryption routines below. These can be found in the &lt;code&gt;/caesar.py&lt;/code&gt; module.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;encryption(clear_text, key):&#10; for each character in clear_text&#10; find the character in the position [idx(character) + key] % character_set_size&#10; add this character to the cipher_text string&#10; return cipher_text string&#10;&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;decryption(cipher_text, key):&#10; for each character in cipher_text&#10; find the character in the position [idx(character) - key] % character_set_size&#10; add this character to the clear_text string&#10; return clear_text string&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Caeser cipher has this nice symmetry property, as you can see. It can easily be broken using classic cryptanalysis techniques like naive brute-forcing.&lt;sup&gt;5&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;Enough of this now. Let’s get to the good part.&lt;/p&gt;&#10;&lt;h2 id="version-1---the-first-attempt"&gt;Version 1 - The first attempt&lt;/h2&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;What is the most basic secret sharing that you can think of?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;We can imagine each resource server maintaining a table of tuples (username, password). If I wish to access a resource, I simply present my password, which the resource server authenticates by looking up the table and matching it. This system works but as you can see, it’s dumb. It doesn’t scale well. If there are 1000 users and 100 resources, we already have 100000 records maintained on the network. Any (user,password) update has to be propagated to &lt;em&gt;every&lt;/em&gt; resource server. Moreover, this deeply couples authentication and the actual resource sharing processes. We would ideally want to decouple these two steps so that the resource server is only concerned with serving up the resources. That’s good software engineering!&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-26183fd0" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-26183fd0" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/kerberos/3.png" alt="Naive authentication system" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-26183fd0" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/kerberos/3.png" alt="Naive authentication system" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Naive authentication system&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Question: Can we do better?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Yes! Let’s create a dedicated authentication_server which maintains a single (username, password) table. Moreover, it also stores a (servername, password) table. For a client, it is sufficient to authenticate to this auth server. Why? Because this auth server issues an auth token - a “proof of authentication”. How does it work? The auth server first lookups the password for the user in its user table and once its satisfied, it created a data packet containing the username (and some other metadata like a network address) and encrypts it &lt;em&gt;using the servername password&lt;/em&gt;. The client takes this auth token and presents it along with its username and network address to the resource server. The resource server attempts to decrypt the auth token and match the username and network address.&lt;/p&gt;&#10;&lt;p&gt;What exactly happened here? It is important to note that we established &lt;em&gt;two things&lt;/em&gt; through this process.&#10;When the resource server was able to successfully decrypt the auth token, it was sure that the auth token was issued by the authentication server. Why? Shared secrets! The server password is only known to the auth server and the resource server!&lt;sup&gt;6&lt;/sup&gt;&#10;Matching the packet username and other data with the decrypted data. This ensures that the request was made by the same entity that authenticated itself to the auth server and so, must be one who should access to the resource.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-92c55b23" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-92c55b23" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/kerberos/2.png" alt="version 1" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-92c55b23" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/kerberos/2.png" alt="version 1" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;version 1&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Question: What are the flaws in this system?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Well, no one is stopping me from capturing these tokens and making a request to the resource server posing as someone else on the network. This is somewhat alleviated by including metadata like network addresses assumed un-editable by system users.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Even more concerning is that the client still has to send the password &lt;em&gt;over the network&lt;/em&gt; in cleartext! If a malicious agent can capture the user password then its pretty much game over since this is the only piece of information that the auth server uses to authenticate the user.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Moreover, if a client wishes to access another resource, they must authenticate themselves with the auth server &lt;em&gt;again&lt;/em&gt;. We would ideally like the “I am a person who has access to XYZ resources, here is the proof” step to be decoupled from “Hey, can I get resource XYZ?” step.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Question: Can you consider modifying the current system to address these issues?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="version-2---streamlining-and-decoupling"&gt;Version 2 - Streamlining and Decoupling&lt;/h2&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-30cc0936" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-30cc0936" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/kerberos/1.png" alt="version 2" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-30cc0936" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/kerberos/1.png" alt="version 2" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;version 2&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Let’s address point 2 first. The client doesn’t send the password along with the username while authenticating with the auth server. Remember that the authentication system is symmetric with respect to the parties, which means it can happen on the client as well. So, we somehow need to make the server send the user password without sending the user password. How is it possible? Well, the auth server just sends back the auth token encrypted &lt;em&gt;with the user password&lt;/em&gt;. The client attempts to decrypt the packet (and is successful if its a legit user). No one but the user will be able to decrypt the packet and get the auth token.&lt;/p&gt;&#10;&lt;p&gt;To address point 3, we will perform another decoupling. We will have two servers - an AuthenticationTokenGrantor server and a ServiceTokenGrantor server, both of which will maintain their own copies of user and service tables. Moreover, we now make these tokens expirable by attaching a &lt;code&gt;time_of_creation&lt;/code&gt; and a &lt;code&gt;lifespan&lt;/code&gt; to each token.&lt;/p&gt;&#10;&lt;p&gt;The ATG server only issues &lt;code&gt;auth_token&lt;/code&gt; which are valid for a certain duration. The STG server issues token specific for any resource server on the network if it gets a valid auth token. As you can see, the user only has to authenticate itself to the ATG server once every reasonable interval of time (say, 8 hours). It can always use the &lt;code&gt;auth_token&lt;/code&gt; to get &lt;code&gt;service_token&lt;/code&gt; from STG and use the &lt;code&gt;service_token&lt;/code&gt; to authenticate itself to the resource server.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Question: What are the flaws with this system?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Actually, there is no guarantee that the request to the resource server that contains the &lt;code&gt;service_token&lt;/code&gt; &lt;em&gt;actually&lt;/em&gt; comes from the user to which the token was issued. Why? Because there is no shared secret between the ATG and the STG.&lt;/li&gt;&#10;&lt;li&gt;It is possible for a malicious actor to gain access to the &lt;code&gt;service_tokens&lt;/code&gt; and use them to exploit the resource server as long as the tokens have not expired. This is called a &lt;a href="https://en.wikipedia.org/wiki/Replay_attack" target="_blank" rel="noopener noreferrer"&gt;“replay attack”&lt;/a&gt;&#10;.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Let’s fix these now.&lt;/p&gt;&#10;&lt;h2 id="version-3---session-keys-and-authenticators"&gt;Version 3 - Session Keys and Authenticators&lt;/h2&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-430eec00" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-430eec00" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/kerberos/0.png" alt="version 3" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-430eec00" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/kerberos/0.png" alt="version 3" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;version 3&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Question: What kind of secret can the ATG and the STG share? And more importantly, how?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;The way this works is by the use of finite-lived session keys. The steps are as follows:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;The client authenticates itself to the ATG and gets the &lt;code&gt;auth token&lt;/code&gt;. The ATG generates an &lt;code&gt;auth_session_key&lt;/code&gt; and includes it in the &lt;code&gt;auth_token&lt;/code&gt; (This is the STG’s copy of the session key). Moreover, includes this session key with the packet it sends to the client which is encrypts with the user password first, of course.&lt;/li&gt;&#10;&lt;li&gt;The client gets the session key and uses it to encrypt another object called an “authenticator” which is basically another piece of info containing the client username and network address. It encrypts this with the session key it just got.&lt;/li&gt;&#10;&lt;li&gt;The client sends both the &lt;code&gt;auth_token&lt;/code&gt; and the &lt;code&gt;authenticator&lt;/code&gt; to the STG. The STG decrypts the &lt;code&gt;auth_token&lt;/code&gt; and uses the newly acquired session key to decrypt the &lt;code&gt;authenticator.&lt;/code&gt; It then matches the username that came with the packet to the username in the token AND the username in the authenticator. It then issues the &lt;code&gt;service_token&lt;/code&gt; to the client. The STG also generates a service-specific session key and includes it in the &lt;code&gt;service_token&lt;/code&gt; and the data packet it sends to the client but encrypts it with the &lt;code&gt;auth_session_key&lt;/code&gt; before.&lt;/li&gt;&#10;&lt;li&gt;The client decrypts the packet, gets the service session key and uses it to build another authenticator for the resource server.&lt;/li&gt;&#10;&lt;li&gt;The resource server performs checks analogous to the STG and finally serves the resource to the client.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Question: What did we achieve here?&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Well, the authenticator implements the proof that the bearer of the token is &lt;em&gt;really&lt;/em&gt; the entity it claims to be. Can you find the shared secrets in this arrangement? Yes, it’s the session keys!&lt;/p&gt;&#10;&lt;p&gt;Although I haven’t implemented this, the authenticator objects also have a lifespan, usually around 1-2 minutes. Thus, it becomes very hard for a malicious actor to replay these attacks.&lt;/p&gt;&#10;&lt;p&gt;Elegant, isn’t it? ^_^&lt;/p&gt;&#10;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p&gt;If you have made it this far, then I must congratulate you! This wasn’t a trivial post by any means. I have left a lot of the details out of this post for the sake of brevity. If you’re interested, feel free to browse the code and read the article which inspired this post.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;All diagrams in this post were produced by the author. Kindly &lt;a href="mailto:gs454236@gmail.com"&gt;write to me&lt;/a&gt;&#10; if you wish to use them :)&lt;/em&gt;&lt;/p&gt;&#10;&lt;h2 id="ideas-for-improvement"&gt;Ideas for improvement&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Make the authenticators finite-lived. This is straightforward to implement and will be analogous to how expiration is implemented for the tokens.&lt;/li&gt;&#10;&lt;li&gt;Implement token refreshing. I think this will be a nice add-on to have if this system should ever be put into production.&lt;/li&gt;&#10;&lt;li&gt;Abstract the client steps. Right now, the communication with ATG (command &lt;code&gt;gat&lt;/code&gt;), STG (command ‘gst’), and the resource server (command &lt;code&gt;get&lt;/code&gt;) happens separately and explicitly. Ideally, a nice client would hide these away from the user.&lt;/li&gt;&#10;&lt;li&gt;Extend the client to manage tokens and sessions for more than one resource. This demo only focuses on one resource on the network model. We would ideally want more than one resource on the server!&lt;/li&gt;&#10;&lt;li&gt;(Advanced) Implement an access control layer on top of this Kerberos. I imagine this would involve creating some role abstraction and storing it in the central database for each user and service. Using this abstraction, the STG may deny issuing &lt;code&gt;service_token&lt;/code&gt; to the client.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="references"&gt;References&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a href="https://web.mit.edu/Kerberos/dialogue.html" target="_blank" rel="noopener noreferrer"&gt;Designing an Authentication System: A dialogue in four scenes&lt;/a&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="footnotes"&gt;Footnotes&lt;/h3&gt;&#10;&lt;p&gt;1: Please excuse me if you cannot access the webpage. It’s on IITD’s internal network so it might be accessible outside the network.&lt;/p&gt;&#10;&lt;p&gt;2: An additional impetus for me to understand Kerberos is because it is the foundational authentication system used by my institute for authentication and identification.&lt;/p&gt;&#10;&lt;p&gt;3: This is called “mutual authentication” and it is generally considered a useful property for general-purpose authentication systems.&lt;/p&gt;&#10;&lt;p&gt;4: This closely relates to the idea of a one-time pad which is provably unbreakable. If you’re interested, please check any standard cryptography textbook.&lt;/p&gt;&#10;&lt;p&gt;5: Highly recommend reading “The Code Book” by Simon Singh. Highly accessibly and fun read. Or you may also try “Gold Bug” by E. A. Poe.&lt;/p&gt;&#10;&lt;p&gt;6: If you’re careful, you may ask but how do we know if the decryption was successful? For this reason, the data packet usually also contains the network identifier of the resource server. So, if the resource server finds its name in the packet, it can be sure that the decryption was successful.&lt;/p&gt;&#10;</description></item><item><title>Hacking Wordle Blitz</title><link>https://basillabib.com/post/hacking-wordle-blitz/</link><pubDate>Tue, 19 Dec 2023 04:36:28 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/hacking-wordle-blitz/</guid><description>&lt;p&gt;Apologies for not blogging in &lt;em&gt;like forever&lt;/em&gt;. Funny things have been happening in my life lately, perhaps the most funny is the concentration of all trips and &amp;ldquo;vacations&amp;rdquo; in my end-semester holidays. You might call that lucky but I quickly learnt that vacations are supposed to be evenly distributed, atleast from the actually-destressing-and-not-getting-tired-as-hell-from-travelling-and-not-being-able-to-catch-a-breath perspective.&lt;/p&gt;&#10;&lt;p&gt;During my entire end-sem holidays, my itinerary allowed me 5 days of staying at home. &lt;em&gt;Just 5&lt;/em&gt;. Before you hasten to the conclusion that 5 days is a lot of time to chill and relax, you must understand that there are relatives and friends to meet to maintain &amp;ldquo;diplomatic relations&amp;rdquo;. There goes my chill and relax.&lt;/p&gt;&#10;&lt;p&gt;Anyways, I&amp;rsquo;m not pissed. This information is important to put what follows in context (And also, to rant a bit :P)&lt;/p&gt;&#10;&lt;p&gt;It cannot be any worse. Right? RIGHT? Guess what, one of my good friends who is the secretary of the Literary club at my college approached me with the request to build a competition portal for a blitz Wordle game as soon as I landed at home. HaHa.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;I accepted.&lt;/em&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;This post chronicles how I hacked together the above portal in 2 days (and with a lot of procrastination).&lt;/p&gt;&#10;&lt;h2 id="baby-steps"&gt;Baby steps&lt;/h2&gt;&#10;&lt;p&gt;First, I had to find an open-source codebase to start with. Given that the competition was in one week, it would be stupid to attempt to make a Wordle clone from scratch. Luckily, I found &lt;a href="https://github.com/cwackerfuss/react-wordle" target="_blank" rel="noopener noreferrer"&gt;this&lt;/a&gt;&#10; cute and very nicely structured codebase on Github which I forked.&lt;/p&gt;&#10;&lt;p&gt;I spent sometime studying and mapping out functionalities, state changes, and components. Steadily, I started commenting out components and looking at the results (to see what breaks). All good, which means I am beginning to understand how the code is wired. Simultaneously, I started designing a flow for the app mentally which would be the path of least resistance with respect to debugging (as any respectable developer will tell you, no such path exists).&lt;/p&gt;&#10;&lt;p&gt;It&amp;rsquo;s a good idea to talk about the structure of this specific competition&lt;sup&gt;2&lt;/sup&gt;. It was supposed to be a timed 30 minutes non-stop contest where the puzzle resets each time the current one is solved or all guesses are exhausted. Words would be non-skippable and picked at random from a list of words for each users so that no word list for two users are same.&lt;/p&gt;&#10;&lt;p&gt;At first, I thought a very basic token system would do. Just map each registered email against a token and send the token as a query param to some kind of &lt;code&gt;/saveRoundData&lt;/code&gt; endpoint on the backend to authorise user. I dismissed this idea quickly due to the possibility of token exchange or other funny things between users or just accidental tampering with the token. There has to be a backend service since the game data would have to be persisted. And there has to be a proper user authorisation with a &lt;code&gt;User&lt;/code&gt; model. and what&amp;rsquo;s a better method to authenticate than my handy JWTs. Also, I have experience with them, no nasty database calls, clean, and simple.&lt;sup&gt;3&lt;/sup&gt; &lt;em&gt;Sigh, did I just make my life more difficult for an informal event that doesn&amp;rsquo;t even hold that much value?&lt;/em&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="its-usestate-all-the-way-down"&gt;It&amp;rsquo;s &lt;code&gt;useState()&lt;/code&gt; all the way down&lt;/h2&gt;&#10;&lt;p&gt;That&amp;rsquo;s when I decided to put a brake on the user authentication subsystem and work on the actual game flow itself. There were a bunch of state management to hack into and to ensure that the flow was as per the expected business need. Besides, completing this part would just make me happy and motivated to work more.&lt;/p&gt;&#10;&lt;p&gt;The code base was in typescript and &lt;a href="https://open.spotify.com/track/1Umw3vhysHBodpBFpFsVgK" target="_blank" rel="noopener noreferrer"&gt;God only knows&lt;/a&gt;&#10; why tsc doesn’t like me at all. Made a few edits and a stream of errors pop up. Somehow bash those and new ones pop up. Anyhow, braved my way through this typescript mania to arrive at a somewhat stable (but still buggy) front end. In hindsight, I think typescript is a million types better than javascript and I strongly recommend it over vanilla JS for large projects. Datatype information can save tons of development and debugging time.&lt;/p&gt;&#10;&lt;p&gt;Right now, I have the frontend without any backend service feeding it. I move on to the next word on win or lose and words are chosen at random. Moreover, I have made a &lt;code&gt;roundData&lt;/code&gt; object that captures metadata about each round’s progress and saves it to localStorage. Neat!&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-9702788d" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-9702788d" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/hacking-wordle-blitz/0.png" alt="GameData schema" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-9702788d" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/hacking-wordle-blitz/0.png" alt="GameData schema" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;GameData schema&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;I had originally intended the app to make a server call on &lt;em&gt;every&lt;/em&gt; round clearance for each user but then decided against it simply because I would be hosting the app on a free tier vercel instance and I have issues with latency on it. 200 odd disgruntled users would resend the request and soon it will be helltown for my free instance. Although, I think it would still have been able to withstand the anticipated load, I was too lazy to do the math and took the conservative path of &lt;em&gt;saving the roundData on the client side and sending one final request to log the entire array at the end of the countdown&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;Now, some of you might have raised your eyebrows high and said &lt;em&gt;“Waaaaiiittt a minute”&lt;/em&gt; and I totally get you. I was counting on the fact that no one would be able to open the network and storage tabs, figure this out, and then manually change the array by hand in 30 minutes. The portal link was anyways private and only accessible at the start of the event so I thought this was a reasonable assumption to make.&lt;sup&gt;4&lt;/sup&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="passportauthenticate"&gt;&lt;code&gt;passport.authenticate()&lt;/code&gt;&lt;/h2&gt;&#10;&lt;p&gt;I started fleshing out the auth API using this &lt;a href="https://www.zacfukuda.com/blog/passport-jwt-react" target="_blank" rel="noopener noreferrer"&gt;nice blog post&lt;/a&gt;&#10; as a reference. It was coming along fine other than a few minor hiccups around Headers and Param types which I identified and resolved. The VS Code Postman extension is such a blessing.&lt;/p&gt;&#10;&lt;p&gt;Then, I bumped into a 400 Bad Request on hitting &lt;code&gt;/login&lt;/code&gt; with valid credentials. Huh? WHERE IS MY JWT?! I checked and rechecked and banged my head against the wall for a good hour trying different ways to get under the hood of what’s going on. After accepting defeat, I headed over to Stackoverflow and humbly typed my query “passport.authenticate returns bad request” and &lt;a href="https://github.com/jaredhanson/passport/issues/371" target="_blank" rel="noopener noreferrer"&gt;turns out&lt;/a&gt;&#10; passport has been a bad boy lately. After scrolling to the end of the thread, I realised maybe I’m messing up the field names (Oh, wow what a revelation) and realised that perhaps passport is opinionated about what goes into its strategy callback function. Fair enough. Problem resolved. For the time being anyways.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="asking-the-right-questions-ft-chatgpt"&gt;Asking the right questions ft. ChatGPT&lt;/h2&gt;&#10;&lt;p&gt;In my current flow, the countdown is an important component to ensure fairness and a useable app so I set out to refine it. The &lt;a href="https://www.npmjs.com/package/react-countdown" target="_blank" rel="noopener noreferrer"&gt;&lt;code&gt;react-countdown&lt;/code&gt;&lt;/a&gt;&#10; component turned out to be horrible in being unable to retain state across page refreshes and I thought that’s probably okay if I assume people won’t refresh their page during the game which is another sane assumption to make. No JK. But then I noticed timer reset on every keystroke. Bruh.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;yarn remove react-countdown&lt;/code&gt; after spending another half an hour pouring over countdown’s documentation. How do I implement a simple countdown that retains state across page refresh? This seemed like a common and solved problem.&lt;/p&gt;&#10;&lt;p&gt;So, I asked ChatGPT to generate the code for me. And I must say, it did a pretty good job. Updating the timer state in a &lt;code&gt;useEffect&lt;/code&gt; block using a pair of &lt;code&gt;setTimeout&lt;/code&gt; and &lt;code&gt;clearTimeout&lt;/code&gt; and storing timer to &lt;code&gt;localStorage&lt;/code&gt;. One fine point though, the initial value of timer was also supposed to be set by first trying to read &lt;code&gt;localStorage&lt;/code&gt; and find a stale value which was done conveniently in a callback to &lt;code&gt;useState&lt;/code&gt;. Neat again!&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;At this point, I have pushed my repos to GitHub and setup vercel projects. I could see the project coming to an end and I was elated. Let me just go through the flow one more time, I thought. I generated a new user, took the credentials and tried to login. Worked like a charm! I cleared the token and tried to login with an incorrect password and it…worked like a charm!&lt;/p&gt;&#10;&lt;p&gt;Sigh. Back to the drawing board. Bunch of console.logs in and I still couldn’t figure out what’s wrong. This was, by far, the hardest bug to find because I trusted the code from that blogpost I linked earlier. The problem was in the password hash matching middleware that I copied verbatim from the website. I logged the hashes of the input password and the user password (which was saved to the database) and they turned out to be different. All good up to this point. Then I realised that upon failure of &lt;code&gt;bcrpyt.compare&lt;/code&gt;, the Promise has to be rejected. This conditional was missing. I added that and bug resolved. Two lines of code.&lt;/p&gt;&#10;&lt;p&gt;This bug made me realised that there are probably many more such edge case bugs lying undetected just because I didn’t write tests. I sighed. This was enough. I’m done. The user flow works properly. Authorisation works properly and I’m getting my data logged at the end of the timer which is pretty much fool-proof. I should call it a day (or night, I dont remember).&lt;/p&gt;&#10;&lt;p&gt;I pushed to prod, dropped a message and went to sleep. I still had to create those users and send them their credentials. And I had to pack for my flight to Heathrow. Oh well.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;Generating user password was a function I copied from Stackoverflow. A 6-character alphanumeric was sufficient for the desired security level. I whipped up a utility &lt;code&gt;‘/signupmany’&lt;/code&gt; endpoint on the server and fed it a &lt;code&gt;JSON.stringify&lt;/code&gt;-ied list of users. 200 OK.&lt;/p&gt;&#10;&lt;p&gt;Made few changes to a python mailing script lying around and added my spare account app password. Drafted a beautiful email (Ugh, shouldn’t &lt;em&gt;this&lt;/em&gt; be done by the secretary. I thought it’s better to avoid delays anyway) Tested. Got the email. Viola.&lt;/p&gt;&#10;&lt;p&gt;Ran a few more tests, added a bunch of &lt;code&gt;try/except&lt;/code&gt; blocks (not too many though) and debug statements. Loaded the &lt;code&gt;users.json&lt;/code&gt; and ran the script. 227 people were spammed by Purple Santa (my alt account name ^_^)&lt;/p&gt;&#10;&lt;p&gt;I shut my laptop and started packing. My name would either go down in history in golden letters or people will curse my name for ages. Moreover, I might have put my friendship in jeopardy. Moreover, I wouldn’t know what happened until 8pm GMT which is around 4 hours after the competition in India. Sometimes, my life decisions surprises even me.&lt;sup&gt;5&lt;/sup&gt;&lt;/p&gt;&#10;&lt;p&gt;I looked back at the code and thought of some obvious edits to make the user experience better but I let it go. Those state change loaders would have been nice. And perhaps a voluntary quit button for people stuck in the middle of the game and having better things to do.&lt;/p&gt;&#10;&lt;p&gt;EDIT: The app turned out to be a success. Hurray! As I connected to the Heathrow WiFi, a stream of notifications and congratulations started pouring in. I was elated. People had used my code and they had loved it. I was fascinated to realise that the line between those messages being of curses and boos was ice thin. It was a bunch of try/catchs at the end of the day that saved me. That’s probably the biggest lesson of this fascinating experience. Who says that one can’t have an adventure sitting in front of a keyboard?&lt;/p&gt;&#10;&lt;p&gt;Oh, what a lovely day! The world is saved yet again by untested code pushed to production by a wilful developer who believes in God.&lt;/p&gt;&#10;&lt;p&gt;PS: I logged into the database and realised a mismatch between the number of gameEntry objects and gameData objects created. Hmmmm. So, just to be safe, I noted the &lt;code&gt;startTimestamp&lt;/code&gt; of each user id upon first login to serve as the baseline in calculating the time deltas for each round completion later. (I have deliberately tried to keep this post jargon free but if you wish to look at the schema or the code, here they are: &lt;a href="https://github.com/basil08/wordle-blitz" target="_blank" rel="noopener noreferrer"&gt;frontend&lt;/a&gt;&#10; and &lt;a href="https://github.com/basil08/wordle-blitz-backend" target="_blank" rel="noopener noreferrer"&gt;backend&lt;/a&gt;&#10;). My conjecture is that people logged in but left the game midway/closed the tab. That voluntary quit button would have been nice after all.&lt;/p&gt;&#10;&lt;p&gt;PPS: (Dec 21st) Just added a &lt;code&gt;Result&lt;/code&gt; page to the app which shows very basic personalised performance report for each user. The metrics shown were: total attempted, wins, loses, average time per solve (basically 1800 seconds divided by the total puzzles seen), and number of tries for each word. I think this additional feature makes for a cute uupgrade in terms of completeness. How the results looked like?&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Total 228 participants&lt;/li&gt;&#10;&lt;li&gt;Average score of 31.28 (A multiplier table of 10 points for getting it right on the 1st attempt and subsequent decreasing points for more guesses was used)&lt;/li&gt;&#10;&lt;li&gt;Average number of attempts is 16.44&lt;/li&gt;&#10;&lt;li&gt;Average number of wins is 12.22&lt;/li&gt;&#10;&lt;li&gt;Hardest word to solve was ZOOEY (based on the number of guesses required by most people)&lt;/li&gt;&#10;&lt;li&gt;Easiest word to solve was ABASH (based on the number of guesses required by most people)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;The podium finishes all scored 100+ 🤯 and the first had an insane average time to solve of around 24 seconds with 76% accuracy!&lt;/p&gt;&#10;&lt;p&gt;Fin.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h3 id="footnotes"&gt;Footnotes&lt;/h3&gt;&#10;&lt;p&gt;1: Partly because I love lit club and would hate to see one more event not organised and also partly because all attempts to pass it on to other people failed 💀&lt;/p&gt;&#10;&lt;p&gt;2: If you&amp;rsquo;re not familiar with classic Wordle, you may check out &lt;a href="https://www.youtube.com/watch?v=WnWPXZ6vQB8" target="_blank" rel="noopener noreferrer"&gt;this&lt;/a&gt;&#10; video.&lt;/p&gt;&#10;&lt;p&gt;3: I hoped &lt;a href="https://www.passportjs.org/docs/" target="_blank" rel="noopener noreferrer"&gt;Passport library&lt;/a&gt;&#10; would come in handy.&lt;/p&gt;&#10;&lt;p&gt;4: Moreover, I will obviously run basic analysis on the gameData to find out if someone cheated by flagging outliers and extremely exceptional performers.&lt;/p&gt;&#10;&lt;p&gt;5: Okay, I must admit. I was enjoying the adrenaline rush. The idea of writing code that people would interact with and the whole thing being setup in a delayed black box fashion made me more excited than nervous. Besides, I somewhat trust the code I write, okay? &lt;em&gt;Somewhat&lt;/em&gt; is the operative part here, of course.&lt;/p&gt;&#10;&lt;p&gt;6: You might have noticed that I have been chattier than usual. That’s partly because I haven’t written in a while and this feels nice and also because I’m sitting here all alone and bored at LHR airport waiting for a senior to join.&lt;/p&gt;&#10;</description></item><item><title>Textile@IITD: Through the eyes of a UG junior student</title><link>https://basillabib.com/post/textile@iitd-through-the-eyes-of-a-ug-junior-student/</link><pubDate>Tue, 01 Aug 2023 20:38:11 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/textile@iitd-through-the-eyes-of-a-ug-junior-student/</guid><description>&lt;h2 id="introduction"&gt;INTRODUCTION&lt;/h2&gt;&#10;&lt;p&gt;I have spent a year in the Textile and Fibre Engineering department of IIT Delhi pursuing a Bachelor of Technology and a minor from the Computer Science department. My designation qualifies me to write this highly opinionated post which is a collection of my experiences and my interpretations for some of them. The value comes from the fact that I make an unbiased review of the department with no filters. This document can serve as a guide for aspiring Textile students at IIT Delhi, current students to guide them in their education, and even professors, administration, and educationists to guide their pedagogical approach.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Note: My experiences are, for better or worse, limited to the UG programme only.&lt;/em&gt;&lt;/p&gt;&#10;&lt;h2 id="the-prior-context"&gt;THE PRIOR CONTEXT&lt;/h2&gt;&#10;&lt;p&gt;One of the first representative experiences for first year students is the subtle branch based discrimination and the nucleation of groups based on it. Although the clustering and formation of impenetrable groups are less prominent in first year as the courses are taken with no regards to department, the bias exists and gets ugly many a times. Casual jokes and snarky remarks about JEE rank and departments are common and even stereotypes based on which department one belongs to (The IITD slang for a Textile student is a Tatta, which means scrotum in English). Just like the natural tendency for humans to hierarchise everything, there exists a well-ordering principle for branches at IITD wherein CS occupies the highest place and Textile the lowest.&lt;/p&gt;&#10;&lt;p&gt;This creates a deep-seated psychological dynamic in the minds of Textile peeps which is almost impossible to get rid of unless someone is extremely confident about themselves. Fortunately (and I make this statement based on observations of my batch), students who come to IITD are significantly more “aware” about themselves and smarter on average than the general populace who sit for JEE. This somewhat cushions the dark abyss into what someone can inadvertently fall into if they are exposed to the toxic first year environment very much prevalent in IIT Delhi. I can only imagine the sheer insecurity and hopelessness that a Textile student at a Tier 2 or Tier 3 college would face.&lt;/p&gt;&#10;&lt;p&gt;It is not a lie that Textile students lack severe engineering aptitude when it comes to academics. This is confirmed by the well-ordering principle applied to JEE ranks and subsequent CGPAS at the end of year 1 (Again, I don’t have exact data but observations about my batch supports the supposition). One problem that severely aggravates the academic divide between a Textile student and an average IITD student is the role of reservations. Being one of the least sought-after branches at IITD, most students are attracted by the brand value of IITD solely. This allows a larger proportion of not-so-academically-inclined students to get admitted to the department. However, they are expected to compete with every branch and it is simply not possible to get up to speed with a meritorious and sincere student in the first year. This leads to Textile students having some of the lowest mean CGPAs across all branches.&lt;/p&gt;&#10;&lt;p&gt;Now let me talk about the other side of the coin - the merit of a Textile student. As noted before, an IITian is and will always be better than the average engineering student. They may not have studied for long hours during JEE prep but most of the students are smart. This is where I’ll introduce the first problem of the department.&lt;/p&gt;&#10;&lt;h2 id="the-coursework"&gt;THE COURSEWORK&lt;/h2&gt;&#10;&lt;p&gt;One of the first course Textile students take in their 2nd year is Textile Fibres (TXL111) - a course which is extremely heavy on factual information that one is supposed to rote-learn. There is no other way for a student to get a good grade except to remember a few numerical facts such as density of cotton etc. For an IITian, this is a serious matter of insult. At least, that&amp;rsquo;s how I felt most students take up the coursework in my experience.&lt;/p&gt;&#10;&lt;p&gt;I also understand that course experience is highly professor dependent but the inherent coursework is so unappealing, ancient, and disconnected with modern textile engineering practices that it ends up being heavily resented by students. I remember the course Yarn Manufacturing 1 (TXL221) I took in my 4th semester and the coursework was so disorganised and abrupt. There were very few concepts and no structure to any of them or how they relate to one another. It was impossible to follow the material through lectures and I had to pass that course somehow. The slides were very old and badly developed. It was evident that the same material was recycled each year.&lt;/p&gt;&#10;&lt;h2 id="the-professor"&gt;THE PROFESSOR&lt;/h2&gt;&#10;&lt;p&gt;The course coordinator or the professor is one parameter that is immutable for a student. One can opt or or drop a course but they have no control over the choice of professor at IITD. This creates very interesting dynamics and leads to further fractionalisation of students (Although, I suspect this is not a Textile specific phenomenon, it is, however, definitely a IITD phenomenon). Continuing the example of TXL221, there was a major disconnect between what the professor taught versus what he expected us to regurgitate in the exam. The marking scheme is prof-dependent and is one of the most opaque and harsh in the Textile department. So, there was a lot of surprise when the first minor exam marks came in and a random student got the highest and the class toppers got apparently random marks (myself included although I wasn’t lucky enough to land on the upper side of the class average).&lt;/p&gt;&#10;&lt;p&gt;Eventually, a small section of the batch realised how to game the system by learning how to write rather than what to learn. Thus, the optimisation is greatly skewed towards how to maximise marks obtained in an exam rather than to maximise learning. This can lead to a lot of interesting things such as but not limited to, following every single, however ridiculous, whim of the professor, grading policy, and exam pattern. I have seen my friends memorise everything just to regurgitate it on paper for the sake of marks. It is an extremely sad and ugly affair.&lt;/p&gt;&#10;&lt;p&gt;The situation is not as hopeless and horrible as it seems. There are sparks of great teaching also. Although I got the lowest possible grade in TXL221, I also got the highest grade possible in another course in the same semester. When I look back, it was a combination of a terse and extremely structured course content and an extremely passionate teacher who evidently put a lot of effort in the course. And by no means was this course “easy”, it was one of the most challenging and tricky courses for me but I knew what was happening and I think that matters a lot to students.&lt;/p&gt;&#10;&lt;h3 id="course-content-and-student-expectation-mismatch"&gt;Course content and student expectation mismatch&lt;/h3&gt;&#10;&lt;p&gt;Textile@IITD courses are not “easy”, they are logical and analytical and expect effort from the students but it still lacks in terms of rigour as compared to an average IITD course. Perhaps it is because of a complete lack of mathematics. Definitions are loosely defined, concepts may feel hollow at times and the course material simply superfluous.&lt;/p&gt;&#10;&lt;p&gt;Secondly, professors are forced to implement ridiculous policies ranging from roll call attendance to surprise quizzes just to make sure students are engaged in the class. I can understand how this is a serious pedagogical problem - how to teach something to students who are entirely disinterested in learning? However, this only lowers the reputation of the branch in the eyes of the students who feel like they are being treated as children.&lt;/p&gt;&#10;&lt;h3 id="non-uniform-and-non-standardised-evaluation"&gt;Non-uniform and non-standardised evaluation&lt;/h3&gt;&#10;&lt;p&gt;Unfairness prevails in the department as students are incentivised to mug up one day before exams, rampantly cheat without shame, and game the system in whatever way possible. A glaring example of this is seen in lab courses (TXPXXX) where marking schemes are geared towards ensuring students turn up rather than ensuring that they get something of value. It is a common practice to allocate 50% of the weightage on attendance and so on. Class participation is valued but that is mostly an eyewash as the process is highly non-institutionalised with very biassed or incorrect assumptions about what “class participation” even means. Vivas are conducted by equally disinterested TAs and are simply decorative in nature and anyone keen to game the system only has to pay a little attention. The tragedy of the situation is that students are not motivated enough to do even that.&lt;/p&gt;&#10;&lt;h3 id="a-comparison"&gt;A comparison&lt;/h3&gt;&#10;&lt;p&gt;At the same time, take a CS course where the student-professor dynamic is totally different. The students are inherently interested to learn. The professor takes the role of a facilitator and has to simply point the students in a certain direction. As a result, attendance policies are unheard of in CS/EE courses and students have a say in course policies like evaluation schemes etc. This increases the reputation of the department and the inherent confidence of the students themselves in their eyes.&lt;/p&gt;&#10;&lt;p&gt;Most often, it is sufficient to pretend that one is studying than to actually learn in order to secure a good grade. This greatly discourages learning, which discourages interest, which discourages new ideas from the students, which discourages active research, which harms the department&amp;rsquo;s reputation. It is a vicious cycle. Textile engineering offers a vibrant field for some really amazing research opportunities and for anyone interested it is a gold mine, both from the academic as well as the monetary perspective. I feel this is true for any disciple of engineering simply because people would always demand better goods and services.&lt;/p&gt;&#10;&lt;h2 id="the-student"&gt;THE STUDENT&lt;/h2&gt;&#10;&lt;p&gt;I began this article with the student in mind and I revisit this part of the story to tie all parts together. There is a strong correlation between the type of students who opt for a department versus the department they choose. For example, the most studious and academically bright opt for CS. A little less opt for Maths. EE consists of even less competent students and so on. Note that the word “competent” is a highly controversial term and I detest using it but it should be interpreted as “competency as measured by the JEE exam”. It is the well-ordering principle at play.&lt;/p&gt;&#10;&lt;p&gt;Students who opt for Textile mostly come with a set target in mind even before they begin their first year. This is a sad situation as it completely debars students to explore the liberating academic lifestyle of a university and to effectively identify their interests. Even as I write this, my friends are busy preparing hard for the intern season. As for me, I’m not sure where my interest lies and I am still exploring. I think it&amp;rsquo;s a good thing to not know. With the textile legacy, it is very difficult to do that.&lt;/p&gt;&#10;&lt;p&gt;Psychologically, Textile students are also surprisingly self-confident. Perhaps, this is just the experience of my batch but I find them very proactive in pursuing a number of hobbies from photography to debating and they are pretty good at it. I think it is this self-confidence that enables them to choose textile in the first place, in spite of all the lore and hostility towards it. They approach the department with the prior bias of “This is a dead branch. I have to figure out something else.”&lt;/p&gt;&#10;&lt;p&gt;This attitude, fueled by an outdated (or unstructured) curriculum incentivises students to be least bothered about their courses as long as it fetches them a decent CGPA. I am no one to opine if it is right or wrong but as I pointed out, I feel that the world is bigger than the nutshell we choose to live in.&lt;/p&gt;&#10;&lt;h2 id="the-opportunities-after-textileiitd"&gt;THE OPPORTUNITIES AFTER TEXTILE@IITD&lt;/h2&gt;&#10;&lt;p&gt;&lt;em&gt;I shall update this section after my third year of the B. Tech programme.&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;However, I must note one significant experience from my pre-internship experience. Sprinklr is a sought-after tech company that recruits interns from campus. However, this year it did not allow Textile and Material Science students to sit for the pre-intern tests. The reason cited by the company’s HR was: “I don’t think these departments will be able to compete and clear the tests like the other branches.” These are the popular impressions of the department in spite of a significant number of Textile graduates securing tech placements.&lt;/p&gt;&#10;&lt;h2 id="what-is-the-solution"&gt;WHAT IS THE SOLUTION?&lt;/h2&gt;&#10;&lt;p&gt;As part of my training as an engineer, I am expected to provide solutions to problems and after presenting this piece, it is only apt that I do so now. Note that this is not substantiated by any data or research on my behalf apart from my experience so take them with a grain of salt.&lt;/p&gt;&#10;&lt;p&gt;The individual cogs of the machine work together to make the Textile department what it is today. I don&amp;rsquo;t know what incentive mechanism drives the faculty or the course development but I can vouch for the fact that the presentation makes a lot of difference simply because I have seen it happen in a Textile course. Textile courses are as important (and perhaps engaging too if they are made to be) as any other course but a combination of above highlighted factors makes it impossible for students to believe otherwise. I think one possible solution would be to reimagine the course material from the ground up and include industry-relevant material with emphasis on application of digital technologies, CAD, connection with business, entrepreneurship etc. Essentially focus on aspects of Textile engineering that are more “usable” for the students. Another solution would be to allow students to craft course policies as they please and introduce more innovative accountability measures than just traditional attendance and exams. This can include projects, reports, mini research work that includes more hands-on application of concepts taught in class. The exams must NEVER be a way for students to test their memory muscles. All this will require a major effort from both the faculty and the student body at the humble Textile department at IIT Delhi.&lt;/p&gt;&#10;</description></item><item><title>The Murky Mempool: Making sense of {M|B}EV</title><link>https://basillabib.com/post/the-murky-mempool-making-sense-of-mbev/</link><pubDate>Thu, 27 Jul 2023 23:30:59 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/the-murky-mempool-making-sense-of-mbev/</guid><description>&lt;p&gt;It is human tendency to become unconditionally enchanted by something popular regardless of its intrinsic value. Known by many names like &lt;a href="https://en.wikipedia.org/wiki/Network_effect" target="_blank" rel="noopener noreferrer"&gt;network effect&lt;/a&gt;&#10;, &lt;a href="https://en.wikipedia.org/wiki/Herd_mentality" target="_blank" rel="noopener noreferrer"&gt;herd mentality&lt;/a&gt;&#10;, etc., this psychological effect has a diabolic effect on information dissemination. This is especially true when there is high variance in the educational distribution of the population. Take the &lt;a href="https://phys.org/news/2023-07-korean-team-room-temperature-ambient-pressure-superconductor.html" target="_blank" rel="noopener noreferrer"&gt;room temperature semi-conductor craze&lt;/a&gt;&#10; for example. Or the plethora of medical news during the 2020 pandemic. Or the invention of the web. You get the point. It&amp;rsquo;s everywhere.&lt;/p&gt;&#10;&lt;p&gt;Something similar has happened to the blockchain technology, made popular by a &lt;a href="https://bitcoin.org/bitcoin.pdf" target="_blank" rel="noopener noreferrer"&gt;2008 anonymous paper&lt;/a&gt;&#10;. The real information about the tech and its implication is so sparse and shrouded in so much nonsense that the market around it has emerged as a scam/fraud leading to less credibility and trust.&lt;/p&gt;&#10;&lt;p&gt;In this post, I wish to discuss how blockchain tech may not be the panacea of decentralised commerce as it has been once been marketed as. I will discuss design flaws inherent in the Ethereum chain and what it means for the properties that enabled Ethereum to differentiate itself from traditional financial systems.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Note: This post assumes the older Ethereum consensus mechanism, namely Proof-Of-Work.&lt;/em&gt;&lt;/p&gt;&#10;&lt;h2 id="frontrunning"&gt;Frontrunning&lt;/h2&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;Front-running&lt;/strong&gt; (noun): &lt;em&gt;The practice by market-makers of dealing on advance information provided by their brokers and investment analysts, before their clients have been given the information.&lt;/em&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Let us take an example. Imagine there are two parties Alice and Bob. Alice somehow gets to know that Bob is about to make a &amp;ldquo;big&amp;rdquo; trade, that is, he&amp;rsquo;s about to purchase a lot of cookies. So, just before Bob&amp;rsquo;s purchase order, Alice goes to the market and buys a lot of cookies &lt;em&gt;for herself thereby raising the price of cookies&lt;/em&gt;. Now, as Bob&amp;rsquo;s purchase order is processed, he has to pay more money for the same commodity. Alice has managed to extract money from Bob simply by knowing about Bob&amp;rsquo;s purchase order in advance. Note that there&amp;rsquo;s also a bunch of other factors, most importantly the design of the trading platform that Alice and Bob are using that affects how big this problem can be. In some ways, frontrunning is a fundamental design flaw in the very act of trading.&lt;/p&gt;&#10;&lt;p&gt;Traditional (that is, centralised) financial trading firms use a &lt;em&gt;&lt;a href="https://nanonets.com/blog/purchase-order/" target="_blank" rel="noopener noreferrer"&gt;purchase order&lt;/a&gt;&#10; book&lt;/em&gt; to match up buy and sell orders. If Eve had access to this list of buy and sell orders, she would be able to anticipate the price changes that some of those orders would have and &lt;em&gt;frontrun&lt;/em&gt; those transactions to make money out of thin air. Frontrunning is illegal on CeFis and govermental agencies oversee that brokers and trading agencies don&amp;rsquo;t engage in such activites.&lt;/p&gt;&#10;&lt;h2 id="exploitable-opportunities"&gt;Exploitable opportunities&lt;/h2&gt;&#10;&lt;p&gt;What kind of purchases can lead to frontrunnable opportunities? Large volume orders are an obvious example. A buy order of 0.5% of all tradable ETH would have a substantial impact on the price of ETH. It would also create arbitrage opportunities across multiple markets that would decay based on the oracle latency between them. Even human errors in placing buy and sell orders can lead to frontrunnable scenarios in DeFi as we&amp;rsquo;ll see later.&lt;/p&gt;&#10;&lt;p&gt;Unlike CeFi, DeFi are designed to be transparent, that is, transactions are public. One cannot be decentralised if one cannot see what&amp;rsquo;s going on&amp;hellip;which makes sense. But now, let us understand how this design feature aggravates frontrunning on blockchain and reduces consensus security.&lt;/p&gt;&#10;&lt;p&gt;In the Ethereum protocol, local transactions are broadcasted via a gossip protocol to nearby nodes and are processed by rational miners based on the decreasing order of transaction fees. However, there is always a publicly accessible set of unconfirmed transactions called a &lt;em&gt;&lt;a href="https://currency.com/what-is-mempool" target="_blank" rel="noopener noreferrer"&gt;mempool&lt;/a&gt;&#10;&lt;/em&gt;. It contains transactions that might potentially fail or be valid but nobody knows. As you can imagine, this presents perfect oppurtunity to frontrunners who can examine the mempool and frontrun profitable transactions without a fuss.&lt;/p&gt;&#10;&lt;h2 id="an-example"&gt;An example&lt;/h2&gt;&#10;&lt;p&gt;Most DeFi exchanges are based on the &lt;em&gt;purchase orderbook&lt;/em&gt; mechanism in which a centralised clearing house matches a list of buy orders with a list of sell orders when they find compatible trades. In a DeFi, the central authority is, of course, the immutable logic of the smart contract behind the exchange. Note that this is a different design than the more innovative automated market maker model introduced by Uniswap. Now, let us see how a frontrunning transaction can exploit an erroneous trade order.&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s assume that someone offers to buy an asset at 10x the market rate (perhaps due to a slip while filling the price or a decimal point error). A bot would identify this opportunity and quickly buy that asset from the market and then sell this back to the original buyer. This is possible because of the inherent atomicity of transactions on the blockchain via so-called blocks. Moreover, the incentive mechanism for miners to &amp;ldquo;mine&amp;rdquo; a particular trade is assumed to be strictly dependent on the transaction fee they reap out of the order. Hence, in order to prioritise a transaction, one only has to set a marginally higher transaction fee to frontrun a transaction. We&amp;rsquo;ll see how this leads to an interesting phenomenon.&lt;/p&gt;&#10;&lt;p&gt;On the flip side, let us assume that someone offers to sell their token at .1x the market rate (again, an error). A frontrunner might buy those token immediately and sell them in the market and gain 0.9x profit.&lt;/p&gt;&#10;&lt;p&gt;Arbitrage and frontrunning situations can arise from a number of factors such as price difference of the same asset across markets and the different response rates for market movements based on platform design, logic bugs, human error, etc. Generalising this, a sophisticated frontrunning bot will replay a number of shortlisted transactions in the mempool in a local environment and try to frontrun one or a sequence which leads to a net positive profit for itself. The exact profit is difficult to calculate simply because it is not possible to anticipate the subset of transactions visible to a bot which depends on a number of factors such as latency, network congestion, and the well-connectedness of the bot.&lt;/p&gt;&#10;&lt;p&gt;What I described above is a particular type of exploiting frontrunnable transactions dubbed &amp;ldquo;pure revenue opportunities&amp;rdquo; by &lt;a href="https://arxiv.org/abs/1904.05234" target="_blank" rel="noopener noreferrer"&gt;this paper&lt;/a&gt;&#10;. Due to the unique public nature of these transactions, a victim can see what happened with their funds. I reproduce few comments made by victims of such bots from the paper linked above.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;h4 id="i-am-a-single-parent-trying-to-make-ends-meet-at-a-job-i-hate-please-have-some-mercy"&gt;&amp;ldquo;I am a single parent trying to make ends meet at a job I hate. Please have some mercy.&amp;rdquo;&lt;/h4&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;h4 id="please-i-ask-you-to-send-me-the-eth-back-as-i-really-need-it-to-continue-my-education-i-might-need-to-sell-my-car-to-pay-what-is-already-due-for-this-semester"&gt;&amp;ldquo;Please I ask you to send me the ETH back as I really need it to continue my education. I might need to sell my car to pay what is already due for this semester.&amp;rdquo;&lt;/h4&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;h4 id="this-was-obviously-a-mistake-transaction---any-chance-you-can-find-it-in-your-heart-to-send-it-back"&gt;&amp;ldquo;This was obviously a mistake transaction - any chance you can find it in your heart to send it back?&amp;rdquo;&lt;/h4&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="priority-gas-auctions-pgas"&gt;Priority Gas Auctions (PGAs)&lt;/h2&gt;&#10;&lt;p&gt;The mechanism for exploiting an abitrage opportunity is by placing your order before the victim order. In the blockchain paradigm, this translates to assigning a higher transaction fee to your transaction to incentivise a rational miner to include the attack transaction first. But an interesting phenomenon happens if two bots find an exploitable transaction. In this case, a naive strategy bot A would keep increasing their transaction fee in response to bot B, following the same strategy, and trying to nullify A&amp;rsquo;s attempt to reap a profit. This leads to interesting series of transactions with the same nounce but incrementally higher gas fee by two wallets for the same trade - a pretty conspicuous phenomenon.&lt;/p&gt;&#10;&lt;p&gt;Due to the stochastic nature of block inclusion time (which depends on the miner being able to find a solution to a cryptographically hard problem), the transaction to be actually included is not pre-determined.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-1740be1c" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-1740be1c" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/mev/0.png" alt="Source: Flash Boys 2.0" loading="lazy" class="zoomable-image" style="max-height:200px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-1740be1c" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/mev/0.png" alt="Source: Flash Boys 2.0" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Source: Flash Boys 2.0&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Note this interesting PGA battle where two bots engaged in a biding war. Eventually, the green transaction for &lt;code&gt;0x6BEcA...&lt;/code&gt; was included at 134.02. Interestingly, the bots agreed to raise their fee to ~8K. The other transactions were, of course, discarded from the mempool once the winning transaction was mined.&lt;/p&gt;&#10;&lt;p&gt;Bots can adopt different strategies for bidding in such auctions. Two of the simplest and observed strategies are:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Blind raising: Simply increase the bid by a fraction until the order is successful. This is a non-adaptive strategy and doesn&amp;rsquo;t take into account what the other agent is upto.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Counterbidding: As the name suggests, only raise the bid if the current bid will lead to failure and raise the bid by a certain fraction of the other agent&amp;rsquo;s bid.&lt;sup&gt;1&lt;/sup&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Moreover, the winner of a bid war is highly dependent on the latency of the network and the connectedness of the bots to the network, hinting at the development of &lt;a href="https://arxiv.org/abs/2009.14021" target="_blank" rel="noopener noreferrer"&gt;high-frequency trading-like scenarios on blockchain platforms&lt;/a&gt;&#10;; an undesirable and &lt;a href="https://academic.oup.com/qje/article/130/4/1547/1916146" target="_blank" rel="noopener noreferrer"&gt;unintended consequence&lt;/a&gt;&#10; of the current design of the system.&lt;/p&gt;&#10;&lt;h2 id="transaction-reordering-and-unintended-optimisation"&gt;Transaction reordering and unintended optimisation&lt;/h2&gt;&#10;&lt;p&gt;Once in the mempool, the miners have a lot of freedom in choosing which transactions to include in the block they&amp;rsquo;ll mine next. The protocol assumes that &lt;em&gt;rational&lt;/em&gt; miners would order transactions by decreasing transaction fee which is the intended behaviour. However, if the miners have a greater incentive from outside the system to deviate from this behaviour then it leads to lowered network security. &lt;a href="https://arxiv.org/abs/2101.05511" target="_blank" rel="noopener noreferrer"&gt;Studies show&lt;/a&gt;&#10; that at certain thresholds, miners are &lt;em&gt;incentivised&lt;/em&gt; to even fork the chain.&lt;/p&gt;&#10;&lt;p&gt;If a miner is aware of a transaction sequence that would lead to profitable outcome or even personal connections with the transaction participants, they can reorder transaction orders if the profit from such reordering is greater than the transaction fee. When can such scenarios occur? Once large volume trades are detected in the mempool, miners may identify potential frontrunnable transactions or more sophisticated types called &amp;ldquo;sandwich attacks&amp;rdquo; (flank the victim transaction with two attacking transactions to bet in favour of a condition that is determined to occur as an outcome of the victim transaction). Miners can collude with third-party users and reorder transactions to maximise the block reward they can mine. They may also censor transactions of a particular kind, type, demographic, and so on. One quickly realises how this design pattern can fundamentally compromise blockchain security.&lt;/p&gt;&#10;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p&gt;While transacting on the application layer, it is often possible that endusers abstract away the complexity of what goes under the hood on the Ethereum protocol which enables them decentralised consensus. But we find that the mempool is not a utopic panacea that will enable blockchain to replace traditional CeFi. This primarily arises from fundamental design flaws that were not originally conceived - an ideal case study to understand why engineering is hard.&lt;/p&gt;&#10;&lt;p&gt;I haven&amp;rsquo;t even scratched the surface of how profitable these &amp;ldquo;extractable values&amp;rdquo; have been for adversaries in the past (for numbers, check out the Flash Boys paper) and still continue to be a source of income for sophisticated bots. The original paper &lt;a href="https://arxiv.org/abs/1904.05234" target="_blank" rel="noopener noreferrer"&gt;Flash Boys 2.0&lt;/a&gt;&#10; dubbed this value as MEV or Miner Extractable Value but after the transition to PoS, it was proposed to name it as Maximum Extractable Value. &lt;a href="https://arxiv.org/abs/2101.05511" target="_blank" rel="noopener noreferrer"&gt;This paper&lt;/a&gt;&#10; argues that MEV is a subset of total extractable value which it dubs BEV or Blockchain Extractable Value. So, even if the terminologies are not very definite, I hope the problem is.&lt;/p&gt;&#10;&lt;p&gt;Well, the above picture might seem more grim than the situation is. Currently there is amazing work going on which will nullify wealth stealing by transferring the incentives to the users and miners. An interesting middle layer is being built - for example, &lt;a href="https://boost.flashbots.net/" target="_blank" rel="noopener noreferrer"&gt;MEV Boost&lt;/a&gt;&#10; for PoS-enabled blockchains.&lt;/p&gt;&#10;&lt;h2 id="references"&gt;References&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a href="https://arxiv.org/abs/1904.05234" target="_blank" rel="noopener noreferrer"&gt;paper&lt;/a&gt;&#10; Flash Boys 2.0: Frontrunning, Transaction Reordering, and Consensus Instability in Decentralized Exchanges: Daian P. et. al&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://arxiv.org/abs/2009.14021" target="_blank" rel="noopener noreferrer"&gt;paper&lt;/a&gt;&#10; Quantifying Blockchain Extractable Value: How dark is the forest?: Qin K. et. al&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;1: I am simplifying a lot of details here. For example, transaction fee increments are not continuous. Most ETH clients require a bid to be atleast 12.5% of the initial bid to be considered for inclusion thus making bidding discrete in practise.&lt;/p&gt;&#10;</description></item><item><title>How Bonding Curves Are Crucial to DeFi Protocols</title><link>https://basillabib.com/post/how-bonding-curves-are-crucial-to-defi-protocols/</link><pubDate>Thu, 06 Jul 2023 14:26:12 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/how-bonding-curves-are-crucial-to-defi-protocols/</guid><description>&lt;div style="text-align: center"&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-2df9c24c" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-2df9c24c" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/bc/0.jpg" alt="Source: Google images" loading="lazy" class="zoomable-image" style="max-height:200px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-2df9c24c" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/bc/0.jpg" alt="Source: Google images" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Source: Google images&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;/div&gt;&#10;&lt;p&gt;After the initial excitement, the current state of DeFi rests with the spawning of a number of speculation and lending markets (such as &lt;a href="https://compound.finance" target="_blank" rel="noopener noreferrer"&gt;Compound&lt;/a&gt;&#10;, &lt;a href="https://aave.com" target="_blank" rel="noopener noreferrer"&gt;AAVE&lt;/a&gt;&#10;), complementing their traditional counterparts. For a comprehensive bird-eye view of the history of DeFi, I strongly recommend &lt;a href="https://medium.com/@njess/defi-101-some-basic-primitives-where-they-came-from-and-why-it-matters-35f8c0cf8557" target="_blank" rel="noopener noreferrer"&gt;this post&lt;/a&gt;&#10; by &lt;a href="http://twitter.com/njess" target="_blank" rel="noopener noreferrer"&gt;@njess&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;p&gt;Speculation markets work by providing a platform for traders to bet on the value of any digital asset over time. DeFi is experimenting with devising financial instruments like futures and options for digital assets as evident by a number of perpetual futures protocols like &lt;a href="https://perp.com" target="_blank" rel="noopener noreferrer"&gt;Perpetual&lt;/a&gt;&#10;, &lt;a href="https://dydx.exchange" target="_blank" rel="noopener noreferrer"&gt;dYdX&lt;/a&gt;&#10;, &lt;a href="https://mycelium.xyz" target="_blank" rel="noopener noreferrer"&gt;Mycelium&lt;/a&gt;&#10;, etc.&lt;/p&gt;&#10;&lt;h2 id="amms--a-defi-primitive"&gt;AMMs – a DeFi primitive&lt;/h2&gt;&#10;&lt;p&gt;However, when the exchange is small and relatively unknown, it is difficult to exchange tokens/assets issued by the exchange for ETH or any stablecoin because one won&amp;rsquo;t be able to find a buyer or seller. In other words, the exchange doesn&amp;rsquo;t have enough &lt;em&gt;liquidity&lt;/em&gt;. Of course, the smart contract implementing the exchange protocol will provide the liquidity initially but at what price? In other words, what should be the mechanism for &lt;a href="https://en.wikipedia.org/wiki/Price_discovery" target="_blank" rel="noopener noreferrer"&gt;price discovery&lt;/a&gt;&#10;?&lt;/p&gt;&#10;&lt;p&gt;This was the problem that Uniswap protocol designers were grappling with when they launched Unisocks in 2019, a smart contract to exchange socks but equipped with an automated market maker or AMM. Based on a formula, the contract was able to quote a price for an asset &lt;em&gt;on demand&lt;/em&gt;. The Unisock protocol used something like the &lt;span class="has-mathjax"&gt; \( x*y = k\) &lt;/span&gt;&#10; formula to define the number of x and y in circulation. This ensured that there was always enough of x and y in the contract to trade anytime with anyone. As &lt;a href="http://twitter.com/njess" target="_blank" rel="noopener noreferrer"&gt;@njess&lt;/a&gt;&#10; writes,&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;h3 id="very-few-people-realized-the-power-of-having-a-permissionless-market-always-willing-to-trade-not-an-efficient-market-an-available-market"&gt;&amp;hellip;very few people realized the power of having a permissionless market always willing to trade. Not an efficient market. An &lt;em&gt;available&lt;/em&gt; market.&lt;/h3&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;We&amp;rsquo;ll dive into the math soon but first, we have to ask a deeper question.&lt;/p&gt;&#10;&lt;h2 id="why-should-one-buy-tokens"&gt;Why should one buy tokens?&lt;/h2&gt;&#10;&lt;p&gt;For the same reason why one buys any other asset. Because they believe that the asset will appreciate in value overtime. Note that the asset itself doesn&amp;rsquo;t need to have intrinsic value. Things like paper currency notes, or application of oil on canvas paper are absurdly overvalued if one believes their real value are strictly tied to their intrinsic value. Most of the time, value is conferred by other people and tokens are just another such asset albeit digital.&lt;/p&gt;&#10;&lt;p&gt;Infact, web3 protocols are more efficient than traditional finsystems at matching value-valuation levels of an asset if they are well designed as tokens actively incentivise holders towards certain actions. This is an area of active research and people are experimenting with curation markets where they incentivise users to report non-credible, fishy news. Encouraging such positive social behaviour would make such protocols appear valuable and so the tokens would appreciate in value (in other words, get more in demand).&lt;/p&gt;&#10;&lt;h2 id="bonding-curves"&gt;Bonding curves&lt;/h2&gt;&#10;&lt;p&gt;In finance, a bonding curve is essentially a &lt;strong&gt;function to describe the relationship between the price and supply of an asset&lt;/strong&gt;. Bonding curves are what powers liquidity pools and other exchange protocols. Since exchange is such a fundamental action in DeFi, AMMs are one of the primitives of DeFi.&lt;/p&gt;&#10;&lt;p&gt;A typical bonding curve looks like:&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$ y = mx^n $$ &#10;&lt;/div&gt;&#10;&#10;where m is a curve parameter (to fine-tune the shape) and n is the order of the curve. y is the token price and x is the token supply.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-d45255fb" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-d45255fb" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/bc/1.png" alt="The power function for various values of m and n. Source: author, plotted on desmos.com" loading="lazy" class="zoomable-image" style="max-width:400px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-d45255fb" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/bc/1.png" alt="The power function for various values of m and n. Source: author, plotted on desmos.com" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;The power function for various values of m and n. Source: author, plotted on desmos.com&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;If we take the simplest curve, &lt;span class="has-mathjax"&gt; \( y = x \) &lt;/span&gt;&#10; which is the bonding curve for our token ABT (Alice-Bob token). Let us see how it affects user behaviour.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-6717c60a" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-6717c60a" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/bc/2.png" alt="The area represents the price for the tokens at different supply levels. Source: author, plotted on desmos.com" loading="lazy" class="zoomable-image" style="max-width:400px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-6717c60a" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/bc/2.png" alt="The area represents the price for the tokens at different supply levels. Source: author, plotted on desmos.com" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;The area represents the price for the tokens at different supply levels. Source: author, plotted on desmos.com&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;If Alice buys token when the supply is low, she has to pay a small amount. She bought 3 tokens when the price was 3 ETH and now the tokens in supply is 6 tokens so the token price rises to 6 ETH. Bob notices that the token has increased in demand so he wants to have some of ABT too. He buys 2 ABT for 6 ETH each. Now, Alice can sell her stock of ABT as the token price is higher than her cost price and reap a profit out of it. Meanwhile, Bob would incur a loss if he sells after Alice has sold her stock since the token price is higher than his cost price. Thus, this curve incentivises users to buy early and rewards early adopters.&lt;/p&gt;&#10;&lt;p&gt;I should clarify that the statement &amp;ldquo;He buys 2 ABT for 6 ETH each&amp;rdquo; is not correct but a simplification. Since these are continuous curves, the price of assets are continuously changing, hence the price of ABT would increase for even a &lt;span class="has-mathjax"&gt; \( 10^{-18} \) &lt;/span&gt;&#10; part change. Thus, to compute the cost for 2 ABT, the contract would actually &lt;strong&gt;compute the area under curve&lt;/strong&gt; for the function under appropriate limits. This can be done by good &amp;lsquo;ol integral calculus.&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$ \int mx^n dx = m \int x^n dx = \frac{m}{n+1} x^{n+1} $$&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;Let&amp;rsquo;s say that the number of tokens in circulation was t, and we want to buy k more tokens. By putting limits,&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$&#10;p = \left[\frac{m}{n+1} x^{n+1} \right]_t^{t+k} = \frac{m}{n+1}(t+k)^{n+1} - \frac{m}{n+1}t^{n+1}$$&#10;&#10;$$ = \frac{m}{n+1}t^{n+1} \left( \frac{(t+k)^{n+1}}{t^{n+1}} - 1 \right)&#10;$$&#10;&#10;$$ = \frac{m}{n+1}t^{n+1} \left( \left( 1 + \frac{k}{t} \right)^{n+1} - 1 \right) $$ &#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;By solving for k, we can find the number of tokens that one can trade for p units of ETH or stablecoin, likewise:&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$ k = t \left( \left( \frac{p(n+1)}{t*s^{n+1}} + 1 \right)^{\frac{1}{n+1}} -1 \right) $$&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;The problem with the above formula is ofcourse the pesky exponent term which can lead to overflow for large t or n. How can we compute p and k efficiently. We need some math trickery and this is exactly what the bancor formulae are.&lt;/p&gt;&#10;&lt;h2 id="bancor-formula"&gt;Bancor formula&lt;/h2&gt;&#10;&lt;p&gt;If we define the initial pool balance that is the cost of going from 0 to t as another variable, say b, then the above expressions can be written as follows:&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$ p = b \left( \left( \frac{k}{t} + 1 \right)^{n+1} - 1 \right) $$&#10;and,&#10;&#10;$$ k = t \left( \left( \frac{p}{b} +1 \right)^{\frac{1}{n+1}} - 1 \right) $$ &#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;We still have to compute the exponent but we are getting there, bancor formula introduced another variable called the &lt;code&gt;reserveRatio&lt;/code&gt; as follows:&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$ r = \frac{poolBalance}{tokenPrice * tokenSupply} $$&#10;&#10;$$ r = \frac{b}{p*t} $$&#10;which is &#10;&#10;$$ = \frac{\frac{m}{n+1}t^{n+1}}{(m*t^n)*t} = \frac{1}{n+1} $$&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;Thus, the price formula under the bancor parameterisation becomes:&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$ p = b \left( \left( \frac{k}{t} +1 \right)^{\frac{1}{r}} - 1 \right) $$ &#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;If you have had some prior exposure to basic economics then the above bonding curve seems a little disconcerting. Why is the price more when there are more tokens in circulation? Shouldn&amp;rsquo;t the price be more when the tokens are scarce? But think about it for a minute. More tokens in circulation means more demand. It is not like more tokens implies less scarcity.&lt;/p&gt;&#10;&lt;h2 id="sigmoid-functions-as-bonding-functions"&gt;Sigmoid functions as bonding functions&lt;/h2&gt;&#10;&lt;p&gt;People coming from ML are already acquainted with the sigmoid function, a friendly little function with a nice shape.&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$ g(x) = \frac{1}{1-e^{-x}} $$ &#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;However, the integral of this function is:&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$ \int\frac{1}{1-e^{-x}}dx = ln(1-e^{-x}) $$&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;which is a hairy function to compute on the EVM as an infinite power series has to be summed. Smart contracts ideally cannot implement fancier curves as they cost more gas price to compute on-chain. But there is a similar function with a much nicer integral:&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$ f(x) = \frac{x}{\sqrt{1+x^2}} $$&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;and, more importantly,&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;$$ \int\frac{x}{\sqrt{1+x^2}}dx = \sqrt{1+x^2} $$&#10;&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;Now, just like m and n in the power bonding function, we can parameterise this function as follows and compute the sigmoid price function:&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;&#10;$$&#10;y = a \left( \frac{(x+b)}{\sqrt{c + (x+b)^2}} -1 \right)&#10;$$&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;which when integrated within proper limits yield the following price function:&lt;/p&gt;&#10;&lt;div class="has-mathjax mathjax-block"&gt;&#10;&#10;$$&#10;p = \int_{t}^{t+k} a \left( \frac{(x+b)}{\sqrt{c + (x+b)^2}} -1 \right)dx&#10;$$&#10;&#10;$$&#10;= a (\sqrt{c + (t+k+b)^2} + t + k ) - b&#10;$$&#10;&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;which is much easier to compute.&lt;/p&gt;&#10;&lt;h2 id="price-discovery-mechanism"&gt;Price discovery mechanism&lt;/h2&gt;&#10;&lt;p&gt;Quantitatively, the value of a token can be defined as the sum of total cash flow available to the bearer. But this raises many questions such as is it always possible to determine the future cash flow from an asset? If yes, then how to compute that? Traditional pricing techniques have evolved tools to price options and futures such as Black-Sholes formula, etc. but I haven&amp;rsquo;t researched much into that. The precise token design is also affected by the amount of token in circulation. Is it possible to value a token in the future if one allows infinite token minting by design? How to do that? These questions are under active research and no doubt, bonding curves will be an important part of future DeFi protocols.&lt;/p&gt;&#10;&lt;h2 id="problems-with-bonding-curves"&gt;Problems with bonding curves&lt;/h2&gt;&#10;&lt;p&gt;Not a problem per se but I just wanted to note that AMMs are useful to provide liquidity to a token when it is in its infancy. But once the token is liquid enough to be traded on the free market at other DEXes, then who sets the price? Well, then the AMM and the free market stay at the same price. Any difference is levelled out by arbitrage.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Frontrunning a transaction&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Do you remember the case for ABT token above? Well, what if Eve got to know that Alice is about to buy her tokens, somehow places an exact transaction with a higher gas fee to incentivise miners to mine her transaction as oppose to Alice&amp;rsquo;s and then sell her tokens to Alice at a higher price (since the token&amp;rsquo;s price has just increased). One possible solution is limiting the gas fee that one can set for these transactions and &lt;em&gt;always&lt;/em&gt; ask buyers/sellers to set it to that value.&lt;/p&gt;&#10;&lt;ol start="2"&gt;&#10;&lt;li&gt;Nested bonding curves compounds risks&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Imagine that ABT is pegged to ETH with a reserve ratio of 50%. Now, Darlene designs a token and pegs it to ABT with a reserve ratio of 10%. The actual reserve ratio is .5 * .1 = .05 or 5% of ETH. Further nesting can result in a highly volatile asset which basically means huge changes in the value of the asset for tiny changes in the value of the underlying asset (ETH in this case). One possible solution is to limit the number of nesting, of course.&lt;/p&gt;&#10;&lt;h2 id="references"&gt;REFERENCES&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a href="https://blog.relevant.community/how-to-make-bonding-curves-for-continuous-token-models-3784653f8b17" target="_blank" rel="noopener noreferrer"&gt;How to make continuous bonding token models&lt;/a&gt;&#10; by Slava Balasanov.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.relevant.community/bonding-curves-in-depth-intuition-parametrization-d3905a681e0a" target="_blank" rel="noopener noreferrer"&gt;Bonding Curves in Depth: with math&lt;/a&gt;&#10; by Slava Balasanov.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://medium.com/molecule-blog/token-bonding-curve-design-parameters-95d365cbec4f" target="_blank" rel="noopener noreferrer"&gt;Token Curve Design by Parameters&lt;/a&gt;&#10;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://billyrennekamp.medium.com/converting-between-bancor-and-bonding-curve-price-formulas-9c11309062f5" target="_blank" rel="noopener noreferrer"&gt;Converting between bonding curve and bancor parameters&lt;/a&gt;&#10; by billy Rennekamp.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://medium.com/linum-labs/ethereum-tokens-explained-ffe9df918008" target="_blank" rel="noopener noreferrer"&gt;Ethereum tokens explained&lt;/a&gt;&#10; by Linum Labs.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://medium.com/linum-labs/intro-to-bonding-curves-and-shapes-bf326bc4e11a" target="_blank" rel="noopener noreferrer"&gt;Introduction to bonding curves, shapes, and use cases&lt;/a&gt;&#10; by Linum Labs.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Playing God: Making sense of CRISPR</title><link>https://basillabib.com/post/playing-god-making-sense-of-crispr/</link><pubDate>Wed, 22 Feb 2023 16:29:20 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/playing-god-making-sense-of-crispr/</guid><description>&lt;h2 id="abstract"&gt;Abstract&lt;/h2&gt;&#10;&lt;p&gt;Imagine standing on a long corridor with closed doors that stretches to infinity on both ends. One keeps walking along and knocking on each door, maybe trying to pick the lock, peeping through the keyhole, or even trying to break in. But the process is tiresome and hopeless. And yet once in a while, someone manages to open a door and view a world they have never imagined. The story of scientific innovation is roughly similar to the story of the endless corridor.&lt;/p&gt;&#10;&lt;p&gt;In 2015, Junjiu Huang and co. at the Sun Yat-Sen university in China&lt;a href="https://www.nature.com/articles/nature.2015.17378" target="_blank" rel="noopener noreferrer"&gt; modified the gene responsible for beta-thalassaemia&lt;/a&gt;&#10;, a genetic blood disorder leading to anaemia, in non-viable embryos. Through this experiment, they had opened a new door - a door that led to a utopia of a disease-free humanity and, if I get ahead of myself, of “designer” humans and GMOs.&lt;/p&gt;&#10;&lt;p&gt;In this post, we’ll dive deep into CRISPR, the gene-editing tool that has taken the world by storm. The focus of this post will be on the technology rather than the social, political, or governance fallout of the tool. Previous understanding of genetics and biochemistry is recommended.&lt;/p&gt;&#10;&lt;h2 id="adaptive-immunity-and-crispr"&gt;Adaptive immunity and CRISPR&lt;/h2&gt;&#10;&lt;p&gt;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-2db1c746" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-2db1c746" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/4.png" alt="Adaptive Immunity System in bacteria and archaea, stage 1" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-2db1c746" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/4.png" alt="Adaptive Immunity System in bacteria and archaea, stage 1" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Adaptive Immunity System in bacteria and archaea, stage 1&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-e37d1624" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-e37d1624" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/2.png" alt="Adaptive Immunity System in bacteria and archaea, stage 2" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-e37d1624" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/2.png" alt="Adaptive Immunity System in bacteria and archaea, stage 2" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Adaptive Immunity System in bacteria and archaea, stage 2&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;Biological systems are known to be highly resilient towards a changing environment and this resilience stems from their ability to adapt quickly to changing environmental parameters. Bacteria and archaea protect themselves from viruses and plasmids by an adaptive immunity system. The crux of the mechanism is based on recording the genomic fingerprint of the intruder and to deploy proteins that splice future invaders based on the “memory” that it has developed over time. Short sequences of the invader’s genome are integrated into the bacteria or archaea’s genome. These short sequences are called Clustered Regulatory Interspaced Short Palindromic Repeats or CRISPR. These sequences along with CRISPR-associated proteins or Cas proteins form the machinery that enables this feature.&lt;/p&gt;&#10;&lt;p&gt;The CRISPR locus have two types of sequences, a highly variable region, called spacer, that constantly gets updated with new incoming invader genomic data and non-contiguous repeat sequences that separate them. Upon transcription, this stretch of DNA is expressed as a special RNA sequence called crRNA (CRISPR RNA) which doesn’t undergo translation but serves as activation director for Cas proteins. The crRNA enabled Cas9 is, then, able to bind to DNA sequences that are complementary to the crRNA and cleave them. The degradation of the foreign DNA renders the attack void and thus, immunity is gained.&lt;/p&gt;&#10;&lt;h2 id="crispr-cas9-system-how-it-works"&gt;CRISPR-Cas9 system: How it works&lt;/h2&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-951d2e25" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-951d2e25" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/1.png" alt="Block diagram showing the pathway of the crRNA" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-951d2e25" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/1.png" alt="Block diagram showing the pathway of the crRNA" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Block diagram showing the pathway of the crRNA&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;The interplay between CRISPR and Cas proteins that impart immunity to the bacteria/archaea. Three different types of CRISPR-Cas systems have been identified by researchers: Type I, Type II, and Type III. All systems differ in their exact mechanism to generate crRNA and Cas proteins and eventually catalyse the cleavage of DNA. Our focus will be to understand Type II systems owing to their simplicity and adoption in gene-editing. It uses a single Cas protein called Cas9 and two RNA components. We have met one of these components namely crRNA. The other component is transcribed from a locus upstream of the CRISPR locus on the mother sequence. This sequence is called trans-activating CRISPR RNA (tracrRNA). The tracrRNA has a region complementary to the repeat region of the CRISPR locus and thus can bind to the transcribed crRNA (I’m simplifying details here as there’s something called a pre-crRNA which undergoes epigenetic modifications). The double stranded RNA (tracrRNA and crRNA) undergoes cleavage by a RNA nuclease called RNAIII and the crRNA-tracrRNA complex binds with a Cas9 protein to form a ribonucleo-protein (RNP) complex.&lt;/p&gt;&#10;&lt;p&gt;The Cas9 protein can be termed as an RNA-activated endonuclease as it can specifically cleaves DNA that are complementary to the associated crRNA strand. The Cas9 looks for spots along the invading DNA sequence after binding to regions on the invading plasmid DNA called a Proto-spacer Adjacent Motif or PAMs. There are different PAM sites associated with different Cas9 proteins. For example: S. pyogenes Cas9 (SpCas9) recognises a 5’-NGG-3’ PAM site and it is most commonly used for gene editing. Finding different Cas9 proteins to attach to different PAM sites gives the geneticist a versatile toolkit and increases the efficiency of the editing but we’ll get to that later.&lt;/p&gt;&#10;&lt;p&gt;In the case of SpCas9, two arginine residues bind to the guanine nucleobase on the PAM site (the two GG from NGG and N could be any nucleotide base). This interaction positions the phosphate of the DNA backbone to a phosphate-lock loop on the Cas9 and facilitates DNA unwinding. After unwinding, the crRNA forms a RNA-DNA hybrid called an R loop and then cleavage occurs. There are two domains on the Cas9 namely: HNH and RuvC-like. They individually nick one strand of the DNA resulting in a double strand break. The HNH nicks the strand complementary to the crRNA and RuvC-like nicks the other strand. This cleavage happens 3 base pairs (bp) upstream of the PAM site in case of SpCas9. As the cleavage is successful, the invading DNA is rendered harmless as it cannot be expressed effectively anymore.&lt;/p&gt;&#10;&lt;h2 id="dna-repairs-and-motivation-for-editing"&gt;DNA repairs and motivation for editing&lt;/h2&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-d4c4f390" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-d4c4f390" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/3.png" alt="Homology-Directed Repair" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-d4c4f390" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/3.png" alt="Homology-Directed Repair" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Homology-Directed Repair&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;DNA-RNA systems are highly versatile and robust. In response to a Cas9-induced double break, the organism employs machinery to repair the DNA. There are two types of DNA repair - Non-Homologous End Joining (NHEJ) and Homology-Directed Repair (HDR). NHEJ can be either canonical NHEJ (c-NHEJ) where the two ends are simply “glued” back together or alternative NHEJ (alt-NHEJ) where each strand is cut out to repair the damage. Both these pathways are highly error-prone resulting in unintentional insertions and deletions.&lt;/p&gt;&#10;&lt;p&gt;On the other hand, if there is a complementary DNA strand nearby the double break, the homologous DNA can easily bind to repair the break. This is the Homology-Directed Repair pathway. HDR is exploited to introduce engineered sequences into the genome by making the edited strand available near a double break. Thus, to edit a gene, we need to make a cleavage at the desired location which is achieved by the CRISPR-Cas9 duo by making sure the crRNA is transcribed and RNP complex is formed. Further, we check for the availability of a PAM site near the point of interest and finally, to make sure that the engineered strand is available during repair.&lt;/p&gt;&#10;&lt;p&gt;A lot of research and study has gone (and is still going) into the role and importance of the various machinery involved in gene editing. The tracrRNA seems to be important for DNA cleavage by Cas9. Additionally, instead of introducing separate tracrRNA and crRNA, researchers can introduce a single guide RNA or sgRNA. This reduces the number of components involved and makes the process simpler.&lt;/p&gt;&#10;&lt;h2 id="steps-of-a-gene-edit"&gt;Steps of a gene edit&lt;/h2&gt;&#10;&lt;p&gt;Let us investigate the steps needed for an edit in greater detail. The process can be divided into roughly three steps:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;The selection of a suitable target and guide.&lt;/li&gt;&#10;&lt;li&gt;Introduction of CRISPR-Cas9 and template DNA components into the organism.&lt;/li&gt;&#10;&lt;li&gt;Identification of the mutation.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="target-selection"&gt;Target Selection&lt;/h3&gt;&#10;&lt;p&gt;In the case of SpCas9, there is a 20-nucleotide sequence of guide RNA adjacent to the PAM site, 5’-NGG-3’. For best results, the PAM site should be as close as possible to the mutation locus. In fact, the efficiency for a mutation is inversely correlated to the number of base pairs from a PAM site. This is important because if the specificity of the target is not sufficient then the Cas9 can bind to other off-target regions and splice at unintentional locus resulting in edits that we don’t want. Factors that affect RNA efficiency is an active area of research in CRISPR research today. For example: the presence of purine bases (G or A) at the 3’ end of the 20-nucleotide guide is known to increase efficiency.&lt;/p&gt;&#10;&lt;h3 id="introduction-of-different-components"&gt;Introduction of different components&lt;/h3&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-bec73f06" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-bec73f06" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/0.png" alt="Cleavage of DNA by Cas9" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-bec73f06" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/0.png" alt="Cleavage of DNA by Cas9" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Cleavage of DNA by Cas9&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;There are primarily three forms in which we can introduce the components:&lt;/p&gt;&#10;&lt;h4 id="dna-form"&gt;DNA form&lt;/h4&gt;&#10;&lt;p&gt;In this pathway, we have to introduce the DNA sequences for sgRNA and Cas9 protein. The point being that we wholly rely on the translation and transcription facilities of the cell for the production of Cas9 protein and sgRNA. Now, as the Cas9 DNA is transcribed into its complementary mRNA, the presence of appropriate 3’ and 5’ untranslated regions (UTR) is ensured for successful translation into the protein. Lastly, a promoter must also be present upstream of the transcription site to bind a RNA polymerase. A promoter must also be present for the transcription of the sgRNA sequence by RNA polymerase III, which is a polymerase which specialises in the expression of ribosomal RNAs and tRNAs. Note that the ultimate product of the sgRNA pathway is RNA and not protein.&lt;/p&gt;&#10;&lt;h4 id="rna"&gt;RNA&lt;/h4&gt;&#10;&lt;p&gt;Instead of depending wholly on the cell to produce the required components for an edit, we can ease the burden by synthesising few components outside the cellular system. In this pathway, we introduce sgRNA and mRNA encoding Cas9 instead of DNAs for the same. However, the mRNA needs to be modified post transcription with a 5’ cap and a 3’ poly-A tail.&lt;/p&gt;&#10;&lt;h4 id="rnpprotein-complexes"&gt;RNP/Protein complexes&lt;/h4&gt;&#10;&lt;p&gt;Following the same theme, we can synthesise Cas9 itself and introduce the Cas9 complexed with tracrRNA and crRNA as the RNP complex entity. This pathway is ideal if we do not have the requirement to optimise for the species-specific UTRs and promoters. However, the purification of the protein and complexing in-vitro increases the cost of introduction of the components.&lt;/p&gt;&#10;&lt;h3 id="identification-of-the-genetic-mutation"&gt;Identification of the genetic mutation&lt;/h3&gt;&#10;&lt;p&gt;The efficacy of gene editing can only be determined if we are able to identify the desired mutation in the target genome. Identification of genetic modification is a significant bottleneck in the modern CRISPR workflow. There are two types of methods:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Screening&lt;/li&gt;&#10;&lt;li&gt;Selection&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="screening"&gt;Screening&lt;/h3&gt;&#10;&lt;p&gt;In screening for a specific phenotype, one can screen for the corresponding molecular change as a result of the edit. One particular assay is called restriction fragment length polymorphisms (RFLP) analysis. The idea being that the region of interest after a mutation is amplified by PCR and then treated with a restriction endonuclease which cuts a specific DNA sequence. If the enzyme is able to cut, then we still have the unmutated sequence and there is no edit and vice-versa. The DNA fragments can easily be visualised by gel electrophoresis.&lt;/p&gt;&#10;&lt;p&gt;However, one disadvantage is that the mutation itself can create another restriction site. There are other endonuclease-based assays such as CEL1 and bacterial T7 endonuclease that recognise and cleave single bases. They have the advantage that they do not rely on creation or destruction of restriction sites. But this process requires a large number of candidates and is thus time consuming.&lt;/p&gt;&#10;&lt;h3 id="selection"&gt;Selection&lt;/h3&gt;&#10;&lt;p&gt;Selection relies on the fact that lethal template DNA would kill the cells but the ones with the mutation will not be killed. So, a drug-resistant edit is achieved in the cells with a successful splice whereas other cells are killed by the introduction of the HDR template strand.&lt;/p&gt;&#10;&lt;h2 id="variations-of-the-core-crispr-cas9-model"&gt;Variations of the core CRISPR-Cas9 model&lt;/h2&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-123bdc6f" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-123bdc6f" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/5.jpg" alt="Expression of Green Fluorescent Protein (GFP) in lab mice" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-123bdc6f" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/crispr/5.jpg" alt="Expression of Green Fluorescent Protein (GFP) in lab mice" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Expression of Green Fluorescent Protein (GFP) in lab mice&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;In this section, let us look at other exciting applications of the CRISPR-Cas9 system. The CRISPR gene editing technique derives its utility from the specificity of the Cas9 incision. Now, we can increase the specificity of a particular cut by randomly deactivating one of the domains of the Cas9 protein. In this case, Cas9 proteins are only capable of nicking one strand on their own and it would require two Cas9 proteins to make a double strand break. This increases specificity and protects against unintentional splicing.&lt;/p&gt;&#10;&lt;p&gt;Another point of interest is the PAM site and its essential presence for the Cas9 to bind. Researchers are looking at other proteins that bind to different regions - one such candidate is Cpf1, which recognises T-rich PAM sites and cuts at a 5’ overhang resulting in a protruding ragged end. This increases the robustness of the process as biologists can now target AT rich sites as well.&lt;/p&gt;&#10;&lt;p&gt;Specificity of the CRISPR-Cas9 systems allows gene editing to be successful but the splicing is only the second step of the process of correctly identifying a sequence of interest. If we can deactivate the Cas9 proteins (called dCas9 in the business), and load a custom domain on the Cas9 then we can achieve more varied operations at a particular locus of interest. It’s like having an operating table running along the DNA strand. For example, if we fuse the dCas9 with a domain that represses transcription then we effectively silence the expression of the targeted genes. If the payload is a promoter, then we can activate transcription at the targeted site. We can introduce epigenetics modifiers such as methylators or histone modifying enzymes, all of which modulate transcription at the target site. A fluorescent protein called Green Fluorescent Protein (GFP), derived from jellyfish, has been attached as payload to the dCas9 and used to visualise target genomic sequences complementary to the sgRNA.&lt;/p&gt;&#10;&lt;h2 id="conclusion-and-ethical-concerns"&gt;Conclusion and ethical concerns&lt;/h2&gt;&#10;&lt;p&gt;We have certainly come a long way from where we started. If I have been able to even give you a gist of what CRISPR is, then I consider this post a success. All errors and bad explanations are my fault. We began this post with the chronicle of the Chinese scientists who eliminated beta-thalassaemia from non-viable human embryos. Under very close regulation, another research group edited heterozygous embryos for a mutation in the MYBPC3 gene using CRISPR. This mutation resulted in hypertrophic cardiomyopathy, which can cause sudden death.&lt;/p&gt;&#10;&lt;p&gt;We have unlocked a truly novel technology, one that allows us to reach out and rewrite who we are but at the same time, we are also accountable for what we do. I guess that is the price we pay for knowledge.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="references-and-acknowledgements"&gt;References and Acknowledgements&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Molecular Biology at the Cutting Edge: A Review of CRISPR/Cas9 Gene editing for undergraduates - Thurtle, Schmidt, Lo, et. al.&lt;/li&gt;&#10;&lt;li&gt;GCR Biosecurity curriculum: &lt;a href="https://gcr.bio/" target="_blank" rel="noopener noreferrer"&gt;Biosecurity Fundamentals&lt;/a&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=86JCMM5kb2A" target="_blank" rel="noopener noreferrer"&gt;Homology-Directed Repair animation video&lt;/a&gt;&#10; - Oxford Academics on YouTube&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Prometheus Unleashed: Making sense of information hazards</title><link>https://basillabib.com/post/prometheus-unleashed-making-sense-of-information-hazards/</link><pubDate>Wed, 15 Feb 2023 11:18:29 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/prometheus-unleashed-making-sense-of-information-hazards/</guid><description>&lt;h1 id="intro"&gt;Intro&lt;/h1&gt;&#10;&lt;p&gt;As part of my research into global existential risks, I came across the conundrum of information hazard which simply refers to the potential misuse of scientific or technological information by bad actors that might turn into a global risk for everyone.&lt;/p&gt;&#10;&lt;p&gt;This phenomenon is most aggravated in the field of biotechnological research where misuse of increasingly openly available genomic data or cheaper protein synthesis techniques can easily be used to develop and mass-produce humanity-threatening super-pathogens (“super” referring to immunity against all known forms of human cure). This is a particularly interesting conundrum as the fine line between openness and secrecy governs the difference between better scientific research and global catastrophe.&lt;/p&gt;&#10;&lt;p&gt;It is easy to say that all biotech information and research generated should be classified by labs around the world. But this conservative approach would turn dry the flow of useful information and data that is so important to inspire and facilitate future research. Although this approach guarantees safeguard against information hazard, it also means the end of all future scientific research and technological innovation. Such a scenario will leave humanity helpless in the face of attack by a naturally emerged pathogen.&lt;/p&gt;&#10;&lt;p&gt;On the other hand of the spectrum, completely free proliferation of potentially biohazardous information, if accessed by a malicious organisation or even a bad actor with the required expertise and material, can spell doom for humanity.&lt;/p&gt;&#10;&lt;p&gt;Thus, it can be concluded that the censorship on scientific information and data should lie somewhere in the grey of the spectrum. Where is that point?&lt;/p&gt;&#10;&lt;p&gt;The secrecy point question is more nuanced than it looks. For example, consider this scenario. A biotech lab publishes unsuspecting non-hazardous information. This information is accessed by a malicious group which was lacking some non-hazardous information to bring its “evil project” to fruition. In the light of the newly published (unsuspectful) data, it can now generate the required hazardous information it needs and bring its evil plan to life. Notably, this scenario is quite narrow (one can argue, if the malicious group has the resources to do independent R&amp;amp;D, then it can generate whatever information it needs independently. Or that such expertise is not known to be within the resource constraints of any known terrorist outfit). Sure, but what I’m trying to present is the subtleties and intricacies that surround such censorship and governance issues and truly calls for a departure from the binary of black and white approach&#10;taken by governance organisations today.&lt;/p&gt;&#10;&lt;h1 id="case-studies"&gt;Case studies&lt;/h1&gt;&#10;&lt;p&gt;The US government policies on dual-use research is solely restricted to work that fall under any of its seven classes of “experiments of concern” or if it involves a subset of organisms on the Federal Select Agent List. It is not hard to imagine that emerging research may not fall under this classification and yet be of catastrophic concern to humanity.&lt;/p&gt;&#10;&lt;p&gt;Or take this second scenario that seems to defy common sense. It is beneficial to publish easy-to-discover information hazardous research as it would benefit good actors and the publication of the work is indifferent for the bad actors as they would discover the information themselves sooner or later. This loudly opens a new can of worms by asking where to draw the line for “easy-to-discover”?&lt;/p&gt;&#10;&lt;p&gt;Moreover, keeping useful information hidden may lead to unintentional misuse of currently deployed technology or slowdown preventive measures in case of large-scale risks. An apt commentary on the reassessment to a publication concerning the identification of a subtype of the neurotoxic botulinum toxin reads as follows:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Although it is ethical to identify and mitigate DURC, it is also an ethical imperative to enable others to counter potential harm with good. With critical national security and public health at stake, it is unethical to impede research competitors for personal, professional, or commercial motives. Likewise, excessive government regulation is not helpful if it slows the progress of countermeasure development (Keim, 2016, p. 333).&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h1 id="incentive-games"&gt;Incentive games&lt;/h1&gt;&#10;&lt;p&gt;There are various notorious and perverse incentive games at work too. Take for instance, the decry against biological weaponry in the Geneva protocol during WW2 which informed the Japanese of the potential utility of biological weapons and eventually inspired them to develop and use their own B-weapons (is this a word?). Incentives act on all levels of society. Scaling the academia ladder forces researchers to publish their work, unfortunately often bypassing ethics and common-sense, in order to be promoted or just receive funding. They are often left at the discretion of the policy-makers or fund dispensers who have little to no idea of the potential impact of the research (both good or bad).&lt;/p&gt;&#10;&lt;h1 id="the-bigger-picture"&gt;The bigger picture&lt;/h1&gt;&#10;&lt;p&gt;The progress of scientific and technological advancements are asking more existential questions than ever before and at a rate that is simply beyond the mental grasp of the common human and barely understood by a select few. Taking a meta look at the information hazard problem, it can be summarised as the inability to infer the intention of human beings. Take for instance, the open problem in security and blockchain dubbed the “Oracle problem” - which basically asks if it is possible to validate any information generated outside the chain by the chain itself. If one examines the question carefully (the leisure of which I do not exercise in this post), it would be apparent that the fundamental problem lies at the interface between the real world and its equivalent projection in the digital space. For eg: in web2, the identity of a person is closely tied to its account on some database. This abstraction worked because the proof that that person’s existence was guaranteed by some central authority. But with the elimination of the central authority, who backs the data? How do we even define identity in such a case? The premise for the abstraction breaks down. The authentication situation is not as dire as it looks as people are trying to find workarounds but I intend to present the bigger picture here.&lt;/p&gt;&#10;&lt;h1 id="solutions"&gt;Solutions?&lt;/h1&gt;&#10;&lt;p&gt;So, we understand that this is an important issue. But what is the solution? To be honest, there doesn’t seem to be any fool-proof solution because the problem is on a different plane as noted in the previous paragraph. There are various suggestions such as assessing the bad (and good) impact of an information promulgation beforehand. Or consciously disclosing information in a way that benefits good actors while posing difficulty for bad actors to use it for malicious plans - “Security by obscurity”. Identification of entities who access a particular piece of information may be an option but I only see it eventually dwindling into authoritarian monopolistic control over the information as “someone” has to decide if the information should be made available to a particular entity or not.&lt;/p&gt;&#10;&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;&#10;&lt;p&gt;In the timeless words of Dickens, I think this is “the best of times, the worst of times”, an exciting time to ask questions, a vibrant time for research,and an amazing time to be alive. The answers we give to ourselves in this century may potentially decide our fate.&lt;/p&gt;&#10;&lt;h1 id="acknowledgements-and-references"&gt;Acknowledgements and References&lt;/h1&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Lewis, et. al - Information Hazards in Biotechnology, 2019&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>A deep dive into JSON Web Tokens (JWTs)</title><link>https://basillabib.com/post/a-deep-dive-into-json-web-tokens-jwts/</link><pubDate>Mon, 22 Nov 2021 08:03:03 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/a-deep-dive-into-json-web-tokens-jwts/</guid><description>&lt;aside class="toc-box"&gt;&#10; &lt;p class="toc-box-title"&gt;Contents&lt;/p&gt;&#10; &lt;nav id="TableOfContents"&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#what-is-jwt"&gt;What is JWT?&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#why-use-jwt"&gt;Why use JWT?&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#why-understand-it"&gt;Why understand it?&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#how-do-jwts-work"&gt;How do JWTs work?&lt;/a&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#user-authentication-walkthrough"&gt;User authentication: Walkthrough&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10; &lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#structure-of-a-jwt"&gt;Structure of a JWT&lt;/a&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#the-header"&gt;The header&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#the-payload"&gt;The payload&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10; &lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#hash-functions"&gt;Hash functions&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#hmac--sha256--hs256"&gt;HMAC + SHA256 = HS256&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#rsa--sha256--rs256"&gt;RSA + SHA256 = RS256&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#other-algorithms"&gt;Other algorithms&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#common-pitfalls-and-mitigation-techniques"&gt;Common pitfalls and mitigation techniques&lt;/a&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#unsecured-tokens-via-alg-none"&gt;Unsecured tokens via &lt;code&gt;alg: none&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#signature-stripping"&gt;Signature stripping&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#cross-site-resource-forgery-csrf"&gt;Cross Site Resource Forgery (CSRF)&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#cross-site-scripting-xss"&gt;Cross Site Scripting (XSS)&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10; &lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#references"&gt;References&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#license"&gt;License&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10; &lt;/li&gt;&#10; &lt;/ul&gt;&#10;&lt;/nav&gt;&#10;&lt;/aside&gt;&#10;&#10;&lt;h2 id="what-is-jwt"&gt;What is JWT?&lt;/h2&gt;&#10;&lt;p&gt;A JSON Web Token or a JWT (pronounced &lt;em&gt;&amp;ldquo;jot&amp;rdquo;&lt;/em&gt;) is a plaintext, human readable piece of data that enables authentication and trust between two unknown entities.&lt;/p&gt;&#10;&lt;h2 id="why-use-jwt"&gt;Why use JWT?&lt;/h2&gt;&#10;&lt;p&gt;There are alternative competing standards such as &lt;a href="https://en.wikipedia.org/wiki/Security_Assertion_Markup_Language" target="_blank" rel="noopener noreferrer"&gt;SAML (Security Assertion Markup Language)&lt;/a&gt;&#10; that cater to the same needs like a JWT but JWT has certain advantages that has made it quite common, almost &lt;em&gt;de facto&lt;/em&gt; in securing modern web applications.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;It is &lt;strong&gt;stateless&lt;/strong&gt;. It means the server or the client don&amp;rsquo;t have to maintain any piece of data in-memory in between requests for it to work.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;It can be &lt;strong&gt;delegated&lt;/strong&gt;. It means the task of generating and issuing a JWT can be completely disjoint from the core application itself, which can be delegated the task of only validated them. Less complexity leads to more robust and bug-free applications.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;It is &lt;strong&gt;simple&lt;/strong&gt;. In terms of the complexity of data (and hence the size of a JWT) which makes them ideal for low-bandwidth regions.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;It is &lt;strong&gt;efficient&lt;/strong&gt; to compute. A JWT is generated and validated. Both these processes can take place without considerable constraints on resources, making them ideal for hardware critical applications.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="why-understand-it"&gt;Why understand it?&lt;/h2&gt;&#10;&lt;p&gt;Most developers and builders can get away without truly understanding the underlying mathematics or algorithms that makes JWT works and that&amp;rsquo;s. . . fine. There are tons of battle-tested, production ready libraries that enable JWT generation and validation, even third-party services like &lt;a href="https://auth0.com" target="_blank" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt;&#10;, to whom one can delegate the entire process of authenticaing via JWT for their applications.&lt;/p&gt;&#10;&lt;p&gt;But, superficial knowledge gets one only so far. A thorough understanding of this technology will equip you to make sound decisions when designing an architecture or troubleshoot elusive JWT errors. It will empower you with a sense of self-assurance and confidence that one gets by mastery.&lt;/p&gt;&#10;&lt;h2 id="how-do-jwts-work"&gt;How do JWTs work?&lt;/h2&gt;&#10;&lt;p&gt;JWTs combine concepts from cryptography and web technologies to create a standard that allows user authentication and session management among other things. In general, it allows one to say with confidence about the authenticity of a piece of data. This has been adapted to provide security on the modern web.&lt;/p&gt;&#10;&lt;p&gt;A JWT is human readable, encoded JSON objects concatenated by periods (&lt;code&gt;.&lt;/code&gt;). Here&amp;rsquo;s an example (newline added for readability):&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.&#10;eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.&#10;TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are three pieces of strings:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Header&lt;/li&gt;&#10;&lt;li&gt;Payload&lt;/li&gt;&#10;&lt;li&gt;Signature&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;The header and payload are Base64-URL encoded JSON objects. Their contents can be decoded and read. Note: &lt;strong&gt;A JWT is not encrypted&lt;/strong&gt;. However, there are standards for encryption &lt;a href="https://tools.ietf.org/html/rfc7516" target="_blank" rel="noopener noreferrer"&gt;(JWE) RFC 7516&lt;/a&gt;&#10;. A JWT with only the header and payload is called an &lt;em&gt;unsecured JWT&lt;/em&gt; and is generally not used except to store data on the client side in special use cases.&lt;/p&gt;&#10;&lt;p&gt;The signature is used by applications to verify the authenticity of the payload. There are many recommended algorithms to achieve the same but the most popular are &lt;code&gt;HS256&lt;/code&gt; (&lt;code&gt;HMAC&lt;/code&gt; and &lt;code&gt;SHA-256&lt;/code&gt;) and &lt;code&gt;RS256&lt;/code&gt; (&lt;code&gt;RSA&lt;/code&gt; and &lt;code&gt;SHA-256&lt;/code&gt;). Signed JWTs are defined in &lt;a href="https://tools.ietf.org/html/rfc7515" target="_blank" rel="noopener noreferrer"&gt;RFC 7515&lt;/a&gt;&#10;. We will look at signing and verifying later.&lt;/p&gt;&#10;&lt;h3 id="user-authentication-walkthrough"&gt;User authentication: Walkthrough&lt;/h3&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-939951e3" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-939951e3" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/jwt/0.png" alt="User authentication workflow using JWTs." loading="lazy" class="zoomable-image" style="max-width:400px;" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-939951e3" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/jwt/0.png" alt="User authentication workflow using JWTs." class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;User authentication workflow using JWTs.&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;In this section, we will walkthrough a process of user authentication using JWT. The outlined process is agnostic of the algorithm used upon which the process might slightly differ.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;The user client (browser or app) makes a HTTP request to the authorization server with the user (suppose, Alice) credentials (maybe a login form). This should be secure preferably via TLS (Transport Layer Security).&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;The authorization server may perform necessary checks to be sure that the user is in fact who it poses to be. This maybe achieved by querying a database or via some other method like Single Sign-On (SSO).&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;The authorization server generates a signed JWT with the header specifying &lt;code&gt;alg&lt;/code&gt; and &lt;code&gt;typ&lt;/code&gt; and payload containing some unique identification for this user. The misuse of this identifier shouldn&amp;rsquo;t pose a secuirty risk, however, it should be adequate for our application to identify the user.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Authorization server issues the token to the client.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;User requests resources from the application with authorization header set to &lt;code&gt;Bearer ${token}&lt;/code&gt;.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Application verify the token using a secret or a public key based on which algorithm is used (See below). Choosing the corresponding PUB key can be based on the &lt;code&gt;iss&lt;/code&gt; field in the payload and maybe an app managed database of public keys.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Application can reject the request or respond with the requested resources based on the result of the validation.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;It should be noted that all data flows through the user client in the form of parameters to the request. On a browser, this should be the key-value pairs after &lt;code&gt;?&lt;/code&gt; in the URL bar. Moreover, the transaction is completely stateless. To initiate another request, the client can use the token within its &lt;code&gt;exp&lt;/code&gt; period and avail the services. The application will undergo the validation process all over again.&lt;/p&gt;&#10;&lt;p&gt;To mitigate many secuirty risks, the &lt;code&gt;exp&lt;/code&gt; period is kept as small as possible, however, it is not convenient to login for each single request. This gave rise to the concept of &lt;strong&gt;session management&lt;/strong&gt; and &lt;code&gt;access tokens&lt;/code&gt; and &lt;code&gt;refresh tokens&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Refresh tokens have a longer &lt;code&gt;exp&lt;/code&gt; time than access tokens and can be used in lieu of user credentials to ask for new access tokens when the previous ones expire.&lt;/p&gt;&#10;&lt;h2 id="structure-of-a-jwt"&gt;Structure of a JWT&lt;/h2&gt;&#10;&lt;h3 id="the-header"&gt;The header&lt;/h3&gt;&#10;&lt;p&gt;The header is a plain JSON object having atleast one field &lt;code&gt;alg&lt;/code&gt; which specifies which algorithm is to be used to verify it. Another is a &lt;code&gt;typ&lt;/code&gt; field which is the &lt;a href="http://www.iana.org/assignments/media-types/media-types.xhtml" target="_blank" rel="noopener noreferrer"&gt;media type&lt;/a&gt;&#10; of the JWT itself. For most practical purposes, its value is &lt;code&gt;JWT&lt;/code&gt;. There may be other fields (or &lt;em&gt;&amp;ldquo;claims&amp;rdquo;&lt;/em&gt;) but these are necessary. A sample header:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;{&#10; &amp;#34;alg&amp;#34;: &amp;#34;RS256&amp;#34;,&#10; &amp;#34;typ&amp;#34;: &amp;#34;JWT&amp;#34;&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But wait. This doesn&amp;rsquo;t look like what we saw earlier. What&amp;rsquo;s going on? The header and payload are actually encoded by a scheme to the most common subset of UTF-8 characters to ensure portability and avoid garbled text, a sad product of multiple encoding schemes. The chosen method is &lt;code&gt;Base64URL&lt;/code&gt;, a URL safe variation of &lt;code&gt;Base64&lt;/code&gt; which swaps &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;+&lt;/code&gt; characters by &lt;code&gt;_&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt; respectively.&lt;/p&gt;&#10;&lt;p&gt;&lt;a name="alg"&gt;&lt;/a&gt; The &lt;code&gt;alg&lt;/code&gt;, if set to &lt;code&gt;none&lt;/code&gt;, can become a security threat for some verification libraries. The reason being, in the past many verification libraries relied on the &lt;code&gt;alg&lt;/code&gt; field to pick up the right algorithm. This was the result of ambuiguity in the implementation of verification libraries. To migitate this issue, never use &lt;code&gt;alg: none&lt;/code&gt; and never allow &lt;em&gt;unsecured&lt;/em&gt; JWTs in your application. Onwards to the payload.&lt;/p&gt;&#10;&lt;h3 id="the-payload"&gt;The payload&lt;/h3&gt;&#10;&lt;p&gt;The payload is another JSON that is the data store of the token. All claims are stored in the payload. There are some &lt;em&gt;registered&lt;/em&gt; claims whose field names have been standardized.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;iss&lt;/code&gt;: The &lt;em&gt;issuer&lt;/em&gt;. A URI string identifying the authority issuing the JWT. The data can be application specific.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;sub&lt;/code&gt;: The &lt;em&gt;subject&lt;/em&gt;. A URI string identifying the entity that this JWT carries information about. The data can be application specific.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;aud&lt;/code&gt;: The &lt;em&gt;audience&lt;/em&gt;. A list of URIs intended as recipients for this JWT.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;exp&lt;/code&gt;: The &lt;em&gt;expiration date&lt;/em&gt;. Expressed in number of seconds passed since Epoch.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;nbf&lt;/code&gt;: &lt;em&gt;not before (time)&lt;/em&gt;. The opposite of the &lt;code&gt;exp&lt;/code&gt; claim. Expressed in number of seconds since Epoch.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;iat&lt;/code&gt;: The &lt;em&gt;issued at (time)&lt;/em&gt;. Expressed in number of seconds since Epoch.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;code&gt;jti&lt;/code&gt;: The &lt;em&gt;JWT ID&lt;/em&gt;. A unique identifier of this JWT. Again, it is application specific.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Apart from the registered claims, there are so-called &lt;strong&gt;private claims&lt;/strong&gt; which are defined by users and add application specific communication to the JWT. Namespaces may be used to avoid key collision. &lt;strong&gt;Public claims&lt;/strong&gt; are either registered with the &lt;a href="https://tools.ietf.org/html/rfc7519#section-10.1" target="_blank" rel="noopener noreferrer"&gt;IANA JSON Web Token registry&lt;/a&gt;&#10; (central registry to prevent name collisions) or named using collision resistant methods.&lt;/p&gt;&#10;&lt;p&gt;Now, let us take a brief interlude to understand hash functions and then examine how signature works.&lt;/p&gt;&#10;&lt;h2 id="hash-functions"&gt;Hash functions&lt;/h2&gt;&#10;&lt;p&gt;&lt;em&gt;Hashing&lt;/em&gt; is a computational operation that produces a fixed length string from a data of arbitrary size. Moreover, hashing has certain interesting properties which makes them ideal for cryptographic applications.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Irreversibility&lt;/strong&gt;: Hash functions are one-way functions. The original data cannot be deduced from the output. The only way (known so far) is to brute force each possible data to match the output.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Reproducibility&lt;/strong&gt;: Hash functions are one-one functions. A piece of data is always guaranteed to produce the same output every single time.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Uniqueness&lt;/strong&gt;: By choosing proper parameters, it is possible to reduce collisions to practically zero. Collisions happen when two different data match to the same output.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Unpredictability&lt;/strong&gt;: Even if a single byte is changed in the original data, atleast 50% of the bits changes. This makes it very difficult to guess the data by cunning or intelligent guesses. See &lt;a href="https://en.wikipedia.org/wiki/Avalanche_effect" target="_blank" rel="noopener noreferrer"&gt;Avalanche effect&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Let us examine a popular hash function &lt;code&gt;SHA-256&lt;/code&gt;. The Secure Hash Algorithm (SHA) family is defined in &lt;a href="http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf" target="_blank" rel="noopener noreferrer"&gt;FIPS-180&lt;/a&gt;&#10;. We are going to use the native node module &lt;code&gt;crypto&lt;/code&gt; to generate a SHA256 hash.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// code source: https://nodejs.org/api/crypto.html&#10;const {&#10; createHash&#10;} = await import(&amp;#39;crypto&amp;#39;);&#10;&#10;const hash = createHash(&amp;#39;sha256&amp;#39;);&#10;&#10;hash.on(&amp;#39;readable&amp;#39;, () =&amp;gt; {&#10; // Only one element is going to be produced by the&#10; // hash stream.&#10; const data = hash.read();&#10; if (data) {&#10; console.log(data.toString(&amp;#39;hex&amp;#39;));&#10; // Prints:&#10; // 6a2da20943931e9834fc12cfe5bb47bbd9ae43489a30726962b576f4e3993e50&#10; }&#10;});&#10;&#10;hash.write(&amp;#39;some data to hash&amp;#39;);&#10;hash.end();&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="hmac--sha256--hs256"&gt;HMAC + SHA256 = HS256&lt;/h2&gt;&#10;&lt;p&gt;We have all the components to actually sign and verify a JWT. As it has been noted, the issuing authority can be seperate and oblivious of the identity of the verifier. This is an advantage of JWT. The question is: how do we achieve that? HMAC or Hash-Based Message Authentication Code works this way:&lt;/p&gt;&#10;&lt;p&gt;Generation of token:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Take the Header (H), Payload (P), and a secret data (SECRET). Concatenate them together and pass through a hash function.&lt;/li&gt;&#10;&lt;li&gt;Let the output be HASH. Append this as the signature or Message Authentication Code (MAC) for our JWT.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Validation of token:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Take H, P, and the &lt;strong&gt;same&lt;/strong&gt; SECRET and produce a hash.&lt;/li&gt;&#10;&lt;li&gt;If the contents of the payload had not been tampered with, then MAC and this hash will match.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;This scheme is very simple. However, there is an obvious disadvantage: The token issuer and the validation application have to share a common secret which is essentially the crux of security. Requirement of distribution across a large or insecure network makes regular key rotation unfeasible. Moreover, since the SECRET is stored on many nodes in the network, there is more possibility of a SECRET leak or susceptibility to an attack. Public-key cryptography addresses this issue.&lt;/p&gt;&#10;&lt;h2 id="rsa--sha256--rs256"&gt;RSA + SHA256 = RS256&lt;/h2&gt;&#10;&lt;p&gt;HS256 suffers from the downside of being unable to distinguish between token generation and token validation. Both are essentially the same process. RSA public key cryptography is a scheme that involves 2 keys: a private key and a public key. The private key can sign a piece of data while a public key can definitively validate if the associated private key had signed the data, hence establishing data integrity. Moreover, compromise of the public key does not diminish the security of the scheme. The private key can be safely stored &lt;strong&gt;only&lt;/strong&gt; with the token generation authority.&lt;/p&gt;&#10;&lt;p&gt;Tools like &lt;a href="https://www.openssl.org" target="_blank" rel="noopener noreferrer"&gt;OpenSSL&lt;/a&gt;&#10; can generate these keys for you or even online services such as &lt;a href="http://travistidwell.com/jsencrypt/demo/" target="_blank" rel="noopener noreferrer"&gt;this one&lt;/a&gt;&#10;. An example 1024-bit RSA key pair:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;-----BEGIN PUBLIC KEY-----&#10;MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgFpZk5mSyh3rLS/G5+5dWqHAmXJh&#10;vUgIbRh9UXQIoOMe+NOd+9Uu8koI1FP7whsSY5jdwR8IFxaSwOsqcaHZJY0E4S8v&#10;f5mI3GexfoNG4sI6C1g4nk40NWpfpifhEERquHp9C2CdLkGLAlRrAhgkdbmhseWr&#10;ZmwgrzULeSIlM/k/AgMBAAE=&#10;-----END PUBLIC KEY-----&#10;&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;-----BEGIN RSA PRIVATE KEY-----&#10;MIICWwIBAAKBgFpZk5mSyh3rLS/G5+5dWqHAmXJhvUgIbRh9UXQIoOMe+NOd+9Uu&#10;8koI1FP7whsSY5jdwR8IFxaSwOsqcaHZJY0E4S8vf5mI3GexfoNG4sI6C1g4nk40&#10;NWpfpifhEERquHp9C2CdLkGLAlRrAhgkdbmhseWrZmwgrzULeSIlM/k/AgMBAAEC&#10;gYA3LAQp/61U38vUJQ50TKWPqOELLAw99IdKMRyQUsSMbNldwFeMMwLZmtKuBkrd&#10;5TjUnHXV0UboDe2orV5+iu+WlZAsWb7p2phjaOEhR0oWpzZELomVSMRPLMyyRzIw&#10;75kopGYuo/G63ciytbIAVH5R+z8VwJAtq6xykDIxe61HAQJBALJJavLsWqudnA2G&#10;Dgvqa3StiQXmT0Jno0nxuJtm0IxfV5rEVsrCJ94hAplidbRgV15NI03MSnsiZSko&#10;/SzCxd8CQQCBu3/oELpQF4gosBvtpks/lzSRyhzd1eou70P6sDt9DV76cHhe5Mb0&#10;edd/JsXv2V2y3sc6vW1v2sMPi+E6q3ihAkEAp3T2Ez2U3GSpUrp7Lw376Bem//eW&#10;spSNdsKn1q73ayGSMzf/HWrH74ep3U/xpn5nEZf6dPRRcN6ZjdOGGSjImQJAbImg&#10;E1mbZtKpOdFNLGCKaCNq+eYsjHQhvy6yoxO6UX9UTRa3vjyNL5CYeGrKA0bKG5JD&#10;AKPm4UQXGfSfdORswQJAPezFk2XrA0+mX7dkgik6RhOVYmdNzvreL7jarS+FsL6X&#10;WNUbh8tGmrG0BmP8qKI5Tf2iidpJ2ru810QGwaC8fw==&#10;-----END RSA PRIVATE KEY-----&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Token generation:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;A RSA key pair is generated (PVT and PUB). The PUB is made public.&lt;/li&gt;&#10;&lt;li&gt;Header (H), Payload (P) is passed through a hash function (SHA256, in our case). Output is HASH.&lt;/li&gt;&#10;&lt;li&gt;The HASH is signed with the PVT key to produce SIGNED.&lt;/li&gt;&#10;&lt;li&gt;SIGNED is attached as the JWT signature and issued to a client.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Validation:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;H and P are used to generate a hash. Call it HASH2.&lt;/li&gt;&#10;&lt;li&gt;Using PUB, it decrypts the SIGNED to produce HASH.&lt;/li&gt;&#10;&lt;li&gt;The integrity of the data is established if HASH matches HASH2.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;We can deduce authentication because it is guaranteed that just decrypted SIGNED by the PUB key must have been signed by the associated PVT key which is (ideally) only possessed by the issuing server. The issuing server will only issue the token if it is satisfied with the user credentials. Thus, the user possessing the token and posing as the user to our application must be no one else but that user.&lt;/p&gt;&#10;&lt;p&gt;One might question: Why not encrypt the header and payload directly and attach that as a signature. That is a valid question. In fact, such an approach would work in theory. It is not employed in practice because the RSA encryption process can be slow for larger payload sizes. Hashing is a relatively faster computational operation and produces fixed length smaller payloads for RSA to consume.&lt;/p&gt;&#10;&lt;h2 id="other-algorithms"&gt;Other algorithms&lt;/h2&gt;&#10;&lt;p&gt;There are a plethora of other algorithms that may be used. One of them that is specified in the spec and not discussed here is &lt;a href="https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm" target="_blank" rel="noopener noreferrer"&gt;Elliptic Curve Digital Signature Algorithm (ECDSA)&lt;/a&gt;&#10; leading to ES256. Further, there are variations based on the key or block size in the SHA family or the public-key crypto scheme. For a thorough discussion, see the &lt;a href="https://auth0.com/e-books/jwt-handbook" target="_blank" rel="noopener noreferrer"&gt;JWT Handbook&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;h2 id="common-pitfalls-and-mitigation-techniques"&gt;Common pitfalls and mitigation techniques&lt;/h2&gt;&#10;&lt;p&gt;If you want to develop secure JWT-enabled applications, you must be aware of the common pitfalls that may allow attackers to exploit the weakness of your system. Moreover, we shall discuss some mitigation techniques to safeguard your application. It is &lt;strong&gt;highly recommended&lt;/strong&gt; to use tested, production-ready libraries and allow oneself to thoroughly understand the nuances of the provided API. It is generally a bad idea to roll out an in-house crypto module.&lt;/p&gt;&#10;&lt;h3 id="unsecured-tokens-via-alg-none"&gt;Unsecured tokens via &lt;code&gt;alg: none&lt;/code&gt;&lt;/h3&gt;&#10;&lt;p&gt;You must note that JWTs are &lt;em&gt;unencrypted&lt;/em&gt; in general. JWE encrypts only the payload, the header is always in unenrypted form. Thus, due to ambiguity in specification, an attackers crafted JWT having &lt;code&gt;alg: none&lt;/code&gt; can be interpreted as valid because of reliance of choosing the algorithm based on that field.&lt;/p&gt;&#10;&lt;p&gt;For more, see &lt;a href="#alg"&gt;this paragraph&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;h3 id="signature-stripping"&gt;Signature stripping&lt;/h3&gt;&#10;&lt;p&gt;A JWT, as you know, is made of three components – header, payload, and signature. A common method of attack is to strip the signature, manipulate the contents of the payload and mark the token as &amp;ldquo;unsecured&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;To mitigate signature stripping attacks, make sure your application rejects all signature-less tokens as invalid.&lt;/p&gt;&#10;&lt;h3 id="cross-site-resource-forgery-csrf"&gt;&lt;a href="https://owasp.org/www-community/attacks/csrf" target="_blank" rel="noopener noreferrer"&gt;Cross Site Resource Forgery (CSRF)&lt;/a&gt;&#10;&lt;/h3&gt;&#10;&lt;p&gt;This attack is effective when JWTs are stored as session cookies on the browser. The attacker embeds a crafted link to the target site on a malicious page. When the browser requests the resource, it sends the cookie too, if the user had previously logged into target. If the target doesn&amp;rsquo;t implement &lt;a href="https://owasp.org/www-community/attacks/csrf" target="_blank" rel="noopener noreferrer"&gt;CSRF&lt;/a&gt;&#10; mitigation on the server side, the potentially modified token can be deemed valid.&lt;/p&gt;&#10;&lt;p&gt;Short-lived tokens, not storing JWTs as cookies, and implementing CSRF mitigation on the application are used to tackle CSRF.&lt;/p&gt;&#10;&lt;h3 id="cross-site-scripting-xss"&gt;&lt;a href="https://owasp.org/www-community/attacks/xss/" target="_blank" rel="noopener noreferrer"&gt;Cross Site Scripting (XSS)&lt;/a&gt;&#10;&lt;/h3&gt;&#10;&lt;p&gt;This is similar to SQL injection attacks where the attack takes advantage of improper user data validation on the back end, to inject JavaScript into the trusted sites. It then attempts to harvest JWTs from cookies or local storage. Any data received from a user must be sanitized.&lt;/p&gt;&#10;&lt;h2 id="references"&gt;References&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Blog post: &lt;a href="https://blog.angular-university.io/angular-jwt/" target="_blank" rel="noopener noreferrer"&gt;JWT: The Complete Guide to JSON Web Tokens&lt;/a&gt;&#10; by Angular University (Good beginner introduction).&lt;/li&gt;&#10;&lt;li&gt;Book: &lt;a href="https://auth0.com/e-books/jwt-handbook" target="_blank" rel="noopener noreferrer"&gt;The JWT Handbook&lt;/a&gt;&#10; by Auth0 (In-depth treatment of algorithms, See Chapter 7).&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="license"&gt;License&lt;/h2&gt;&#10;&lt;p&gt;All diagrams and code used in this article is produced by the author unless otherwise noted and is licensed under &lt;a href="https://creativecommons.org/licenses/by-nc-sa/3.0/" target="_blank" rel="noopener noreferrer"&gt;CC BY-NC-SA 3.0&lt;/a&gt;&#10;. This means you are free to share, adapt, or modify it but you should provide credit and not use the contents for commercial purposes.&lt;/p&gt;&#10;</description></item><item><title>Hacktoberfest and open source</title><link>https://basillabib.com/post/hacktoberfest-and-open-source/</link><pubDate>Thu, 21 Oct 2021 06:22:07 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/hacktoberfest-and-open-source/</guid><description>&lt;aside class="toc-box"&gt;&#10; &lt;p class="toc-box-title"&gt;Contents&lt;/p&gt;&#10; &lt;nav id="TableOfContents"&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#the-philosophy-of-open-source"&gt;THE PHILOSOPHY OF OPEN SOURCE&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#people-who-write-open-source"&gt;PEOPLE WHO WRITE OPEN SOURCE&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#the-psychology-of-a-hacker"&gt;THE PSYCHOLOGY OF A HACKER&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#why-is-open-source-the-future"&gt;WHY IS OPEN SOURCE THE FUTURE?&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#why-go-open"&gt;WHY GO OPEN?&lt;/a&gt;&#10; &lt;ul&gt;&#10; &lt;li&gt;&lt;a href="#personal-experience"&gt;PERSONAL EXPERIENCE&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10; &lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#little-history"&gt;LITTLE HISTORY&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#further-reading"&gt;FURTHER READING&lt;/a&gt;&lt;/li&gt;&#10; &lt;li&gt;&lt;a href="#bibliography"&gt;BIBLIOGRAPHY&lt;/a&gt;&lt;/li&gt;&#10; &lt;/ul&gt;&#10; &lt;/li&gt;&#10; &lt;/ul&gt;&#10;&lt;/nav&gt;&#10;&lt;/aside&gt;&#10;&#10;&lt;p&gt;DigitalOcean, a cloud service provider annually hosts &lt;a href="https://hacktoberfest.digitalocean.com" target="_blank" rel="noopener noreferrer"&gt;Hacktoberfest&lt;/a&gt;&#10;, a month-long celebration of open source culture where developers from around the globe contribute to open projects. It is in its 8th iteration this year and has become quite an attraction for programmers, young and experienced.&lt;/p&gt;&#10;&lt;p&gt;It is unfortunate that most people don&amp;rsquo;t really understand what open source is. This post will talk about open source, the people, beliefs, and psychology behind it and how it has changed our lives.&lt;/p&gt;&#10;&lt;h2 id="the-philosophy-of-open-source"&gt;THE PHILOSOPHY OF OPEN SOURCE&lt;/h2&gt;&#10;&lt;p&gt;Open source is a philosophy, a collection of ideas that have evolved into a culture. That philosophy is grounded in freedom and liberty. The founder of the GNU project and eventually one of the pioneers of open source, Richard Stallman is famously quoted,&lt;/p&gt;&#10;&lt;blockquote class="pullquote"&gt;&amp;ldquo;Think free speech, not free beer.&amp;rdquo;&lt;br&gt;&#10;~Richard Stallman&lt;/blockquote&gt;&#10;&#10;&lt;p&gt;This succint definition captures the essence of what open or &lt;em&gt;&amp;ldquo;libre&amp;rdquo;&lt;/em&gt; (from French meaning &amp;ldquo;freedom&amp;rdquo;) means. Open source is not a free commodity for anyone to use (though it is that in some plane of thought) but it is much more than that. It is an expression of defiance to corporations and closed systems. Crafty people have, in all eras, attempted to dominate the masses. With software, such ambitions have become more absolute and sustainable because software affects so much of our lives and will continue to do so with increasing fervour. Open source is denial and decentralization of that power back to the people. However, modern corporations have, instead of opposing, &lt;em&gt;embraced&lt;/em&gt; open source which is an interesting compromise.&lt;/p&gt;&#10;&lt;h2 id="people-who-write-open-source"&gt;PEOPLE WHO WRITE OPEN SOURCE&lt;/h2&gt;&#10;&lt;p&gt;As the computing era was underway in the 60s, people obviously realised the potential computing carried. It was one of those mega money-making invention that rarely blessed mankind. However, there was one group who believed freedom should extend to software too. They were romantic cyberpunks who believed that no single group should be allowed to dictate what and how software was created. This was a group of misfits, and troublemakers. They were non-conformists and brilliant. They broke things often and fixed them, often infusing their own ideas and ideals. They were also, what is now popularized as, a &amp;ldquo;nerd&amp;rdquo; or a &amp;ldquo;geek&amp;rdquo;. But unlike the popular &amp;ldquo;geek&amp;rdquo; image painted by media, they were agile and vigilant and receptive to the needs of the society. They were not careless about their expertise and knew what power they yielded. They were responsible, persistent and infinitely curious. These visionaries were called hackers. To be called a hacker is a badge of honour and not of disgust. It is unfortunate that media has rallied this pure word to mark a certain section of malicious actors who use their skills mostly for destruction and chaos. That is a distortion of the real meaning of a hacker.&lt;/p&gt;&#10;&lt;h2 id="the-psychology-of-a-hacker"&gt;THE PSYCHOLOGY OF A HACKER&lt;/h2&gt;&#10;&lt;p&gt;If someone is really interested, they may ask the question: Why do hackers behave the way they do? Why do they see it more befitting to give away their code for free, instead of making money and getting rich of it? Naturally, that is what people in other fields would expect and choose to do.&lt;/p&gt;&#10;&lt;p&gt;So, here&amp;rsquo;s the thing: The peculiarity of Computer Science is that it is a highly specialized field. Most people just &amp;ldquo;leave it to the experts&amp;rdquo; and unlike fields like personal finance where people &lt;em&gt;have&lt;/em&gt; to &amp;ldquo;get their hands dirty&amp;rdquo; and learn a thing or two, they can simply forget about what goes inside the black box of their systems and assume everything works fine. Nobody &lt;em&gt;bothers&lt;/em&gt; to understand what programmers and software writers do, let alone appreciate the art of writing code.&lt;/p&gt;&#10;&lt;p&gt;This leaves hackers in an awkward position. It&amp;rsquo;s a psychological response. This group of people seek appreciation from people like them and because they are few in number, they share a special and unique sense of camaraderie which they try to consolidate. Being blissfully unaware of social norms and conventions, they communicate what they are truly passionate about: their code and their visions. The freedom to exchange ideas and distribute code freely is an offshoot of this process. It is the cry of a brilliant dreamer to seek out appreciation from the small community who, they are sure, will understand his visions and his works.&lt;/p&gt;&#10;&lt;p&gt;Not for a moment believe that what I have outlined is anything short of a superhuman power. How many professionals would choose to put the good of community above their own selfish gain?&lt;/p&gt;&#10;&lt;p&gt;For more on hackers and their beliefs, see &lt;a href="http://www.catb.org/~esr/faqs/hacker-howto.html" target="_blank" rel="noopener noreferrer"&gt;here&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;h2 id="why-is-open-source-the-future"&gt;WHY IS OPEN SOURCE THE FUTURE?&lt;/h2&gt;&#10;&lt;p&gt;Open source is the culture that nurtures thinkers, creators, and hackers. As Paul Graham, worked on LISP, founder of Y Combinator (a tech startup incubator), wrote in this interesting essay &lt;a href="http://paulgraham.com/hp.html" target="_blank" rel="noopener noreferrer"&gt;&amp;ldquo;Hackers And Painters&amp;rdquo;&lt;/a&gt;&#10;,&lt;/p&gt;&#10;&lt;blockquote class="pullquote"&gt;You can&amp;rsquo;t do anything really well unless you love it, and if you love to hack you&amp;rsquo;ll inevitably be working on projects of your own.&lt;br&gt;&#10;~Hackers and Painters (Essay)&lt;/blockquote&gt;&#10;&#10;&lt;p&gt;One might scorn in disgust as they use an open software and complain that it is suboptimal or that it doesn&amp;rsquo;t look good. It is because either 1) That software is fairly new so it doesn&amp;rsquo;t have much community yet 2) You have completely missed the point. Freedom of expression is most important to hackers. It is the space of infinite creativity where they can realize their wildest dreams. If you don&amp;rsquo;t like the way a software is built, they give you the opportunity to change it yourself. This choice of having an actionable opinion in what one uses is non-existent in commercial software. This is also one of the reasons why software written by hackers are often choked full of nifty, geeky shortcuts to literally everything, many a times, to the disgust of their corporate employers. It is because they don&amp;rsquo;t write code to please their employees. Hackers intentionally design their software to be modular like a jigsaw puzzle. It is perhaps because they understand most intimately, of all people, the high of writing great software and the need to communicate ideas.&lt;/p&gt;&#10;&lt;blockquote class="pullquote"&gt;&amp;ldquo;Sharing knowledge is the most fundamental act of friendship. Because it is a way you can give something without loosing something.&lt;br&gt;&#10;~R. Stallman&lt;/blockquote&gt;&#10;&#10;&lt;h2 id="why-go-open"&gt;WHY GO OPEN?&lt;/h2&gt;&#10;&lt;p&gt;Creators and builders understand the need to share their ideas without any hindrance. It leverages different perspectives, minds, and hands which eventually enables software of far superior quality as opposed to a commerical alternative. Take for example, the GNU project, Emacs, Vim, git, Linux, BSD, Blender, Python and so many others. Decentralized fintech such as Bitcoin and Ethereum which are challenging the financial tapestry of our society. Even recent corporate projects have been opened such as Facebook&amp;rsquo;s React framework.&lt;/p&gt;&#10;&lt;p&gt;People believe that if one has to pay for something, &lt;em&gt;then&lt;/em&gt; the thing they are paying for must be a valuable product worth the value set by the seller. This is natural herd psychology yet, it&amp;rsquo;s a fallacy. The price doesn&amp;rsquo;t determine the value. They are &lt;em&gt;not&lt;/em&gt; causative events, in economic jargon.&lt;/p&gt;&#10;&lt;p&gt;When code is scrutinized by thousands of peering eyes instead of a couple hundered inside a closed organization, it naturally leads to more bug-free, robust, and versatile systems. Imagine the marvel that is the development story of Linux. Torvalds admits, in his autobiography, to have manually applied the patches (sent as tarballs from strangers over the internet) into his codebase! And somehow it worked.&lt;/p&gt;&#10;&lt;blockquote class="pullquote"&gt;&amp;ldquo;Software is like sex: It&amp;rsquo;s better when it&amp;rsquo;s free&amp;rdquo;. &lt;br&gt;&#10;Linus Torvalds.&lt;/blockquote&gt;&#10;&#10;&lt;h3 id="personal-experience"&gt;PERSONAL EXPERIENCE&lt;/h3&gt;&#10;&lt;p&gt;I wouldn&amp;rsquo;t have started my fascinating journey into software, had it not been for open source. I started out by literally pulling out and reading source code of repositories on Github! I can see fairly clearly the self-reinforcing structure of open source. It invites not one group, but everyone to contribute back and on the way, enabling them and equipping them with the tools and expertise to do so. And why shouldn&amp;rsquo;t people have a right to look into the source code of the software they use and depend on every day?&lt;/p&gt;&#10;&lt;h2 id="little-history"&gt;LITTLE HISTORY&lt;/h2&gt;&#10;&lt;p&gt;&lt;em&gt;(I saved this for the last because I think history doesn&amp;rsquo;t appeal to many people. But it does to me!)&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;A basic outline of the history is called for, at this point. It started at AT&amp;amp;T Bell Laboratories in the 60s. This place was the &lt;em&gt;geek haven of the 60s&lt;/em&gt;. Most of the foundation of computer science as we know it today was invented here. The transistor was invented at Bell Labs. Dennis Ritchie and Ken Thompson, two prodigious programmers, writers of several CS &amp;ldquo;bibles&amp;rdquo;, and visionaries invented UNIX after MULTICS, another operating system failed to materialize out of a contract. Note that this was the pre-personal computing era so most of these research is geared towards militiary or enterprise applications. UNIX was (is?) a revolutionary piece of software. It introduced novel concepts such pipes, user-level programmable shell, hierarchial file system, and the famous UNIX philosophy: &lt;em&gt;&amp;ldquo;small is beautiful&amp;rdquo;&lt;/em&gt;. But, UNIX was essentially a commercial project. The strategy of AT&amp;amp;T was simple and the gist of it exists in this 1907 tagline,&lt;/p&gt;&#10;&lt;blockquote class="pullquote"&gt;&amp;ldquo;One Policy. One System. Universal Service.&amp;rdquo;&lt;/blockquote&gt;&#10;&#10;&lt;p&gt;They collected and hired the best, gave them creative and financial autonomy, and reaped the benefits of commercialization by leveraging its monopoly in the telecomm sector.&lt;/p&gt;&#10;&lt;p&gt;Richard Stallman independently started implementing a complete system from scratch and called it the GNU project which is a recursive acronym for &amp;ldquo;GNU&amp;rsquo;s Not UNIX&amp;rdquo;. The terms of the GNU Project, launched in 1983, was simple: This &lt;em&gt;source code&lt;/em&gt; is free. I won&amp;rsquo;t charge you any money. But if you modify and make something interesting, you have to publish it with this message. The GNU Public License or GPL was ironclad with no judicial loopholes for exploitation (because Stallman employed lawyers to draw the terms). Though, there were pockets of &amp;ldquo;hackers&amp;rdquo; around the globe who were tinkering on their machines, the GNU project was the one that came into the spotlight. The idea wasn&amp;rsquo;t new, but it captured the public imagination like never before. It spawned the *BSD family of operating systems, all open. And of course, in 1991 a 21 year-old Finnish student started writing a kernel using GNU software and make his hobby project into one of the most successful open source stories in recent times. Linus Torvalds became a sort of poster boy for libre software. Though, Torvalds has recently been known to controversial, I gauge people with what they did. &lt;code&gt;Linux&lt;/code&gt; and &lt;code&gt;git&lt;/code&gt; are great software. I use them everyday.&lt;/p&gt;&#10;&lt;p&gt;The details of Linux is a messy one due to lack of standardization, prolific burst of new microprocessor architectures, birth of WWW, and potential financial gains. For more, I suggest &lt;a href="https://www.goodreads.com/en/book/show/53011383-unix" target="_blank" rel="noopener noreferrer"&gt;UNIX: a history and a memoir&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;h2 id="further-reading"&gt;FURTHER READING&lt;/h2&gt;&#10;&lt;p&gt;I suggest two authors that I have personally read in recent memory. Paul Graham&amp;rsquo;s collection of essays &lt;a href="http://www.amazon.com/gp/product/0596006624" target="_blank" rel="noopener noreferrer"&gt;&amp;ldquo;Hackers And Painters&amp;rdquo;&lt;/a&gt;&#10;, and Eric S Raymond&amp;rsquo;s (a spokesman for now dwindling hacker population who harbours impressive political thoughts on freedom) &lt;a href="http://www.catb.org/~esr/writings/cathedral-bazaar/" target="_blank" rel="noopener noreferrer"&gt;&amp;ldquo;The Cathedral And The Bazaar&amp;rdquo;&lt;/a&gt;&#10; which is a very interesting study of the mechanics and philosophy of open source. For someone who really cares about hacker jargon, you can read &lt;a href="http://www.catb.org/~esr/" target="_blank" rel="noopener noreferrer"&gt;the jargon files&lt;/a&gt;&#10; online (technical expertise required to fully appreciate) or order a copy of the same.&lt;/p&gt;&#10;&lt;p&gt;Also, of much importance is &lt;a href="http://www.catb.org/jargon/html/" target="_blank" rel="noopener noreferrer"&gt;ESR&amp;rsquo;s site&lt;/a&gt;&#10; that though being CSS-agnostic, documents very interesting pieces of information in the usual overtly humorous and sarcastic tones of a hacker.&lt;/p&gt;&#10;&lt;h2 id="bibliography"&gt;BIBLIOGRAPHY&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Torvalds, Linux; Diamond, David: Just for Fun - The Story of an Accidental Revolutionary, Harper&lt;/li&gt;&#10;&lt;li&gt;Kerninghan W. Brian: UNIX - a history and a memoir, KDP&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="http://www.catb.org/~esr/faqs/hacker-howto.html" target="_blank" rel="noopener noreferrer"&gt;&amp;ldquo;How to Become a Hacker&amp;rdquo;&lt;/a&gt;&#10;: Essay by Eric S Raymond&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="http://paulgraham.com/hp.html" target="_blank" rel="noopener noreferrer"&gt;&amp;ldquo;Hackers and painters&amp;rdquo;&lt;/a&gt;&#10;: Essay by Paul Graham&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Building a Discord bot: A definitive guide</title><link>https://basillabib.com/post/building-a-discord-bot-a-definitive-guide/</link><pubDate>Sat, 16 Oct 2021 07:56:12 +0530</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/building-a-discord-bot-a-definitive-guide/</guid><description>&lt;p&gt;In this guide, I will be building a Discord bot from scratch using only &lt;a href="https://discord.js.org" target="_blank" rel="noopener noreferrer"&gt;discord.js&lt;/a&gt;&#10; and a few open public APIs. I&amp;rsquo;ll also explain each step and discuss the reason for preference of a particular way of implementation over others. Unlike other &amp;ldquo;recipes&amp;rdquo; on the web, this will give you insight into the workflow of building software.&lt;/p&gt;&#10;&lt;p&gt;This may seem like a long post because it is. I suggest you to follow along in chunks so you don&amp;rsquo;t get overwhelmed. The tutorial is divided into chapters. So, let&amp;rsquo;s get started.&lt;/p&gt;&#10;&lt;h2 id="what-are-we-building"&gt;WHAT ARE WE BUILDING?&lt;/h2&gt;&#10;&lt;p&gt;&lt;strong&gt;Mayhem&lt;/strong&gt; is a simple Discord bot that is aimed for entertainment and fun. It queries external public APIs for images, jokes, facts and definitions. Few commands are &lt;code&gt;meow&lt;/code&gt;, &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;whatis&lt;/code&gt;, &lt;code&gt;whoami&lt;/code&gt;, &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;help&lt;/code&gt;, &lt;code&gt;whereami&lt;/code&gt; and so on. There is a subtle shoutout to UNIX if you&amp;rsquo;re familiar ;).&lt;/p&gt;&#10;&lt;p&gt;Moreover, these commands are chosen and designed in a way to demonstrate capabilities of the Discord API. Thus, features such as exclusive commands, cooldown periods, pre-fetching etc. are also discussed.&lt;/p&gt;&#10;&lt;h3 id="code-repository"&gt;CODE REPOSITORY&lt;/h3&gt;&#10;&lt;p&gt;The latest version of the codebase can be found &lt;a href="https://github.com/basil08/mayhem" target="_blank" rel="noopener noreferrer"&gt;here&lt;/a&gt;&#10;. Instructions for installation on your Discord server can be found in the &lt;a href="https://github.com/basil08/mayhem/blob/main/README.md" target="_blank" rel="noopener noreferrer"&gt;README.md&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;p&gt;For options to host the bot, see &lt;a href="#deployment-options"&gt;Deployment Options&lt;/a&gt;&#10; in the README.&lt;/p&gt;&#10;&lt;h2 id="js-pre-requisite-check"&gt;JS PRE-REQUISITE CHECK&lt;/h2&gt;&#10;&lt;p&gt;You should be familiar with JavaScript and computer programming concepts such as loops and conditionals. But JS is a particularly notorious language as it is continuously being modified to suit the industry. You should know at least ES6 or ES7. If not, no worries! Below I cover few JS concepts we&amp;rsquo;ll be using in this project.&lt;/p&gt;&#10;&lt;h3 id="arrow-functions"&gt;ARROW FUNCTIONS&lt;/h3&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;[1,2,3].map(elem =&amp;gt; &#10;&#9;{ console.log(&amp;#39;doubling!&amp;#39;);&#10;&#9; return 2*elem; &#10;});&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A syntactic-sugar to reduct clutter. Nothing more but highly convenient for writing chained promises (If you ever found yourself in such a situation that is).&lt;/p&gt;&#10;&lt;h3 id="promises"&gt;PROMISES&lt;/h3&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;const data = fetch(URI)&#10;&#9;.then(res =&amp;gt; res.text())&#10;&#9;.then(tex =&amp;gt; JSON.parse(tex))&#10;&#9;.catch(err =&amp;gt; console.log(err));&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A fix to execute asynchoronous statements synchoronously. Think of these commands as being executed &lt;em&gt;one after the other&lt;/em&gt; and not all together because they can&amp;rsquo;t as the data are dependent on successful execution of previous steps. I never liked it much.&lt;/p&gt;&#10;&lt;h3 id="awaitasync-functions"&gt;&lt;code&gt;await&lt;/code&gt;/&lt;code&gt;async&lt;/code&gt; FUNCTIONS&lt;/h3&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;async function nano(args){&#10;&#9;const _r = await fs.readFile(&amp;#39;stuff.json&amp;#39;);&#10;&#9;// more code&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Successor to Promises. Cleaner, more logical syntax. As opposed to being an API like Promises, async/await are keywords in JS now.&lt;/p&gt;&#10;&lt;p&gt;If you want to gain more insights into these constructs, it is recommended to check out &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript" target="_blank" rel="noopener noreferrer"&gt;MDN JavaScript module&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;h2 id="installation"&gt;INSTALLATION&lt;/h2&gt;&#10;&lt;p&gt;You need &lt;a href="https://nodejs.org" target="_blank" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;&#10; and a package manager such as &lt;a href="https://npmjs.com" target="_blank" rel="noopener noreferrer"&gt;&lt;code&gt;npm&lt;/code&gt;&lt;/a&gt;&#10; or &lt;a href="https://yarnpkg.com" target="_blank" rel="noopener noreferrer"&gt;&lt;code&gt;yarn&lt;/code&gt;&lt;/a&gt;&#10;. &lt;code&gt;npm&lt;/code&gt; comes packaged with Node.&lt;/p&gt;&#10;&lt;p&gt;We will be using these npm packages:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;code&gt;discord.js@12.4.1&lt;/code&gt; or above (Note: The code is tested with this version only)&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;node-fetch@2.6.1&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="basic-setup"&gt;BASIC SETUP&lt;/h3&gt;&#10;&lt;p&gt;To make a new &lt;code&gt;node&lt;/code&gt; project, run the following in your terminal:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ mkdir mayhem&#10;$ cd mayhem&#10;$ npm init -y # -y to say yes to all defaults&#10;$ npm install discordjs@12.4.1 node-fetch@2.6.1&#10;$ touch bot.js&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, open &lt;code&gt;package.json&lt;/code&gt; and add a new script for convenience:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;...&#10; &amp;#34;main&amp;#34;: &amp;#34;index.js&amp;#34;,&#10; &amp;#34;scripts&amp;#34;: {&#10; &amp;#34;test&amp;#34;: &amp;#34;echo \&amp;#34;Error: no test specified\&amp;#34; &amp;amp;&amp;amp; exit 1&amp;#34;,&#10; &amp;#34;start&amp;#34;: &amp;#34;node bot.js&amp;#34;&#10; },&#10;...&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="create-bot-application-and-generate-token"&gt;CREATE BOT APPLICATION AND GENERATE TOKEN&lt;/h2&gt;&#10;&lt;p&gt;Now, we have to create a bot application through your &lt;a href="https://discord.com" target="_blank" rel="noopener noreferrer"&gt;Discord account&lt;/a&gt;&#10; (Make one, we need it!). The steps are as follows:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Login and open the &lt;a href="https://discord.com/developers/applications" target="_blank" rel="noopener noreferrer"&gt;Discord Developer Portal&lt;/a&gt;&#10;.&lt;/li&gt;&#10;&lt;li&gt;Click on the &amp;ldquo;New Application&amp;rdquo; button.&lt;/li&gt;&#10;&lt;li&gt;Enter a name and click &amp;ldquo;Create&amp;rdquo; button to confirm.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;!-- Logged in discord dev portal ss --&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-d6b0607a" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-d6b0607a" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/mayhem/0.jpg" alt="Created New Application in Discord Developer Portal" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-d6b0607a" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/mayhem/0.jpg" alt="Created New Application in Discord Developer Portal" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Created New Application in Discord Developer Portal&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You are free to edit other information as you wish on this page. We&amp;rsquo;ll leave it to defaults. Then navigate to the &amp;ldquo;Bots&amp;rdquo; tab and click &amp;ldquo;Add Bot&amp;rdquo; button.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-e5cd095d" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-e5cd095d" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/mayhem/1.jpg" alt="Created new bot and customized with a name and image icon" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-e5cd095d" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/mayhem/1.jpg" alt="Created new bot and customized with a name and image icon" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Created new bot and customized with a name and image icon&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Clicking on &amp;ldquo;Reveal Token&amp;rdquo; will show your bot token. A &lt;strong&gt;Token&lt;/strong&gt; is a unique identifier for your bot application and is used by Discord to authorize your bot to actually log in and appear online in a server.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;CAUTION&lt;/strong&gt;: It is very important that you keep your token safe and secret. You must not mistakenly commit it to a public repo or such. The consequences can be disastrous. If the key got leaked by mistake, immediately click on &amp;ldquo;Regenerate token&amp;rdquo; to generate a new token and invalidate all previous tokens.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;: Make sure you copy the &amp;ldquo;Bot Token&amp;rdquo; and not the &amp;ldquo;Client Secret&amp;rdquo; or the &amp;ldquo;Client Key&amp;rdquo; which are on the Applications page.&lt;/p&gt;&#10;&lt;h2 id="generate-bot-invite-link"&gt;GENERATE BOT INVITE LINK&lt;/h2&gt;&#10;&lt;h3 id="bot-invite-links-explained"&gt;Bot invite links explained&lt;/h3&gt;&#10;&lt;p&gt;A typical invite link looks like this:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;https://discord.com/api/oauth2/authorize?client_id=123456789012345678&amp;amp;permissions=0&amp;amp;scope=bot%20applications.commands&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The fields that need elaboration are the query parameters which are as follows:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;client_id&lt;/code&gt;: The client id is the application that is authorized.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;permissions&lt;/code&gt;: an integers which encodes the bot&amp;rsquo;s permissions on the server in binary.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;scope=bot&lt;/code&gt;: Yes, this is a bot invite link.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;applications.commands&lt;/code&gt;: To enable Discord to allow &lt;code&gt;/&lt;/code&gt; (slash) to invoke bot commands.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Head over to the OAuth2 page, scroll down and select &lt;code&gt;applications.commands&lt;/code&gt; and &lt;code&gt;bot&lt;/code&gt;. Then, choose permissions that you want your bot to have. The page handily generates the link for you.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-ec2936d6" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-ec2936d6" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/mayhem/2.jpg" alt="Permissions for Mayhem" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-ec2936d6" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/mayhem/2.jpg" alt="Permissions for Mayhem" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Permissions for Mayhem&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Here, I show the permissions for our bot. As you can see, it is a fairly less-demanding bot in terms of administrative privileges.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-65edd67f" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-65edd67f" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/mayhem/3.jpg" alt="Authorizing your bot into a server" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-65edd67f" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/mayhem/3.jpg" alt="Authorizing your bot into a server" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Authorizing your bot into a server&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Paste the generated link and you will be asked to authorize the server. Note: You can add them to servers in which &lt;em&gt;you&lt;/em&gt; have admin access only. If successful, you&amp;rsquo;re greeted by a confirmation dialog. Congratulations, now you can see Mayhem online with a &amp;ldquo;BOT&amp;rdquo; tag!&lt;/p&gt;&#10;&lt;h1 id="chapter-2"&gt;CHAPTER 2&lt;/h1&gt;&#10;&lt;p&gt;In &lt;a href="#what-are-we-building"&gt;Chapter 1&lt;/a&gt;&#10; of this tutorial, we had finished logging our bot into our server. In this post, we plan to give it some interactivity with human users. Moreover, we will organize our code in a modular pattern to facilitate modification and addition of more commands easily.&lt;/p&gt;&#10;&lt;h2 id="first-command--botjs"&gt;FIRST COMMAND &amp;amp; &lt;code&gt;bot.js&lt;/code&gt;&lt;/h2&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// New file: /bot.js&#10;const Discord = require(&amp;#39;discord.js&amp;#39;);&#10;const {prefix, token} = require(&amp;#39;./config.json&amp;#39;);&#10;&#10;const client = new Discord.Client();&#10;&#10;client.once(&amp;#39;ready&amp;#39;, () =&amp;gt; {&#10;&#9;console.log(&amp;#39;Ready!&amp;#39;);&#10;});&#10;&#10;client.on(&amp;#39;message&amp;#39;, (message) =&amp;gt; {&#10;&#9;console.log(`[${message.channel.name}] ${message.author.username}: ${message.content}`);&#10;&#10;&#9;// Don&amp;#39;t interfere when you are not required and don&amp;#39;t be dumb enough to reply to your own msgs.&#10;&#9;if(!message.content.startsWith(prefix) || message.author.bot) return;&#10;&#10;&#9;const args = message.content.slice(prefix.length).trim().split(/ +/);&#10;&#9;const cmdname = args.shift().toLowerCase();&#10;&#10;&#9;try{&#10;&#9;&#9;if (cmdname == &amp;#39;hi&amp;#39;) {&#10;&#9;&#9;&#9;message.channel.send(&amp;#34;Hi ${message.author}! I&amp;#39;m Mayhem. Nice to meet you!&amp;#34;);&#10;&#9;&#9;}&#10;&#9;} catch(err){&#10;&#9;&#9;console.error(err);&#10;&#9;&#9;message.reply(&amp;#39;Oops. There was an error while executing that command.&amp;#39;);&#10;&#9;}&#10;});&#10;&#10;client.login(token);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The code is fairly straightforward to understand. We import the required modules and secrets. The prefix is a special character you use to invoke commands on this bot for eg: &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;--&lt;/code&gt; etc. After instantiating an instance of &lt;code&gt;Discord.Client()&lt;/code&gt;, we log simple text when bot is ready i.e. the ready event is triggered.&lt;/p&gt;&#10;&lt;p&gt;Then, for every message sent to a channel, the &lt;code&gt;'message'&lt;/code&gt; event is triggered which is handled by the function we supply to &lt;code&gt;client.on(...)&lt;/code&gt;. The code parses the message and sends a hi message to the user for messages of the form &lt;code&gt;&amp;lt;PREFIX&amp;gt;hi&lt;/code&gt;. Finally, we login with the bot token we generated before.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// /config.json &#10;{&#10;&#9;&amp;#34;prefix&amp;#34;: &amp;#34;YOUR-PREFERRED-BOT-PREFIX&amp;#34;,&#10;&#9;&amp;#34;token&amp;#34;: 0123456789&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Run &lt;code&gt;npm start&lt;/code&gt; or &lt;code&gt;node bot.js&lt;/code&gt; and your bot is online!&lt;/p&gt;&#10;&lt;p&gt;For now, the bot doesn&amp;rsquo;t do much and only sends the &amp;lsquo;hi&amp;rsquo; message rather annoyingly.&lt;/p&gt;&#10;&lt;p&gt;The code structure as such is not very robust or versatile. We&amp;rsquo;ll organize it to be modular to facilitate that. The idea is we keep code logic for each command in a seperate file in the &lt;code&gt;/commands&lt;/code&gt; directory and load them into the client upon instantiation of the client object. Then, adding a new command will be as clean as adding a new file to this directory.&lt;/p&gt;&#10;&lt;h2 id="modularization"&gt;MODULARIZATION&lt;/h2&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ cd /path/to/mayhem&#10;$ mkdir commands&#10;$ cd commands&#10;&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;// New file: /commands/hi.js&#10;const {bot_name} = require(&amp;#39;../config.json&amp;#39;);&#10;const Discord = require(&amp;#39;discord.js&amp;#39;);&#10;&#10;module.exports = {&#10;&#9;name: &amp;#39;hi&amp;#39;,&#10;&#9;description: &amp;#39;A nice introduction is the gentleman\&amp;#39;s way.&amp;#39;,&#10;&#9;usage: &amp;#39;&amp;#39;,&#10;&#9;execute(message, args){&#10;&#9;&#9;const embed = new Discord.MessageEmbed()&#10;&#9;&#9;&#9;.setColor(&amp;#39;#00ebc7&amp;#39;)&#10;&#9;&#9;&#9;.setTitle(`Hello! ${message.author.username}`)&#10;&#9;&#9;&#9;.addFields(&#10;&#9;&#9;&#9;&#9;{name: `${bot_name} bot says...`, value: `Yo! Nice to meet you!`},&#10;&#9;&#9;&#9;)&#10;&#9;&#9;&#9;.setTimestamp();&#10;&#10;&#9;&#9;message.channel.send(embed);&#10;&#9;}&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you may notice, we export few other handy metadata apart from the function &lt;code&gt;execute(...)&lt;/code&gt; which contains the main logic. We will be adding other fields for unique commands as we need them. This approach was highlighted in &lt;a href="https://discord.js.org" target="_blank" rel="noopener noreferrer"&gt;discordjs.guide&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;p&gt;Now, make the following changes to &lt;code&gt;bot.js&lt;/code&gt;.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// /bot.js&#10;const fs = require(&amp;#39;fs&amp;#39;);&#10;const Discord = require(&amp;#39;discord.js&amp;#39;);&#10;const {prefix, token} = require(&amp;#39;./config.json&amp;#39;);&#10;&#10;const client = new Discord.Client();&#10;&#10;client.commands = new Discord.Collection();&#10;&#10;// will be used later&#10;const cooldowns = new Discord.Collection();&#10;&#10;const files = fs.readdirSync(&amp;#39;./commands&amp;#39;).filter(name =&amp;gt; name.endsWith(&amp;#39;.js&amp;#39;));&#10;&#10;for(const f of files){&#10;&#9;const command = require(`./commands/${f}`);&#10;&#9;// According to Discord API, Discord.Collection is a JS Map Object with more convenience methods&#10;&#9;// Set the record with key as the command name and value as the command itself&#10;&#9;client.commands.set(command.name, command);&#10;}&#10;&#10;client.once(&amp;#39;ready&amp;#39;, () =&amp;gt; {&#10;&#9;console.log(&amp;#39;Ready!&amp;#39;);&#10;});&#10;&#10;client.on(&amp;#39;message&amp;#39;, (message) =&amp;gt; {&#10;&#9;console.log(`[${message.channel.name}] ${message.author.username}: ${message.content}`);&#10;&#10;&#9;// Don&amp;#39;t interfere when you are not required and don&amp;#39;t be dumb enough to reply to your own msgs.&#10;&#9;if(!message.content.startsWith(prefix) || message.author.bot) return;&#10;&#10;&#9;// basic parsing&#10;&#9;const args = message.content.slice(prefix.length).trim().split(/ +/);&#10;&#9;const cmdname = args.shift().toLowerCase();&#10;&#10;&#9;// get the cmd object&#10;&#9;const cmd = client.commands.get(cmdname);&#10;&#9;if(!cmd) {&#10;&#9;&#9;return message.channel.send(&amp;#39;Oops! No such command ;-;&amp;#39;);&#10;&#9;}&#10;&#10;&#9;try{&#10;&#9;&#9;cmd.execute(message, args);&#10;&#9;}catch(err){&#10;&#9;&#9;console.error(err);&#10;&#9;&#9;message.reply(&amp;#39;Oops. There was an error while executing that command.&amp;#39;);&#10;&#9;}&#10;});&#10;&#10;client.login(token);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Most of the code is intact. The &lt;code&gt;Discord.Collection()&lt;/code&gt; is a wrapper around the JS Map Object with few added convenient methods. We read the &lt;code&gt;/commands&lt;/code&gt; directory using &lt;code&gt;fs&lt;/code&gt; and set the object using &lt;code&gt;client.commands.set(..., ...)&lt;/code&gt;. Inside the handler, we parse the incoming message, try to get a command object and execute it. At any stage, if we have an exception, gracefully inform the user.&lt;/p&gt;&#10;&lt;p&gt;Run the bot again via &lt;code&gt;npm start&lt;/code&gt;. Though, we cannot detect any visible changes, we have refactored the code into much more tangible form. Let me talk about one more thing that I have ignored till now.&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-b5f1b4c4" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-b5f1b4c4" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/may2/0.jpg" alt="First command of Mayhem" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-b5f1b4c4" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/may2/0.jpg" alt="First command of Mayhem" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;First command of Mayhem&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h2 id="embed-messages"&gt;EMBED MESSAGES&lt;/h2&gt;&#10;&lt;p&gt;Currently, the messages sent by the bot to the channel are simple text messages which are unattractive and less appealing to our users. If you have experience with Discord bots, you maybe aware of colourful, sprawling messages with many fields, formatted texts and images. The &lt;code&gt;Discord.MessageEmbed&lt;/code&gt; object in &lt;code&gt;discord.js&lt;/code&gt; allows us to construct such messages. If you see the logic of the &lt;code&gt;hi&lt;/code&gt; command, we are indeed constructing one.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// /commands/hi.js&#10;...&#10;&#9;execute(message, args){&#10;&#9;&#9;const embed = new Discord.MessageEmbed()&#10;&#9;&#9;&#9;.setColor(&amp;#39;#00ebc7&amp;#39;)&#10;&#9;&#9;&#9;.setTitle(`Hello! ${message.author.username}`)&#10;&#9;&#9;&#9;.addFields(&#10;&#9;&#9;&#9;&#9;{name: `${bot_name} bot says...`, value: `Yo! Nice to meet you!`},&#10;&#9;&#9;&#9;)&#10;&#9;&#9;&#9;.setTimestamp();&#10;&#10;&#9;&#9;message.channel.send(embed);&#10;&#9;}&#10;...&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These are all obvious imperative methods that adds a particular field one at a time. For eg: You should deliberate on a color theme because it conveys your bot&amp;rsquo;s purpose and &amp;ldquo;vibe&amp;rdquo; to your users. The &lt;code&gt;setColor()&lt;/code&gt; can be used to set a color. &lt;code&gt;setTitle()&lt;/code&gt; adds a bold title to the message. The &lt;code&gt;addFields()&lt;/code&gt; take up a JSON object and add all key-value pairs to the message body. The &lt;code&gt;setTimestamp()&lt;/code&gt;, as expected, adds a timestamp field to the message.&lt;/p&gt;&#10;&lt;h2 id="summary"&gt;SUMMARY&lt;/h2&gt;&#10;&lt;p&gt;In summary, we have executed our first command, modularized our codebase and paved the way to breezy development and we know how &lt;code&gt;discord.js&lt;/code&gt; handles channel events (via handlers) and about &lt;code&gt;Discord.Collection()&lt;/code&gt;, &lt;code&gt;Discord.Client()&lt;/code&gt; and &lt;code&gt;Discord.MessageEmbed()&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h1 id="chapter-3"&gt;CHAPTER 3&lt;/h1&gt;&#10;&lt;p&gt;In the &lt;a href="#chapter-2"&gt;last chapter&lt;/a&gt;&#10; of this guide, we looked at creating our first command, &lt;code&gt;Discord.MessageEmbed()&lt;/code&gt;, and modularization. In this part, we&amp;rsquo;ll demonstrate API fetching and command aliases, while adding two new commands to our bot!&lt;/p&gt;&#10;&lt;h2 id="meow--node-fetch"&gt;&lt;code&gt;meow&lt;/code&gt; &amp;amp; &lt;code&gt;node-fetch&lt;/code&gt;&lt;/h2&gt;&#10;&lt;p&gt;The &lt;code&gt;meow&lt;/code&gt; command is a simple command that queries a public API of cat pictures and sends one random, cute cat picture down in the channel. We will use the following endpoint:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;https://aws.random/cat/meow&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: If you wish to use an API that requires some kind of authorization, then you have to generate a secret key from a dashboard usually and feed this credential to your &lt;code&gt;.env&lt;/code&gt; file (this step is more or less platform-dependent. For eg: &lt;a href="https://replit.com" target="_blank" rel="noopener noreferrer"&gt;replit&lt;/a&gt;&#10; provides a key-value based secret storage feature). Then, you can read that value via &lt;code&gt;process.env.API_SECRET_KEY&lt;/code&gt; or likewise. Consult your API documentation and hosting provider documentation.&lt;/p&gt;&#10;&lt;p&gt;But to fetch on the server-side, we have to install a &lt;code&gt;fetch&lt;/code&gt;-equivalent module on our server. This is achieved by the &lt;code&gt;node-fetch&lt;/code&gt; package.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;yarn add node-fetch # or npm install node-fetch&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, let us implement the &lt;code&gt;meow&lt;/code&gt; command.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// New file: /commands/meow.js &#10;const fetch = require(&amp;#39;node-fetch&amp;#39;);&#10;const {prefix} = require(&amp;#39;../config.json&amp;#39;);&#10;&#10;module.exports = {&#10;&#9;name: &amp;#39;meow&amp;#39;,&#10;&#9;description: &amp;#39;Coz everyone likes cats!&amp;#39;,&#10;&#9;usage: `${prefix}meow`,&#10;&#9;cooldown: 10,&#10;&#9;execute(message, args){&#10;&#9;&#9;const file = fetch(&amp;#39;https://aws.random.cat/meow&amp;#39;)&#10;&#9;&#9;&#9;.then(file =&amp;gt; file.text())&#10;&#9;&#9;&#9;.catch(err =&amp;gt; { &#10;&#9;&#9;&#9;&#9;console.error(err);&#10;&#9;&#9;&#9;&#9;message.channel.send(`An Error was encountered while executing that command.`);&#10;&#9;&#9;&#9;});&#10;&#9;&#9;message.channel.send(JSON.parse(file).file);&#10;&#10;&#9;}&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-df891ecb" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-df891ecb" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/may3/1.jpg" alt="The meow command" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-df891ecb" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/may3/1.jpg" alt="The meow command" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;The meow command&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Once again, the code is obvious and there is no element that requires an explanation as such. The &amp;ldquo;cooldown&amp;rdquo; parameter will be dealt with in another command. But, you can probably guess it is to prevent a indirect DDoS attack on the cat server by bad actors and/or bots.&lt;/p&gt;&#10;&lt;h2 id="help"&gt;&lt;code&gt;help&lt;/code&gt;&lt;/h2&gt;&#10;&lt;p&gt;Good UI/UX don&amp;rsquo;t require a manual to be used. So, like every good software, we will provide a handy help command for potentially new users of our bot. This command will demonstrate sending personal direct messages and command aliases.&lt;/p&gt;&#10;&lt;p&gt;The design of the messages is decoupled from the logic. Feel free to make your bot as snazzy as possible.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// New file: /commands/help.js&#10;const {prefix, version,bot_name, bot_author} = require(&amp;#39;../config.json&amp;#39;);&#10;const Discord = require(&amp;#39;discord.js&amp;#39;);&#10;&#10;module.exports = {&#10;&#9;name: &amp;#39;help&amp;#39;,&#10;&#9;description: &amp;#39;Display all available commands&amp;#39;,&#10;&#9;aliases: [&amp;#39;h&amp;#39;,&amp;#39;?&amp;#39;],&#10;&#9;execute(message, args){&#10;&#9;&#9;const data = [];&#10;&#9;&#9;const {commands} = message.client;&#10;&#10;&#9;&#9;if(!args.length){&#10;&#9;&#9;&#9;// show all commands&#10;&#9;&#9;&#9;data.push(`This is the ${bot_name} Bot version ${version}`);&#10;&#9;&#9;&#9;data.push(commands.map(command =&amp;gt; command.name).join(&amp;#39;, &amp;#39;));&#10;&#9;&#9;&#9;data.push(`Use ${prefix}help [command name] to get details about [command name]`);&#10;&#9;&#9;&#9;data.push(`All commands are prefixed by the ${prefix}`);&#10;&#9;&#9;&#9;data.push(`\n\nSend your love to ${bot_author} ;)`);&#10;&#10;&#9;&#9;&#9;// split: true splits the message into separate messages if char count &amp;gt; 2000&#10;&#9;&#9;&#9;return message.author.send(data, {split: true})&#10;&#9;&#9;&#9;&#9;.then(() =&amp;gt; {&#9;// DM as help message can get messy&#10;&#9;&#9;&#9;&#9;&#9;if(message.channel.type === &amp;#39;dm&amp;#39;) return;&#10;&#9;&#9;&#9;&#9;&#9;message.reply(&amp;#39;I\&amp;#39;ve sent you a DM&amp;#39;);&#10;&#9;&#9;&#9;&#9;})&#10;&#9;&#9;&#9;&#9;.catch(error =&amp;gt; { // the case when user has DM disabled must be gracefully handled&#10;&#9;&#9;&#9;&#9;&#9;console.error(`Could not send help DM to ${message.author.tag}.\n`,error);&#10;&#9;&#9;&#9;&#9;&#9;message.reply(&amp;#39;I can\&amp;#39;t DM you. Do you have DMs disabled?&amp;#39;);&#10;&#9;&#9;&#9;&#9;});&#10;&#9;&#9;}else{&#10; ...&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see, &lt;code&gt;message.author.send(...)&lt;/code&gt; is used to send a message to the original message author&amp;rsquo;s DM. If a bare &lt;code&gt;help&lt;/code&gt; is invoked, we inform the user of our commands personally and ask them to explore more by using the format &lt;code&gt;help [command name]&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Once again, the aliases field will come handy in the next part of this function.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;...&#10;&#9;}else{&#10;&#9;&#9;&#9;const name = args[0].toLowerCase();&#10;&#10; // try to make a hit, either the command or its aliases&#10;&#9;&#9;&#9;const command = commands.get(name) || commands.find(c =&amp;gt; c.aliases &amp;amp;&amp;amp; c.aliases.includes(name));&#10;&#10;&#9;&#9;&#9;if(!command){&#10;&#9;&#9;&#9;&#9;return message.reply(&amp;#39;Not a valid command -_-&amp;#39;);&#10;&#9;&#9;&#9;}&#10;&#10;&#9;&#9;&#9;const embed = new Discord.MessageEmbed()&#10;&#9;&#9;&#9;.setTitle(`Help for ${command.name}`)&#10;&#9;&#9;&#9;.setColor(&amp;#39;#02fede&amp;#39;)&#10;&#9;&#9;&#9;.addFields(&#10;&#9;&#9;&#9;&#9;{name: &amp;#39;Aliases&amp;#39;, value: (command.aliases) ? `${command.aliases.join(&amp;#39;, &amp;#39;)}`: &amp;#39;There are no aliases for this command!&amp;#39;},&#10;&#9;&#9;&#9;&#9;{name: &amp;#39;Description&amp;#39;, value: (command.description) ? `${command.description}`: &amp;#39;Currently no description available!&amp;#39;},&#10;&#9;&#9;&#9;&#9;{name: &amp;#39;Usage&amp;#39;, value: (command.usage) ? `${command.usage}`:&amp;#39;Currently no usage available!&amp;#39;},&#10;&#9;&#9;&#9;&#9;{name: &amp;#39;Cooldown&amp;#39;, value: (command.cooldown) ? `${command.cooldown} second(s)`: &amp;#39;This command has no cooldown!&amp;#39;}&#10;&#9;&#9;&#9;)&#10;&#9;&#9;&#9;.setTimestamp();&#10;&#10;&#9;&#9;&#9;message.channel.send(embed);&#10;&#9;&#9;}&#10;&#9;}&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-01c36d1e" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-01c36d1e" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/may3/0.jpg" alt="help meow command execution" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-01c36d1e" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/may3/0.jpg" alt="help meow command execution" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;&lt;code&gt;help meow&lt;/code&gt; command execution&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You can probably understand the workflow that &lt;code&gt;discord.js&lt;/code&gt; promulgates. Here it is:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;PARSE :right_arrow: EXECUTE :right_arrow: SEND&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="summary-1"&gt;SUMMARY&lt;/h2&gt;&#10;&lt;p&gt;In this post, we finally made an API call from our bot and served some cute cat pictures to our users. Now, the sky&amp;rsquo;s the limit for you! Moreover, we added a help command to be more organized.&lt;/p&gt;&#10;&lt;h1 id="chapter-4"&gt;CHAPTER 4&lt;/h1&gt;&#10;&lt;p&gt;In the &lt;a href="#chapter-3"&gt;last&lt;/a&gt;&#10; part of this guide, we implemented the &lt;code&gt;meow&lt;/code&gt; and &lt;code&gt;help&lt;/code&gt; commands. Now, we will look at leveraging the API exposed by discord.js to implement &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;whatis&lt;/code&gt;, and &lt;code&gt;rm&lt;/code&gt; commands.&lt;/p&gt;&#10;&lt;h2 id="cat--pre-fetching"&gt;&lt;code&gt;cat&lt;/code&gt; &amp;amp; PRE-FETCHING&lt;/h2&gt;&#10;&lt;p&gt;The &lt;code&gt;cat&lt;/code&gt; command makes a query to another free &lt;a href="https://rapidapi.com/brianiswu/api/cat-facts" target="_blank" rel="noopener noreferrer"&gt;API&lt;/a&gt;&#10; and displays interesting cat facts to our users. However, the API has only one endpoint and due to performance reasons, we cannot query the entire database each time a user makes request. So, the idea is to fetch the resource at startup time of the boot and save the data locally on the disk. This, though makes our bot more efficient and performant, has obvious disadvantages: What if the data on the API server mutates? But, we shall ignore such concerns as it is basically a database API.&lt;/p&gt;&#10;&lt;p&gt;Fire up your text editor and create this new file: &lt;code&gt;fetcher.js&lt;/code&gt;.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;# New file: /fetcher.js &#10;const fs = require(&amp;#39;fs&amp;#39;);&#10;const fetch = require(&amp;#39;node-fetch&amp;#39;);&#10;const {CAT_API_KEY} = require(&amp;#39;./config.json&amp;#39;);&#10;&#10;async function writeData(){&#10;&#9;const data = await fetch(&amp;#34;https://brianiswu-cat-facts-v1.p.rapidapi.com/facts&amp;#34;, &#10;&#9;&#9;{&#10;&#9;&#9;&#9;&amp;#34;method&amp;#34;: &amp;#34;GET&amp;#34;,&#10;&#9;&#9;&#9;&amp;#34;headers&amp;#34;: {&#10;&#9;&#9;&#9;&#9;&amp;#34;x-rapidapi-key&amp;#34;: CAT_API_KEY, &#10;&#9;&#9;&#9;&#9;&amp;#34;x-rapidapi-host&amp;#34;: &amp;#34;brianiswu-cat-facts-v1.p.rapidapi.com&amp;#34;&#10;&#9;&#9;&#9;&#9; }&#10;&#9;&#9;})&#10;&#9;&#9;.then(response =&amp;gt; response.text())&#10;&#9;&#9;.then(test_response =&amp;gt; JSON.parse(test_response))&#10;&#9;&#9;.catch(error =&amp;gt; {&#10;&#9;&#9;&#9;console.log(error);&#10;&#9;&#9;});&#10;&#10;&#9;let raw_data = JSON.stringify(data, null, 2);&#10;&#9;console.log(raw_data);&#10;&#9;fs.writeFileSync(&amp;#39;./static/data/cat_data.json&amp;#39;, raw_data);&#10;&#10;&#9;console.log(&amp;#39;written.&amp;#39;);&#10;}&#10;writeData();&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To get your API Key, go &lt;a href="https://rapidapi.com/brianiswu/api/cat-facts" target="_blank" rel="noopener noreferrer"&gt;here&lt;/a&gt;&#10; and follow the steps and put the key in your &lt;code&gt;config.json&lt;/code&gt; likewise.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// FILE: /config.json&#10;{&#10; &amp;#34;CAT_API_KEY&amp;#34;: &amp;#34;kjsalkjwRlkjVNLKKJASHLl2l49SF2qlkjS&amp;#34;,&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The steps are obvious. We request the cat server with a /GET request and fetch all the data. If succeeded, parse and write that to a JSON file on the disk using &lt;code&gt;fs.writeFileSync()&lt;/code&gt;. For convenience, we can update the yarn scripts to run this file before running &lt;code&gt;bot.js&lt;/code&gt; on &lt;code&gt;yarn start&lt;/code&gt;.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// FILE: /package.json&#10;...&#10; &amp;#34;start&amp;#34;: &amp;#34;node fetcher.js &amp;amp;&amp;amp; node bot.js&amp;#34;,&#10;...&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we will add a new command &lt;code&gt;cat&lt;/code&gt; to &lt;code&gt;/commands&lt;/code&gt;. The basic structure remains the same. We export a JSON object with a &lt;code&gt;execute(...)&lt;/code&gt; function and few other metadata.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// NEW FILE: /commands/cat.js&#10;const Discord = require(&amp;#39;discord.js&amp;#39;);&#10;const fs = require(&amp;#39;fs&amp;#39;);&#10;const {prefix} = require(&amp;#39;../config.json&amp;#39;);&#10;&#10;module.exports = {&#10;&#9;name: &amp;#39;cat&amp;#39;,&#10;&#9;description: &amp;#39;An enlightening piece of cat fact that will change your life.&amp;#39;,&#10;&#9;usage: `${prefix}cat`,&#10;&#9;cooldown: 5,&#10;&#9;async execute(message, args){&#10;&#9;&#9;let data_file_name = &amp;#39;\\cat_data.json&amp;#39;;&#10;&#9;&#9;let data = JSON.parse(fs.readFileSync(__dirname + data_file_name));&#10;&#10; // choose a random cat fact from data&#10;&#9;&#9;let index = Math.floor(Math.random() * data.all.length);&#10;&#9;&#9;const factText = data.all[index].text;&#10;&#9;&#9;const upVotes = parseInt(data.all[index].upvotes);&#10;&#10;&#9;&#9;const payload = new Discord.MessageEmbed()&#10;&#9;&#9;.setTitle(&amp;#39;Who wants a cat fact?&amp;#39;)&#10;&#9;&#9;.setColor(&amp;#39;#ffa500&amp;#39;)&#10;&#9;&#9;.addFields(&#10;&#9;&#9;&#9;{name: &amp;#39;Fact&amp;#39;, value: factText},&#10;&#9;&#9;&#9;{name: &amp;#39;\u200b&amp;#39;, value: &amp;#39;\u200b&amp;#39;},&#10;&#9;&#9;&#9;{name: &amp;#39;Upvotes&amp;#39;, value: upVotes}&#10;&#9;&#9;)&#10;&#9;&#9;.setTimestamp();&#10;&#10;&#9;&#9;message.channel.send(payload);&#10;&#10;&#9;}&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Restart your app and test your new command!&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-eff4dd6d" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-eff4dd6d" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/may4/0.png" alt="Testing the cat command" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-eff4dd6d" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/may4/0.png" alt="Testing the cat command" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Testing the cat command&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h2 id="whatis--cooldowns"&gt;&lt;code&gt;whatis&lt;/code&gt; &amp;amp; COOLDOWNS&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://www.urbandictionary.com/" target="_blank" rel="noopener noreferrer"&gt;Urban Dictionary&lt;/a&gt;&#10; provides a free API to query its database. Furthermore, there is no need to generate a key or authorization token. We will be using this API to give our bot a sarcastic nature (you&amp;rsquo;ll soon see what I mean :winking_face:). The overall pattern remains the same. We construct the request using the &lt;code&gt;querystring&lt;/code&gt; package. Just do &lt;code&gt;yarn add querystring&lt;/code&gt;. The rest of the execute function handles exceptions such as zero-element list, network error during fetching, etc. Here is the code:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// NEW FILE: /commands/whatis.js &#10;&#10;const querystring = require(&amp;#39;querystring&amp;#39;);&#10;const fetch = require(&amp;#39;node-fetch&amp;#39;);&#10;const Discord = require(&amp;#39;discord.js&amp;#39;);&#10;const {prefix} = require(&amp;#39;../config.json&amp;#39;);&#10;&#10;module.exports = {&#10;&#9;name: &amp;#39;whatis&amp;#39;,&#10;&#9;description: &amp;#39;Your own dictionary utility on Discord.\n\nNo googling or changing tabs :)&amp;#39;,&#10;&#9;cooldown: 10,&#10;&#9;usage: `${prefix}whatis &amp;lt;search-term&amp;gt;`,&#10;&#9;aliases: [&amp;#39;def&amp;#39;,&amp;#39;define&amp;#39;],&#10;&#9;async execute(message, args){&#10;&#9;&#9;if(!args.length){&#10;&#9;&#9;&#9;message.reply(`fatal! You need to specify a search term.`);&#10;&#9;&#9;&#9;return;&#10;&#9;&#9;}&#10;&#10; // See: https://www.npmjs.com/package/query-string&#10;&#9;&#9;const query = querystring.stringify({term: args.join(&amp;#39; &amp;#39;)});&#10;&#9;&#9;&#10; // helper function to trim message to sizable chunks&#10;&#9;&#9;const trim = (str, max) =&amp;gt; ((str.length &amp;gt; max) ? `${str.slice(0, max-3)}...`: str);&#10;&#10;&#9;&#9;const {list} = await fetch(`https://api.urbandictionary.com/v0/define?${query}`)&#10;&#9;&#9;&#9;.then(response =&amp;gt; response.text())&#10;&#9;&#9;&#9;.then(textres =&amp;gt; JSON.parse(textres))&#10;&#9;&#9;&#9;.catch(err =&amp;gt; {&#10;&#9;&#9;&#9;&#9;console.error(err);&#10;&#9;&#9;&#9;&#9;message.channel.send(`An Error was encountered while executing that command!`);&#10;&#9;&#9;&#9;});&#10;&#9;&#9;&#10;&#9;&#9;if(!list.length){&#10;&#9;&#9;&#9;message.channel.send(`No results found for &amp;#34;${args.join(&amp;#39; &amp;#39;)}&amp;#34;`);&#10;&#9;&#9;&#9;return;&#10;&#9;&#9;}&#10;&#10;&#9;&#9;// just the first element&#10; // See: Array Destructuring&#10;&#9;&#9;const [answer] = list;&#10;&#10;&#9;&#9;const embed = new Discord.MessageEmbed()&#10;&#9;&#9;&#9;.setColor(&amp;#39;#EFFF00&amp;#39;)&#10;&#9;&#9;&#9;.setTitle(answer.word)&#10;&#9;&#9;&#9;.setURL(answer.permalink)&#10;&#9;&#9;&#9;.addFields(&#10;&#9;&#9;&#9;&#9;{name: &amp;#39;Description&amp;#39;, value: (answer.definition) ? trim(answer.definition, 1024): &amp;#39;No description available!&amp;#39; },&#10;&#9;&#9;&#9;&#9;{name: &amp;#39;Example&amp;#39;, value: (answer.example) ? trim(answer.example, 1024) : &amp;#39;No example available&amp;#39; },&#10;&#9;&#9;&#9;&#9;{name: &amp;#39;Rating&amp;#39;, value: (answer.thumbs_up &amp;amp;&amp;amp; answer.thumbs_down) ?`${answer.thumbs_up} thumps up. ${answer.thumbs_down} thumbs down`: &amp;#39;No rating available&amp;#39; }&#10;&#9;&#9;&#9;);&#10;&#10;&#9;&#9;message.channel.send(embed);&#10;&#9;}&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-47d0bb29" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-47d0bb29" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/may4/1.png" alt="Testing the whatis command. Aliases include: def and define" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-47d0bb29" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/may4/1.png" alt="Testing the whatis command. Aliases include: def and define" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Testing the whatis command. Aliases include: def and define&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h3 id="cooldowns"&gt;Cooldowns&lt;/h3&gt;&#10;&lt;p&gt;You may have noticed the &lt;code&gt;cooldown&lt;/code&gt; field. (In fact, it was introduced much earlier but I asked you to ignore it back then). Here, we will use it to add cooldown to our commands. As it is obvious, Indirect DDoS on external servers by repeatedly executing this command is obvious so we must prevent that for susceptible commands such as this.&lt;/p&gt;&#10;&lt;p&gt;Update your &lt;code&gt;bot.js&lt;/code&gt; with the following code:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// FILE: /bot.js&#10;&#10;...&#10; // use command.usage to improve UI/UX&#10;&#9;if(cmd.args &amp;amp;&amp;amp; !args.length){&#10;&#9;&#9;let reply = &amp;#39;You must provide arguments for this command&amp;#39;;&#10;&#10;&#9;&#9;if(cmd.usage){&#10;&#9;&#9;&#9;reply += `\nThe correct usage of this command is:\n${prefix}${cmd.name} ${cmd.usage}`;&#10;&#9;&#9;}&#10;&#9;&#9;message.channel.send(reply);&#10;&#9;&#9;return;&#10;&#9;}&#10;&#10; // cooldown&#10;&#9;if(cmd.cooldown){&#10; // keep track of recent commands in cooldown period&#10;&#9;&#9;if(!cooldowns.has(cmd.name)){&#10;&#9;&#9;&#9;cooldowns.set(cmd.name, new Discord.Collection());&#10;&#9;&#9;}&#10;&#10;&#9;&#9;const now = Date.now();&#10;&#9;&#9;const timestamps = cooldowns.get(cmd.name);&#10;&#9;&#9;const cdtime = (cmd.cooldown) * 1000;&#10;&#10;&#9;&#9;if(timestamps.has(message.author.id)){&#10;&#9;&#9;&#9;const expirationDate = timestamps.get(message.author.id) + cdtime;&#10;&#9;&#9;&#9;&#10; // calculate how much time to wait&#10;&#9;&#9;&#9;if(now &amp;lt; expirationDate){&#10;&#9;&#9;&#9;&#9;const timeLeft = (expirationDate - now) / 1000;&#10;&#9;&#9;&#9;&#9;return message.reply(`You have to wait ${timeLeft.toFixed(1)} more second(s) to use the ${cmd.name} command`);&#10;&#9;&#9;&#9;}&#10;&#9;&#9;}&#10;&#10;&#9;&#9;timestamps.set(message.author.id, now);&#10; // clear timeout&#10;&#9;&#9;setTimeout(() =&amp;gt; timestamps.delete(message.author.id), cdtime);&#10;&#9;}&#10;...&#10;&lt;/code&gt;&lt;/pre&gt;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-152e6a9e" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-152e6a9e" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/may4/2.png" alt="Testing cooldown for whatis command" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-152e6a9e" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/may4/2.png" alt="Testing cooldown for whatis command" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;Testing cooldown for whatis command&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h2 id="rm--exclusive-commands"&gt;&lt;code&gt;rm&lt;/code&gt; &amp;amp; EXCLUSIVE COMMANDS&lt;/h2&gt;&#10;&lt;p&gt;Finally, I will show you how to implement exclusive commands using basic Discord ID and some workarounds.&lt;/p&gt;&#10;&lt;p&gt;We will write the &lt;code&gt;rm&lt;/code&gt; command which (obviously) deletes messages from the channel. It expects an integer argument.&lt;/p&gt;&#10;&lt;p&gt;Each Discord user has a UserID. We will allow the command to succeed if the message author is authorized to use the command, else it will result in a failure.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// NEW FILE: /commands/rm.js&#10;const {prefix} = require(&amp;#34;../config.json&amp;#34;);&#10;&#10;module.exports = {&#10;&#9;name: &amp;#39;rm&amp;#39;,&#10;&#9;description: &amp;#39;Prunes that many messages from current channel. (Reserved)&amp;#39;,&#10;&#9;args: true,&#10;&#9;usage: `${prefix}rm &amp;lt;number&amp;gt;`,&#10;&#9;cooldown: 5,&#10;&#9;aliases: [&amp;#39;remove&amp;#39;,&amp;#39;prune&amp;#39;,&amp;#39;del&amp;#39;,&amp;#39;clear&amp;#39;,&amp;#39;cls&amp;#39;],&#10;&#9;permissions: [ &amp;#39;&amp;lt;AUTHORIZED_USER_ID&amp;gt;&amp;#39; ],&#10;&#9;execute(message, args){&#10;&#9;&#9;const count = parseInt(args[0]);&#10;&#10;&#9;&#9;if(isNaN(count)){ &#10;&#9;&#9;&#9;message.reply(`${args[0]}: That doesn&amp;#39;t look like a number to me`);&#10;&#9;&#9;} &#10;&#9;&#9;else{&#10;&#9;&#9;&#9;// +1 to also delete the delete command&#10;&#9;&#9;&#9;message.channel.bulkDelete(count + 1, true).catch(err =&amp;gt; {&#10;&#9;&#9;&#9;&#9;console.log(err);&#10;&#9;&#9;&#9;&#9;message.channel.send(&amp;#39;Sorry, there was a problem encountered while pruning this channel&amp;#39;);&#10;&#9;&#9;&#9;});&#10;&#9;&#9;}&#10;&#9;}&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, add a check in &lt;code&gt;bot.js&lt;/code&gt; to use the permissions array.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// FILE: /bot.js&#10;// permissions&#10;// Uses the message.author.id to determine if author has privileges to use this command&#10;&#9;if(cmd.permissions){&#10;&#9;&#9;if(!(cmd.permissions[0] === message.author.id)){&#10;&#9;&#9;&#9;&#9;message.channel.send(`fatal: you can&amp;#39;t use this command.\nDo you have enough privileges?`);&#10;&#9;&#9;&#9;&#9;return;&#10;&#9;&#9;}&#10;&#9;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Phew, that has been a lot of concepts and coding! I hope you learnt something interesting and more importantly, got motivated to try out ideas of your own. Below are few commands that I added to my bot. None of them require any special explanation so I have only provided the code. You can also check this in the bot&amp;rsquo;s Github repo.&lt;/p&gt;&#10;&lt;h2 id="ls--list-all-commands"&gt;&lt;code&gt;ls&lt;/code&gt; | LIST ALL COMMANDS&lt;/h2&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// NEW FILE: /commands/ls.js &#10;const {version, bot_name, bot_author, prefix} = require(&amp;#39;../config.json&amp;#39;);&#10;const Discord = require(&amp;#39;discord.js&amp;#39;);&#10;&#9;&#9;const data = [];&#10;&#9;&#9;const {commands} = message.client;&#10;&#10;&#9;&#9;if(!args.length){&#10;&#9;&#9;&#9;const embed = new Discord.MessageEmbed()&#10;&#9;&#9;&#9;.setTitle(`This is the ${bot_name} Bot version ${version}`)&#10;&#9;&#9;&#9;.setColor(&amp;#39;#fa5246&amp;#39;)&#10;&#9;&#9;&#9;.addFields({name: &amp;#39;Commands available&amp;#39;, value: commands.map(command =&amp;gt; &amp;#39;\t\t&amp;#39;+command.name).join(&amp;#39;\n&amp;#39;)})&#10;&#9;&#9;&#9;.setTimestamp();&#10;&#10;&#9;&#9;&#9;return message.channel.send(embed);&#10;&#9;&#9;}&#10;&#9;&#9;else {&#10;&#9;&#9;&#9;return message.channel.send(`ls doesn&amp;#39;t support arguments now`);&#10;&#9;&#9;}&#10;&#9;}&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="whereami--basic-info-practically-useless"&gt;&lt;code&gt;whereami&lt;/code&gt; | BASIC INFO (practically useless)&lt;/h2&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// NEW FILE: /commands/whereami.js&#10;const Discord = require(&amp;#39;discord.js&amp;#39;);&#10;&#10;module.exports = {&#10;&#9;name: &amp;#39;whereami&amp;#39;,&#10;&#9;description: &amp;#39;Tells you where you currently are. Immensely useful after a booze party ;)&amp;#39;,&#10;&#9;usage: &amp;#39;&amp;#39;,&#10;&#9;execute(message, args){&#10;&#9;&#9;&#10;&#9;&#9;const embed = new Discord.MessageEmbed()&#10;&#9;&#9;.setTitle(&amp;#39;Where are you?&amp;#39;)&#10;&#9;&#9;.setColor(&amp;#39;#ff5470&amp;#39;)&#10;&#9;&#9;.addFields(&#10;&#9;&#9;&#9;{name: `Channel name`,value: `${message.channel.name}`},&#10;&#9;&#9;&#9;{name: `Server name`, value: `${message.guild.name}`}&#10;&#9;&#9;)&#10;&#9;&#9;.setTimestamp();&#10;&#10;&#9;&#9;message.channel.send(embed);&#10;&#9;}&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="whoami--basic-info-practically-useless"&gt;&lt;code&gt;whoami&lt;/code&gt; | BASIC INFO (practically useless)&lt;/h2&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;// NEW FILE: /commands/whoami.js&#10;const Discord = require(&amp;#39;discord.js&amp;#39;);&#10;&#10;module.exports = {&#10;&#9;name: &amp;#39;whoami&amp;#39;,&#10;&#9;description: &amp;#39;...Well, if it was a particularly cool booze party (For context: see whereami)&amp;#39;,&#10;&#9;usage: &amp;#39;&amp;#39;,&#10;&#9;execute(message, args){&#10;&#9;&#9;// message.channel.send(`Your username is: ${message.author.username}\nYour ID: ${message.author.id}`);&#10;&#10;&#9;&#9;const embed = new Discord.MessageEmbed()&#10;&#9;&#9;.setTitle(&amp;#39;Who are you?&amp;#39;)&#10;&#9;&#9;.setColor(&amp;#39;#ff5470&amp;#39;)&#10;&#9;&#9;.addFields(&#10;&#9;&#9;&#9;{name: `Your username`,value: `${message.author.username}`},&#10;&#9;&#9;&#9;{name: `Your ID`, value: `${message.author.id}`}&#10;&#9;&#9;)&#10;&#9;&#9;.setTimestamp();&#10;&#10;&#9;&#9;message.channel.send(embed);&#10;&#9;}&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="further-development-ideas"&gt;FURTHER DEVELOPMENT IDEAS&lt;/h2&gt;&#10;&lt;p&gt;One can, in principle, go on and on, adding commands and polishing their bot but we have to stop somewhere. I think this will be a suitable time for me to stop. I built this Discord bot and thought it would be fun sharing the learning / building process. It suffices to say the bot is anything from &amp;ldquo;complete&amp;rdquo; or &amp;ldquo;presentable&amp;rdquo;. Here are few ideas that you can explore:&lt;/p&gt;&#10;&lt;ol start="0"&gt;&#10;&lt;li&gt;Use &lt;code&gt;dotenv&lt;/code&gt; package to hide API keys in &lt;code&gt;.env&lt;/code&gt; instead of &lt;code&gt;config.json&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;Implement an add/delete command to modify permissions.&lt;/li&gt;&#10;&lt;li&gt;Implement a modifier command to control cooldowns.&lt;/li&gt;&#10;&lt;li&gt;Explore Sockets (persistant TCP connections as in music bots, etc).&lt;/li&gt;&#10;&lt;li&gt;Polish the user interface.&lt;/li&gt;&#10;&lt;li&gt;Use better APIs.&lt;/li&gt;&#10;&lt;li&gt;Use a database somewhere in a creative manner.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="deployment-options"&gt;DEPLOYMENT OPTIONS&lt;/h2&gt;&#10;&lt;p&gt;Initially, I hosted the bot on heroku free tier and configured a ping to keep the instance alive but heroku stopped that so I had to find a solution quickly. &lt;a href="https://replit.com" target="_blank" rel="noopener noreferrer"&gt;replit&lt;/a&gt;&#10; is an innovative and exicting platform for young creators and developers. It is a delight to work in a repl, they have group programming, an in-house database, and so much more. Most importantly, they allow their instances to be pinged so my bot is basically online since a year with as little downtime as ~1 day! If you are passionate about your bot and want more power, consider joining their Hacker plan.&lt;/p&gt;&#10;&lt;p&gt;You can program directly in the replit web UI or connect a Github repo and import your code from there.&lt;/p&gt;&#10;&lt;h2 id="acknowledgements"&gt;ACKNOWLEDGEMENTS&lt;/h2&gt;&#10;&lt;p&gt;This is my first tech guide / tutorial on the web. What things worked for you? What did you not like? I would like to &lt;a href="mailto:gs454236@gmail.com"&gt;hear from you&lt;/a&gt;&#10;!&lt;/p&gt;&#10;&lt;p&gt;For the development of Mayhem, I heavily relied upon &lt;a href="https://discord.js.org" target="_blank" rel="noopener noreferrer"&gt;discordjs.guide&lt;/a&gt;&#10;, a well-explained document and the official &lt;a href="https://docs.discordapi.com" target="_blank" rel="noopener noreferrer"&gt;Discord API docs&lt;/a&gt;&#10;.&lt;/p&gt;&#10;</description></item><item><title>Thoughts: UNIX - History and Memoir (Book)</title><link>https://basillabib.com/post/thoughts-unix-history-and-memoir-book/</link><pubDate>Tue, 30 Mar 2021 00:00:00 +0000</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/thoughts-unix-history-and-memoir-book/</guid><description>&lt;p&gt;It&amp;rsquo;s always good to know about the things you love the most. It somehow makes you love them even more. I am glad non-living things are not stupidly self-conscious and self-doubting like us and I can understand them more deeply than I can understand other humans.&lt;/p&gt;&#10;&lt;h2 id="some-mindfulless-blabberings"&gt;Some mind(ful|less) blabberings&lt;/h2&gt;&#10;&lt;p&gt;With that said, I enjoyed this book. In Brian&amp;rsquo;s succint prose, he recollects the history of &lt;em&gt;the mother of all operating systems&lt;/em&gt;: UNIX. And along the way, describes the sideways development that it encouraged such as the development of C (the “lingua franca of computing”), various timeless tools like &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;yacc&lt;/code&gt;, &lt;code&gt;lex&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;, document formatters and so on. He talks about what lead to the development of UNIX and ruminates on the kind of environment contagious to such breakthroughs. Unfettered management, fun atmosphere, great minds, stable funding and the freedom and willingness to solve real-world problems. He also points out and clear a lot of the admittedly confusing history that is the legacy of Unix as the (licensed) source code of Unix reached Berkeley. As he correctly pointed out, AT&amp;amp;T tried to &amp;ldquo;lock the barn door after the crown jewels had flown the coop&amp;rdquo;. And then we had BSD, Minix, Linux, Xenix and yada yada.&lt;/p&gt;&#10;&lt;p&gt;He talks about the revered “Unix philosophy” and spells it out in its entirety which is what I was looking for. He talks about the genius of Ken Thompson (May the Nerd God bless him), the technical eloquence of Dennis Ritchie, the management of Doug and so on. Who wouldn&amp;rsquo;t love to work at a place where people respect and rever things they loves?&lt;/p&gt;&#10;&lt;p&gt;He kind of skims very gently on the technical innovation that Unix brought to the table such as pipes, hierarchial file system, user-level shell programs along with the basic good design decisions and principles of system design.&lt;/p&gt;&#10;&lt;p&gt;He talks about pranks on other colleagues, defiance of the management and other geeky things that sound wholesome and naive (It makes me feel that Gods do sometimes play). He points out rather coyly the Unix system in use in the Jurassic Park which controlled the security locks and latches and how Lex navigated it and saved the day. He talks about the infamous comment in the Unix source code that apparently described code responsible for context-switching between processes and it went like:&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;/** You are not expected to understand this. **/&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;I think I can safely attribute the code it described to Ken :P.&lt;/p&gt;&#10;&lt;p&gt;And he did all that in his usual mix of humour and nostalgia which is heart-warming and I guess will be to anyone who had helped build something that has literally changed the world.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;The masters have shown the way, it is time we lead.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h2 id="logs"&gt;Logs&lt;/h2&gt;&#10;&lt;p&gt;This was a (relatively) short book and I think I completed it in three to four sittings. I finished it on Mar 30, 2021 though I&amp;rsquo;m sure to dig into the myriad of resources Brian has linked to. I also want to get my hands on the Unix src (6th edition) preferably with Lions&amp;rsquo; commentary.&lt;/p&gt;&#10;</description></item><item><title>Thoughts: Design for Hackers (Book)</title><link>https://basillabib.com/post/thoughts-design-for-hackers-book/</link><pubDate>Sun, 14 Mar 2021 00:00:00 +0000</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/thoughts-design-for-hackers-book/</guid><description>&lt;p&gt;Design. An idea which I admit rather shamefully to not have paid any serious attention to (until now).&lt;/p&gt;&#10;&lt;p&gt;Design of a system happens before building not after.&lt;/p&gt;&#10;&lt;p&gt;This, it seems, is a general attitude towards design by builders and engineers or people who work on the guts or so called backend of a system as was coyly pointed out by the author himself.&lt;/p&gt;&#10;&lt;p&gt;Unfortunately, it is true. Design is something I have always thought of as an after thought in my projects at least uptil now. But no more!&lt;/p&gt;&#10;&lt;p&gt;The more I dwelled into David Kadavy&amp;rsquo;s excellent book on Design, I began to appreciate design at a much, much deeper level. This book is exactly what it says on the tin and I like it for how it tries to make a seemingly abstract idea like design accessible in a concrete manner to a &amp;ldquo;hacker&amp;rdquo; (ie one who is functionality minded) by employing lots of examples.&lt;/p&gt;&#10;&lt;p&gt;Kadavy took care to pick out real world good design examples and explain what makes them good. He points out the underlying design principles that generally governs the interface of a design. Seriously though, how on Earth would I have expected to understand why Manet&amp;rsquo;s “Luncheon on the Garden” was the beginning of Impressionism or what makes Seurat&amp;rsquo;s “A Sunday afternoon on the Island of La Grand Jatte” a captivating painting?!&lt;/p&gt;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-fb6c60ae" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-fb6c60ae" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/luncheon-on-the-grass-manet.png" alt="The Luncheon on the Grass by Edouard Manet" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-fb6c60ae" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/luncheon-on-the-grass-manet.png" alt="The Luncheon on the Grass by Edouard Manet" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;The Luncheon on the Grass by Edouard Manet&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure class="image-caption image-zoom-container"&gt;&lt;input type="checkbox" id="img-fa47ae23" class="image-zoom-toggle" /&gt;&#10; &lt;label for="img-fa47ae23" class="image-zoom-label"&gt;&#10; &lt;img src="https://basillabib.com/img/sunday-afternoon-seurat.jpg" alt="A Sunday Afternoon on the Island of La Grand Jatte by George Seurat" loading="lazy" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;label for="img-fa47ae23" class="image-zoom-overlay"&gt;&#10; &lt;img src="https://basillabib.com/img/sunday-afternoon-seurat.jpg" alt="A Sunday Afternoon on the Island of La Grand Jatte by George Seurat" class="zoomable-image" /&gt;&#10; &lt;/label&gt;&#10; &lt;figcaption&gt;A Sunday Afternoon on the Island of La Grand Jatte by George Seurat&#10; &lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h2 id="how-did-i-get-hold-of-this-book"&gt;How did I get hold of this book?&lt;/h2&gt;&#10;&lt;p&gt;Ok, this is another one of my web rabbit hole expedition. I was reading &lt;a href="http://www.catb.org/~esr/faqs/hacker-howto.html" target="_blank" rel="noopener noreferrer"&gt;ESR&amp;rsquo;s jargon files&lt;/a&gt;&#10; where he references a &lt;a href="https://letsgolarval.wordpress.com/" target="_blank" rel="noopener noreferrer"&gt;blog&lt;/a&gt;&#10; by an anon CS student for Linux newbies. Did I need any restraint? Off I went through most of her posts (which were fun and properly written) and found this book in one of her currently readings from perhaps 2016(!). She isn&amp;rsquo;t active as of today. Last post is dated ~2018. &amp;#x1f937;&amp;zwj;&amp;#x2642;&amp;#xfe0f; I enjoyed her writings to be honest.&lt;/p&gt;&#10;&lt;h2 id="design-is-not-definitive"&gt;Design is not definitive&lt;/h2&gt;&#10;&lt;p&gt;Of course, these principles are not set in stone as the subject it deals with is inherently a creative discipline. However, the ideas presented are a general guide for the uninitiated and they seem to be present in almost all good designs so I conclude they must be good heuristics. For eg: the idea of dominance, or direction, composition etc.&lt;/p&gt;&#10;&lt;p&gt;The point is design is not something one gains by reading a book. It is not a place where you can reach and exclaim to others “I have arrived!”&lt;/p&gt;&#10;&lt;p&gt;You can read a &lt;a href="https://basillabib.com/post/notes-design-for-hackers-by-david-kadavy"&gt;book summary I prepared&lt;/a&gt;&#10; for more or better yet read the book (which I highly recommend) if you tinker with stuff.&lt;/p&gt;&#10;&lt;h2 id="what-next"&gt;What next?&lt;/h2&gt;&#10;&lt;p&gt;I would love to go to art exhibitions and develop a taste for art as I can now appreciate the ideas latent therein with more fervour.&lt;/p&gt;&#10;</description></item><item><title>Gemini Protocol</title><link>https://basillabib.com/post/gemini-protocol/</link><pubDate>Thu, 25 Feb 2021 00:00:00 +0000</pubDate><author>gs454236@gmail.com (Basil Labib)</author><guid>https://basillabib.com/post/gemini-protocol/</guid><description>&lt;p&gt;Gemini is a minimal &lt;a href="https://en.wikipedia.org/wiki/Application_layer" target="_blank" rel="noopener noreferrer"&gt;application layer&lt;/a&gt;&#10; request-response protocol that aims to make a &lt;em&gt;Geminispace&lt;/em&gt; of information to be accessible to anyone even with slow/intermittent internet connection. Think HTTP 0.9 or a less restraining Gopher.&lt;/p&gt;&#10;&lt;h2 id="tldr"&gt;TLDR&lt;/h2&gt;&#10;&lt;p&gt;If you haven&amp;rsquo;t checked out Gemini yet, you &lt;a href="https://gemini.circumlunar.space" target="_blank" rel="noopener noreferrer"&gt;totally should&lt;/a&gt;&#10;. This is a HTTP proxy to the actual gemini site.&lt;/p&gt;&#10;&lt;h2 id="why-gemini"&gt;Why Gemini?&lt;/h2&gt;&#10;&lt;p&gt;I really value your time and understand you have better things to do. So, I&amp;rsquo;ll cut down on formalities and introductions and preludes and yada yada. My format might be quirky but my motive is always to be efficient and effective and non-conformant.&lt;/p&gt;&#10;&lt;p&gt;Gemini appeals to me simply because of it&amp;rsquo;s lack of &lt;em&gt;noise&lt;/em&gt;. Of course, noise is a very generic term and doesn&amp;rsquo;t make anything anymore clearer so hold on.&lt;/p&gt;&#10;&lt;p&gt;The WWW has evolved. It is not anymore the &amp;ldquo;collaborative space where you can communicate through sharing information&amp;rdquo; but has become so much more. It&amp;rsquo;s like a &lt;strong&gt;clamoring, dazzling marketplace&lt;/strong&gt;. Which is&amp;hellip;.good&amp;hellip;and bad. Good because there is something for everyone. The space Tim dreamt of certainly exists (and is thriving) and I admire that. WWW and HTTP has empowered man like nothing before.&lt;/p&gt;&#10;&lt;p&gt;Ironically, that is the bane too. The sheer amount of information that the modern woman seems to drowning in, she suffers from lack of clarity. It is so damn easy to get lost in the noises and forget your original motives. And that, my friend, is something I cannot do. &lt;strong&gt;Clarity of thought cannot be bargained with.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Gemini is cool precisely because it doesn&amp;rsquo;t offer much (but don&amp;rsquo;t be fooled to believe it can&amp;rsquo;t do much). It is designed deliberately to be non-extensible and thus, only serve the real purpose for any network - &lt;strong&gt;pure diffusion of information&lt;/strong&gt;. Gemini is text-based (however, you can use any of the MIME formats to serve data). A big upshot is because it lacks images and other media (in a pure sense), there is virtually no ads to distract you. No banners that crept up and goddamn &lt;em&gt;blink&lt;/em&gt;!&lt;/p&gt;&#10;&lt;h2 id="how-does-it-work"&gt;How does it work?&lt;/h2&gt;&#10;&lt;p&gt;Gemini servers are applications listening on remote hosts implementing the protocol. They, by default, listen on port 1965 (the year the first manned Gemini flew as the specification so humbly states). One can have multiple domains being served from the same IP address. The protocol defines a new MIME type &lt;code&gt;text/gemini&lt;/code&gt; to serve gemini files (analogous to &lt;code&gt;text/html&lt;/code&gt; over HTTP) which is a light-weight markdown format and renders like HTML.&lt;/p&gt;&#10;&lt;p&gt;The render process is not precisely laid out in the specification and is upto the client to implement. The resultant &lt;code&gt;.gemini&lt;/code&gt; or &lt;code&gt;.gmi&lt;/code&gt; files are served over a regular TCP connection which is meant for exactly one request-response cycle. Note that however it increases network latency, it serves the greater good of protecting end users from noise by preventing clients from making multiple requests for unneccessary jargon. And &lt;em&gt;I care about that&lt;/em&gt;!&lt;/p&gt;&#10;&lt;p&gt;The protocol can serve text or binary data and let the client handle it. And what&amp;rsquo;s better? If you can&amp;rsquo;t find a &amp;ldquo;rational&amp;rdquo; client, you can write one because the protocol is so simple. &lt;em&gt;I care for this empowerment. It is most crucial for me to know precisely how the system works&lt;/em&gt;. &lt;strong&gt;Ignorance is an eyesore&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;p&gt;You can link to other resources in the Geminispace or even link to HTTPS or Gopher resources! Besides, Gemini takes security very seriously (unlike Gopher or HTTP) by enforcing a TLS handshake for every connection.&lt;/p&gt;&#10;&lt;p&gt;Check out the spec: &lt;a href="https://gemini.circumlunar.space/docs/specification.html" target="_blank" rel="noopener noreferrer"&gt;Gemini Protocol Specification&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&lt;h2 id="gemini-clients"&gt;Gemini clients&lt;/h2&gt;&#10;&lt;p&gt;Due to the relative simplicity of the protocol, there is a number of clients written in a number of languages. However, I could test drive only one which is &lt;a href="https://tildegit.org/solderpunk/AV-98/" target="_blank" rel="noopener noreferrer"&gt;AV-98&lt;/a&gt;&#10;, a client written in Python by the original designer of the protocol itself.&lt;/p&gt;&#10;&lt;p&gt;It&amp;rsquo;s a nice little application though not cross-platform. I tried it inside a VM box running Debian. I can use this thing if it can serve me daily news in my terminal, though I am not sure how many local sites here allow scraping. Would RSS be better? Yeah, I think it&amp;rsquo;d work but &lt;strong&gt;who lives in the past?&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;The repo is &lt;a href="https://tildegit.org/solderpunk/AV-98/" target="_blank" rel="noopener noreferrer"&gt;here&lt;/a&gt;&#10;. Show it some love.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m fiddling with writing a client myself but shhh&amp;hellip;&lt;/p&gt;&#10;&lt;h2 id="more-resources"&gt;More resources&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;This is a great repo which is comprehensive enough to get you running (wild): &lt;a href="https://github.com/kr1sp1n/awesome-gemini" target="_blank" rel="noopener noreferrer"&gt;Awesome Gemini&lt;/a&gt;&#10;.&lt;/li&gt;&#10;&lt;li&gt;This is a gemlog site I found, that is hosting a site in gemini space and providing a nice proxied HTTP interface to users (possibly for convenience or to attract less tech-savvy people) which IMO is very cool. It doesn&amp;rsquo;t ask for all my details and does what it should: maintain my anonymity and let me log. &lt;a href="https://gemlog.blue/" target="_blank" rel="noopener noreferrer"&gt;gemlog.blue&lt;/a&gt;&#10;. Definitely check it out!&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>