<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://yifei.sg/feed.xml" rel="self" type="application/atom+xml" /><link href="https://yifei.sg/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-07-09T22:53:18+08:00</updated><id>https://yifei.sg/feed.xml</id><title type="html">Yifei’s Blog</title><subtitle>Notes on data systems, infrastructure, dashboards, hardware projects, and applied AI.</subtitle><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><entry><title type="html">Harnessed, Not Handcuffed: The Design Tension in Agentic AI</title><link href="https://yifei.sg/jekyll/update/2026/07/08/harnessed-not-handcuffed.html" rel="alternate" type="text/html" title="Harnessed, Not Handcuffed: The Design Tension in Agentic AI" /><published>2026-07-08T22:00:00+08:00</published><updated>2026-07-08T22:00:00+08:00</updated><id>https://yifei.sg/jekyll/update/2026/07/08/harnessed-not-handcuffed</id><content type="html" xml:base="https://yifei.sg/jekyll/update/2026/07/08/harnessed-not-handcuffed.html"><![CDATA[<p>Across the past few posts it should be evident that harnesses, not Large Language Models (LLMs) alone, are a necessary component in any agentic AI system.</p>

<p>And indeed, a rapidly growing ecosystem of public repositories supports this, most notably comprising skills, specs, agent toolkits, MCP servers, and other integration layers.</p>

<p>Yet, given limited context windows, it is equally clear that one cannot simply throw every possible context, skill or tool into an agentic system and expect optimal performance.</p>

<p>This is where the tension occurs. The question is not simply how much control an agentic system requires, but where that control should live.</p>

<h2 id="documentation-from-model-creators">Documentation from Model Creators</h2>

<p>Official documentation does not support a maximalist reading of agent harnesses.</p>

<p><a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents">Anthropic</a> directly states that “good context engineering means finding the smallest possible set of high-signal tokens that maximize the likelihood of some desired outcome.” The same principle applies to prompts: “you should be striving for the minimal set of information that fully outlines your expected behavior,” while noting that “minimal does not necessarily mean short.”</p>

<p>This applies similarly to tools. Anthropic also warns that “one of the most common failure modes” is “bloated tool sets that cover too much functionality or lead to ambiguous decision points about which tool to use,” and concludes that builders should “keep your context informative, yet tight.”</p>

<p><a href="https://developers.openai.com/cookbook/examples/agents_sdk/session_memory">OpenAI</a> makes the same point operationally: “If too much is carried forward, the model risks distraction, inefficiency, or outright failure,” and even a large context window “can be overwhelmed by uncurated histories, redundant tool results, or noisy retrievals.”</p>

<p>In other words, a good harness does not expose everything upfront; it exposes enough for the model to act decisively.</p>

<blockquote>
  <p><strong>TL;DR</strong>: More is not always better.</p>
</blockquote>

<h2 id="where-control-should-live">Where Control Should Live</h2>

<p>Not all additional context clarifies: high-signal content can reduce uncertainty, while low-signal or competing instructions can introduce ambiguity, conflict, and ultimately misapplication.</p>

<p>In other words, <a href="https://arxiv.org/abs/2510.05381">long(er) contexts are not necessarily better</a>; if they are not informative, they can be worse than no context at all.</p>

<p>This makes it vital to distinguish the levels at which control is exercised:</p>

<ul>
  <li>Tier 1: outside-prompt, hard controls</li>
  <li>Tier 2: inside-prompt, unavoidable controls</li>
  <li>Tier 3: inside-prompt, soft guidance</li>
  <li>Everything else: suspicious by default</li>
</ul>

<h3 id="tier-1-outside-prompt-hard-controls">Tier 1: Outside-Prompt, Hard Controls</h3>

<p>A “hard control” here refers to a constraint that is enforced by the system and cannot be overridden by the LLM’s probabilistic interpretation. Such controls include:</p>

<ul>
  <li>File permissions enforced by the operating system</li>
  <li>Schema validity enforced by a validation engine</li>
  <li>Access control enforced by an API gateway</li>
  <li>Coding standards enforced by a linter</li>
  <li>Correctness enforced by tests and checks</li>
</ul>

<p>It is worth noting that even though system prompts sit higher than user prompts in the instruction hierarchy, neither fully determines how robust the control is. They are ultimately still natural language interpreted by the model, and thus probabilistic.</p>

<h3 id="tier-2-inside-prompt-unavoidable-controls">Tier 2: Inside-Prompt, Unavoidable Controls</h3>

<p>Some constraints cannot be moved outside the prompt because the model must interpret them as part of performing the task.</p>

<p>Examples include:</p>

<ul>
  <li>Required output formats, such as JSON</li>
  <li>Mandatory fields or sections to output</li>
  <li>Explicit task boundaries so models know where to stop</li>
  <li>Tool-use requirements that depend on semantic judgement, such as when to search, calculate, or invoke another skill</li>
</ul>

<p>These controls should be treated as necessary overhead: precise, explicit, and as concise as possible, because the objective is to minimise the amount of interpretation required.</p>

<p>Where possible, their outputs should still be validated or enforced through subsequent layers of external hard controls described in Tier 1.</p>

<blockquote>
  <p><strong>TL;DR</strong>: interpret in-prompt only where necessary; enforce outside wherever possible.</p>
</blockquote>

<h3 id="tier-3-inside-prompt-soft-guidance">Tier 3: Inside-Prompt, Soft Guidance</h3>

<p>Beyond unavoidable controls lies guidance intended to shape direction rather than explicitly define output.</p>

<p>Examples include:</p>

<ul>
  <li>Preferred reasoning or problem-solving approaches</li>
  <li>Writing style, tone, or level of detail</li>
  <li>Heuristics for choosing between tools or strategies</li>
  <li>General workflow conventions</li>
  <li>Suggestions about what to prioritise or avoid</li>
</ul>

<p>Such guidance <em>can</em> be extremely useful, but unlike Tier 2 controls, the burden of proof is on the designer or operator to justify their inclusion.</p>

<p>Beyond some critical mass, instructions may begin to compete with one another, reducing the model’s ability to follow all of them effectively.</p>

<p>Not to mention, context is scarce.</p>

<h2 id="preserving-implicit-judgment">Preserving Implicit Judgment</h2>

<p>Tier 3 carries another cost beyond context and instruction competition: excessive guidance may displace the very judgement the model is meant to contribute.</p>

<p>As discussed previously in <a href="/jekyll/update/2026/07/02/judgement-agentic-ai.html">Judgement in the Era of Agentic AI</a>, one potentially valuable property of LLMs is their ability to approximate forms of tacit judgement that cannot be reduced into explicit rules.</p>

<p>They can recognize fit, mismatch, plausibility, local coherence, and other patterns without being given an explicit procedure to do so.</p>

<p>Excessive explicit instruction may suppress this capacity, reducing an agent capable of flexible tacit judgement into one primarily occupied with following procedure.</p>

<p>Though to be fair, this may sometimes be the desired outcome.</p>

<h2 id="conclusion-the-design-tension">Conclusion: The Design Tension</h2>

<p>Which brings us back to the design problem: not all forms of control belong in the prompt.</p>

<p>Where a constraint can be enforced deterministically outside the prompt, it generally should be. Where interpretation is unavoidable, the instruction should be precise and minimal. Where guidance is merely intended to shape behaviour, its inclusion should carry a burden of proof.</p>

<p>This does not eliminate the design tension, since an agent still requires sufficient context, tools, and direction to act effectively.</p>

<p>Treating every desired behaviour as another prompt instruction is itself a design choice—but often a sloppy substitute for architecture.</p>

<p>A good harness therefore constrains what must be constrained, exposes what must be exposed, and minimises everything else to leave space for judgement.</p>

<p>Harnessed, not handcuffed.</p>]]></content><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[Why good agent harnesses must balance structure, context, and control without suppressing model judgement.]]></summary></entry><entry><title type="html">Judgement in the Era of Agentic AI</title><link href="https://yifei.sg/jekyll/update/2026/07/02/judgement-agentic-ai.html" rel="alternate" type="text/html" title="Judgement in the Era of Agentic AI" /><published>2026-07-02T12:00:00+08:00</published><updated>2026-07-02T12:00:00+08:00</updated><id>https://yifei.sg/jekyll/update/2026/07/02/judgement-agentic-ai</id><content type="html" xml:base="https://yifei.sg/jekyll/update/2026/07/02/judgement-agentic-ai.html"><![CDATA[<p>Judgement, and ultimately decision-making, has been a recurring theme across the previous three posts:</p>

<ul>
  <li><a href="/jekyll/update/2026/05/07/day1-vs-day2-agentic-ai.html">Day-1 vs. Day-2 in the Era of Agentic AI</a></li>
  <li><a href="/jekyll/update/2026/05/22/autonomy-is-overrated.html">Autonomy is Overrated: Human Staffing vs. Agentic AI</a></li>
  <li><a href="/jekyll/update/2026/06/09/marginal-economics-agentic-ai.html">The Marginal Economics of Agentic AI</a></li>
</ul>

<p>Yet, because neither term was explicitly defined, it might not be apparent what they mean in the context of agentic AI—especially when some agentic AI loops <em>appear</em> capable of making decisions autonomously.</p>

<p>This post examines what judgement still means in the current era of agentic AI.</p>

<h2 id="frame-versus-procedure">Frame Versus Procedure</h2>

<p><a href="https://en.wikipedia.org/wiki/Problem_structuring_methods">Herbert Simon distinguished between programmed and non-programmed decisions</a>.</p>

<p>Programmed decisions are those that are routine, repetitive, structured or well-defined, rely on standard operating procedures (SOPs), have clear lines of responsibility, and are therefore usually delegatable.</p>

<p>Non-programmed decisions, on the other hand, are novel, unstructured, and usually consequential or impactful. They cannot be resolved simply by applying an existing procedure.</p>

<p>In other words, the former operate within an accepted frame, and are therefore procedural; the latter require setting, revising, or choosing the frame itself, and it is where <em>real</em> judgement is required.</p>

<p>The problem is that the distinction is often unclear: what is colloquially referred to as “judgement” is often just <em>procedural</em> decision-making under mild ambiguity.</p>

<h2 id="agentic-ai-masters-of-procedural-decision-making">Agentic AI: Masters of Procedural Decision-Making</h2>

<p>Consider the simple example: cleanup of messy spreadsheets into structured CSVs. During this process:</p>

<ul>
  <li>Various coercions are applied to columns</li>
  <li>Multi-row headers are merged</li>
  <li>Merged cells are split and filled</li>
  <li>Missing values are standardized</li>
  <li>Duplicate rows are detected and handled</li>
  <li>Empty columns and empty rows are dropped</li>
  <li>Potentially reshaping from wide to long or vice versa</li>
  <li>Etc.</li>
</ul>

<p>Every explicit step above <em>appears</em> to require some form of judgement. At the same time, every listed step has known solutions—extremely well-known solutions, in fact.</p>

<p>So much so that for this class of spreadsheet-cleaning work, current agentic AI loops have already crossed the practical threshold. In repeated use, they can execute the entire process with minimal prompting and supervision, producing better results faster than a human analyst could.</p>

<p>It is not that the process is trivial or even deterministic, it is that the solution space is saturated, and therefore LLMs are increasingly better trained to handle such cases with appropriate tools.</p>

<p>And this is why it may appear confusing to observers. Agentic AI appear to be autonomously making judgement calls, but in reality they are just selecting among routine procedures within a pre-defined frame—programmed decision-making under mild ambiguity.</p>

<h2 id="moving-from-procedural-to-real-judgement">Moving from Procedural to Real Judgement</h2>

<p>At the same time, using the same example, consider the following questions:</p>

<ul>
  <li>Should the data be cleaned at all?</li>
  <li>If so, what is the purpose of cleaning it?</li>
  <li>What are the downstream implications and usage?</li>
  <li>Is this a one-off cleanup, or will this process be repeated?</li>
  <li>If repeated, should the process be automated, and if so, how?</li>
  <li>Is the real problem the messy spreadsheet, or the upstream process that produced it?</li>
</ul>

<p>It is immediately apparent that these questions are either frame-setting or frame-challenging, and therefore mark where real judgement begins.</p>

<p>In Argyris and Schön’s terms, asking such questions essentially turns the situation from single-loop to <a href="https://en.wikipedia.org/wiki/Double-loop_learning">double-loop</a>.</p>

<p>And the key is <em>not</em> having the right answer. It is recognizing that <em>appropriate</em> questions need to be asked at all.</p>

<p>Because the answers may eventually become procedural, codified, or automated, but discretionary framing decisions will likely not.</p>

<p>This same local situation also scales, from one messy spreadsheet to the software economy as a whole.</p>

<h2 id="the-revealed-scarcity">The Revealed Scarcity</h2>

<p><img src="/assets/images/new-apps-and-usage-across-app-stores.png" alt="Figure 12 showing new apps and aggregate usage across iOS, Android, and Chrome app stores" /></p>

<blockquote>
  <p><strong>Source:</strong> Figure 12, “New Apps and Their Usage Across App Stores,” from Mert Demirer, Leon Musolff, and Liyuan Yang, “<a href="https://www.nber.org/system/files/working_papers/w35275/w35275.pdf">Writing Code vs. Shipping Code: Productivity Effects Across Generations of AI Coding Tools</a>,” NBER Working Paper No. 35275, May 2026.</p>
</blockquote>

<p>The figure illustrates the distinction between producing software and producing value: new app creation rises sharply, especially for iOS and Chrome, but downstream usage signals such as ratings and downloads do not show a proportional increase.</p>

<p>The implication is clear: the scarcity of software production has been significantly alleviated.</p>

<p>In turn, this reveals what the friction of production had previously obscured: the scarcity of judgement over what should be built, shipped, trusted, and maintained.</p>

<p>There is, however, a risk of drawing the boundary too neatly. If the argument stopped here, it would suggest that agentic AI is merely procedural. Reality is much more nuanced and complex than the binary procedural-versus-judgemental after all.</p>

<h2 id="modeling-tacit-judgement">Modeling Tacit Judgement</h2>

<p>Michael Polanyi’s assertion that “we can know more than we can tell” captures the difficulty of turning <a href="https://en.wikipedia.org/wiki/Tacit_knowledge">tacit knowledge</a> and judgement into procedure.</p>

<p>This used to mark a boundary for automation.</p>

<p>Now LLMs blur and push this boundary.</p>

<p>As Cheng argues in “<a href="https://arxiv.org/abs/2603.15238">Why the Valuable Capabilities of LLMs Are Precisely the Unexplainable Ones</a>,” the truly valuable capabilities of LLMs reside precisely in the part that cannot be fully captured by human-readable discrete rules—because if they could be fully reduced to rules, they would merely reproduce the expert-system paradigm that LLMs have already surpassed.</p>

<p>Real-world usage and observation suggest that agentic AI does not only execute explicit procedures, the underlying LLMs can also approximate some kind of tacit judgement: they infer what fits from high-dimensional learned patterns, including semantic, procedural, stylistic, and logical regularities absorbed from massive prior examples.</p>

<p>It would thus be a mistake to say that LLMs cannot at least simulate judgement-based decisions. They clearly appear to do so, even if only in a limited sense of recognizing fit, mismatch, plausibility, and local coherence—even when no explicit SOP is provided.</p>

<p>This is why agentic AI can be useful far beyond executing simple checklists. It is particularly capable at drafting plans, critiquing assumptions, repairing code, and surfacing potential failure modes.</p>

<p>The capacity to operationalise latent patterns also means that agentic AI is continuously redefining the scope of judgement.</p>

<h2 id="conclusion-judgement-as-a-moving-frontier">Conclusion: Judgement as a Moving Frontier</h2>

<p>Some of what is commonly considered judgement is merely procedural decision-making, and agentic AI is already excellent at this.</p>

<p>Some judgement flows from tacit knowledge, and LLMs are increasingly able to approximate parts of this as well.</p>

<p>Each time a layer of judgement becomes repeatable, testable, or modelled, it moves closer to procedure. The frontier then shifts upward again, toward whatever remains unstable, underspecified, or not yet pattern-saturated.</p>

<p>Obviously, agentic AI is not a simple drop-in replacement for judgement. It does, however, continuously redraw the boundary between judgement and procedure.</p>

<p>In this era of agentic AI, judgement is not disappearing—it is simply becoming easier to see what was never judgement in the first place.</p>

<p>And when a system improves because someone made the hard calls, the remaining question is not whether judgement mattered.</p>

<p>It is who gets to own it.</p>]]></content><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[A systems-engineering view of judgement in agentic AI: where procedural decision-making ends, where framing decisions begin, and why AI shifts that frontier.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://yifei.sg/assets/images/new-apps-and-usage-across-app-stores.png" /><media:content medium="image" url="https://yifei.sg/assets/images/new-apps-and-usage-across-app-stores.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Marginal Economics of Agentic AI</title><link href="https://yifei.sg/jekyll/update/2026/06/09/marginal-economics-agentic-ai.html" rel="alternate" type="text/html" title="The Marginal Economics of Agentic AI" /><published>2026-06-09T12:30:00+08:00</published><updated>2026-06-09T12:30:00+08:00</updated><id>https://yifei.sg/jekyll/update/2026/06/09/marginal-economics-agentic-ai</id><content type="html" xml:base="https://yifei.sg/jekyll/update/2026/06/09/marginal-economics-agentic-ai.html"><![CDATA[<p>Following Parts <a href="/jekyll/update/2026/05/07/day1-vs-day2-agentic-ai.html">1</a> and <a href="/jekyll/update/2026/05/22/autonomy-is-overrated.html">2</a>, it is clear that the question is no longer whether agentic AI will occupy significant roles in organisations, but where and how.</p>

<p>This Part 3 approaches the problem from an economic stance: the optimal agentic AI workflow is neither maximum autonomy nor no autonomy, but economically bounded token <em>allocation</em>.</p>

<blockquote>
  <p>From ambiguity to context debt to stop-losses, the conclusion is the same: the most economically rational agentic system is not the one that can keep going, but the one that knows when to stop.</p>
</blockquote>

<h2 id="from-llm-to-agentic-ai">From LLM to Agentic AI</h2>

<p>An LLM is, by definition, a <em>model</em>; it cannot execute anything <em>per se</em>.</p>

<p>For it to function in an agentic system, it requires a harness: tools, memory, state management, permissions, instructions. The LLM supplies the reasoning and control signals, while the harness links the model to the execution environment. This combination is what we call “agentic AI”: a system capable of executing seemingly complex, multi-turn operations.</p>

<p>Many describe this as “autonomous” because the underlying engineering pattern is new to them, when in fact developers have been building (and using) various types of execution harnesses for <em>decades</em>. The novelty is that an LLM can now sit inside a suitably configured loop as a language-based controller, adapting workflows at runtime.</p>

<p>But this flexibility comes with a scaling problem: the more the system is allowed to decide for itself, the more tokens it may spend resolving ambiguity.</p>

<h2 id="ambiguity-expands-the-search-space">Ambiguity Expands the Search Space</h2>

<p>At the centre of it all are <em>tokens</em>—the practical accounting unit for everything fed into and generated by LLMs.</p>

<p>The more complex and long-running a workflow is, the more tokens are consumed; but the true culprit is something that token-use scales non-linearly with: <em>ambiguity</em>.</p>

<p>Increasingly complex tasks will trigger more tool calls, context retrieval, intermediate reasoning steps, and validation loops. An unclear, ambiguous or poorly defined task does not merely consume more tokens: it could send the system down the wrong branches entirely, and repeatedly.</p>

<p>This is the equivalent of a client telling a consultant to “look into it” without any concrete requirements—or worse, saying one thing and meaning another. The consultant may work harder, run more analyses, and produce a longer report.</p>

<p>But as we know all too well, the output in either situation may still be unsatisfactory.</p>

<h2 id="autonomy-as-spend">Autonomy as Spend</h2>

<p>Thus, the economic reality becomes unavoidable: if each additional reasoning step or tool call has a cost, then the central question is not whether the AI <em>can</em> continue working, but whether it <em>should</em>.</p>

<p>An agentic workflow is, in effect, a production function: tokens, context, tools, time, and supervision are inputs; useful task progress is the output. But while raw token cost may be relatively linear, the cost per unit of useful progress is almost certainly not.</p>

<p>This is where marginal economics comes in. Tokens should be spent only up to the point where the expected marginal return still justifies the marginal cost. The implication is that the optimal AI workflow is <em>not</em> maximum autonomy, but maximum economic efficiency.</p>

<p>After all, <a href="/jekyll/update/2026/05/22/autonomy-is-overrated.html">autonomy is not just overrated</a>, it is also not free.</p>

<p>A bounded task reduces degrees of freedom: it gives the system less to infer, fewer branches to explore, and a clearer point at which to stop. On top of that, this also decreases time-to-output—token processing and generation is not instantaneous after all.</p>

<blockquote>
  <p>This is also why raw token-spend leaderboards are meaningless <em>per se</em>. High token-use is not necessarily productivity—it could just be the equivalent of staying late to look busy.</p>
</blockquote>

<h2 id="context-debt">Context Debt</h2>

<p>The cost of failed exploration is not limited to tokens already spent. Those tokens are sunk; the accumulated context they leave behind is not. In a multi-turn agentic workflow, failed branches often remain inside the working context even after they have stopped being useful.</p>

<p>Existing context is then processed alongside new inputs, and the implication is that the agent is no longer reasoning only about the original already-underspecified task; it is also reasoning through the residue of prior attempts.</p>

<p>This is a form of context contamination—or, in economic terms, context debt: past ambiguity becoming future marginal cost.</p>

<p>Context debt has a carrying cost. Each subsequent call must process, compress, ignore, or reason around the accumulated residue of failed exploration. The problem is not merely that the context becomes longer, but that the working set becomes lower quality: failed tool calls, stale assumptions, partial plans, and discarded branches may continue to shape future reasoning. Even compaction, commonly used to reduce context length, can worsen this by preserving the wrong abstractions and turning the original task into a lossy summary of prior confusion.</p>

<p>It can also become a human supervision problem. At some point, time is spent untangling the agent’s failed branches instead of simply making the original decision.</p>

<h2 id="stop-losses-as-a-feature">Stop-Losses as a Feature</h2>

<p>This is where stop-losses matter.</p>

<p>Currently, almost all agentic systems already have operational limits, such as max iterations, retry caps, guardrails, and timeouts. But these are technical limits rather than economic ones.</p>

<p>Economically, there should also be explicit points where the system stops, compresses context, narrows scope, escalates decisions to a human, switches to a cheaper path, or even declares that the task is no longer economically rational.</p>

<p>This reframes stopping as a feature rather than a failure. A stop-loss is not evidence that the agent has failed; it is a mechanism for preventing additional inference budget from being spent on a task whose expected value has already fallen.</p>

<h2 id="from-governance-to-allocation">From Governance to Allocation</h2>

<p>The practical question, then, is not simply what the agent is allowed to do, but how much inference budget the task is worth.</p>

<p>AI governance is often framed in terms of safety, permissions, compliance, or human approval. These remain important, but in an agentic world they sit on top of a more basic economic problem: allocating a metered resource under uncertainty.</p>

<p>This is where <a href="/jekyll/update/2026/05/22/autonomy-is-overrated.html#:~:text=Already%20Solved%3A%20Human,boundary%20for%20autonomy.">the staffing analogy</a> still matters. Human autonomy is already constrained by budget, priority, and expected value. Agentic autonomy should be treated the same way, except that every additional step also carries an explicit computational cost.</p>

<p>A properly designed agentic workflow should therefore specify what the agent can decide, which tools it can use, how much context it can retrieve, how many retries are acceptable, when “good enough” has been achieved, when a cheaper model is sufficient, and when escalation is more economical than continued token-spend.</p>

<p>After all, escalation to a human is itself a cost comparison: sometimes the cheapest next step is not more tokens, but judgment.</p>

<p>That is the marginal economics of agentic AI: not asking how much work the system can do, but when additional work stops being worth paying for.</p>]]></content><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[A marginal-economics model for agentic AI workflows, covering token allocation, ambiguity, context debt, stop-losses, and escalation.]]></summary></entry><entry><title type="html">Autonomy is Overrated: Human Staffing vs. Agentic AI</title><link href="https://yifei.sg/jekyll/update/2026/05/22/autonomy-is-overrated.html" rel="alternate" type="text/html" title="Autonomy is Overrated: Human Staffing vs. Agentic AI" /><published>2026-05-22T12:30:00+08:00</published><updated>2026-05-22T12:30:00+08:00</updated><id>https://yifei.sg/jekyll/update/2026/05/22/autonomy-is-overrated</id><content type="html" xml:base="https://yifei.sg/jekyll/update/2026/05/22/autonomy-is-overrated.html"><![CDATA[<p>Amid the current <a href="https://www.gartner.com/en/articles/hype-cycle-for-agentic-ai">hype around agentic AI</a> and growing expectations that AI will <a href="https://www.weforum.org/press/2025/01/future-of-jobs-report-2025-78-million-new-job-opportunities-by-2030-but-urgent-upskilling-needed-to-prepare-workforces/">reshape, reduce, or displace parts of the workforce</a>, there have already been numerous, and sometimes spectacular, failures:</p>

<ul>
  <li><a href="https://www.theguardian.com/technology/2026/apr/29/claude-ai-deletes-firm-database">Claude-powered AI agent’s confession after deleting a firm’s entire database</a></li>
  <li><a href="https://www.theguardian.com/world/2024/feb/16/air-canada-chatbot-lawsuit">Air Canada ordered to pay customer who was misled by airline’s chatbot</a></li>
  <li><a href="https://arstechnica.com/ai/2025/04/cursor-ai-support-bot-invents-fake-policy-and-triggers-user-uproar/">Company apologizes after AI support agent invents policy that causes user uproar</a></li>
  <li><a href="https://www.theguardian.com/technology/2023/may/31/eating-disorder-hotline-union-ai-chatbot-harm">US eating disorder helpline takes down AI chatbot over harmful advice</a></li>
</ul>

<p>The common thread is not simply that AI systems made mistakes; humans make mistakes too.</p>

<p>The deeper issue is that these failures were actually due to a <em>lack</em> of appropriate limitations, safeguards, and oversight. In a mature human or human-in-the-loop model, these failures would likely have been constrained by job scopes, access controls, review processes, and accountability.</p>

<p><strong>The failure was not autonomy itself, but unbounded or poorly scoped autonomy.</strong></p>

<p>Following my previous post on <a href="/jekyll/update/2026/05/07/day1-vs-day2-agentic-ai.html">Day-1 vs. Day-2 in the Era of Agentic AI</a>, this post explores autonomy in the context of human staffing and agentic AI.</p>

<blockquote>
  <p><strong>TL;DR</strong>: Autonomy is overrated in most cases, human or AI.</p>
</blockquote>

<h2 id="more-autonomy-means-a-larger-decision-space">More Autonomy Means a Larger Decision Space</h2>

<p>Humans implicitly suffer from <a href="https://en.wikipedia.org/wiki/Bounded_rationality">bounded rationality</a> and therefore <a href="https://en.wikipedia.org/wiki/Overchoice">choice overload</a>: an overabundance of options can lead to decision fatigue, analysis paralysis, and post-decision regret.</p>

<p>A parallel dynamic applies to agentic AI, as more autonomy means a larger decision space: more tools, more paths, more states to manage, and more opportunities to act on incomplete or incorrect assumptions.</p>

<p>Because autonomy is not just permission to act—it is also permission to <em>choose</em>. Choosing well requires context, constraints, and feedback; without these, failure compounds.</p>

<p>A major problem with unbounded autonomy is therefore not just safety, but also performance degradation.</p>

<h2 id="already-solved-human-staffing">Already Solved: Human Staffing</h2>

<p>“Staffing” in the human sense is not new: the concept of hiring people to delegate work to has been around for millennia. And along with hiring comes job scope and description, roles and responsibilities, performance expectations, accountability, and oversight.</p>

<p>After all, staffing is not just about filling a seat; it is about defining the boundaries of work and responsibility.</p>

<p>Roles also differ in the degree of autonomy they require: the more complex, strategic, or decision-heavy the work, the more discretion the role must be allowed.</p>

<p>In other words, there is no one-size-fits-all approach to staffing, precisely because there is no universal boundary for autonomy.</p>

<h2 id="unclear-territory-agentic-ai">Unclear Territory: Agentic AI</h2>

<p>Deploying an AI agent in an organization is, structurally, not unlike hiring humans: the introduction of a new actor into workflows with specific scope, responsibilities, and also potential for error.</p>

<p>And it follows that the same questions around authority, accountability, and oversight should apply: what is this agent permitted to do, who is accountable when it fails, and who is ultimately responsible for its actions and consequences?</p>

<p>In this case, however, what is missing is the battle-tested framework that guides human staffing—and the corresponding autonomy boundaries. After all, equivalence does not mean identity.</p>

<h2 id="translating-staffing-guardrails">Translating Staffing Guardrails</h2>

<p>The parallel is obviously not one-to-one, but many of the following are immediately recognizable:</p>

<table>
  <thead>
    <tr>
      <th>Human staffing</th>
      <th>Agentic AI equivalent</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Job scope</td>
      <td>Tool permissions / task schema</td>
    </tr>
    <tr>
      <td>Decision rights</td>
      <td>Write permissions / blast-radius limits</td>
    </tr>
    <tr>
      <td>Manager</td>
      <td>Orchestrator / planner / accountable owner</td>
    </tr>
    <tr>
      <td>SOPs</td>
      <td>Prompt + workflow definition</td>
    </tr>
    <tr>
      <td>Review process</td>
      <td>Human approval / automated eval</td>
    </tr>
    <tr>
      <td>Performance review</td>
      <td>Logging / benchmarks / regression tests</td>
    </tr>
    <tr>
      <td>Budget</td>
      <td>Token / compute / API cost limits</td>
    </tr>
    <tr>
      <td>Compliance</td>
      <td>Policy layer / sandbox / audit trail</td>
    </tr>
    <tr>
      <td>Institutional memory</td>
      <td>RAG / context graph / long-term memory</td>
    </tr>
    <tr>
      <td>Escalation path</td>
      <td>Interrupt / fallback / human handoff</td>
    </tr>
  </tbody>
</table>

<p>The key is that these are not merely defensive or control structures—they are also enablers. Clear boundaries, expectations, and escalation paths allow both humans and AI agents to perform with greater confidence <em>within</em> assigned roles.</p>

<h2 id="autonomy-is-overrated-architecture-is-not">Autonomy Is Overrated; Architecture Is Not</h2>

<p>In this current period of “<a href="https://www.gartner.com/en/articles/hype-cycle-for-agentic-ai#:~:text=Agentic%20AI%20reaches,across%20supporting%20capabilities.">Peak of Inflated Expectations</a>”, the appeal of agentic AI autonomy is often overstated. Organizations (and many individuals) want to believe that complex work can be delegated to free-roaming AI agents with minimal oversight, with as few keystrokes as possible.</p>

<p>But, as argued above, autonomy is not the same as effectiveness. In practice, most roles—human or AI—perform best when authority is bounded, not unlimited.</p>

<p>The path forward is therefore not to build ever more autonomous agents, but to design agentic system architectures: <em>bounded</em> environments where agents operate in.</p>

<p>Ultimately the goal is not to maximize autonomy, but to scope it to each role’s context, decision rights, and consequences of failure.</p>

<p><strong>Because the problem is not autonomy, but autonomy without staffing logic.</strong></p>]]></content><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[A staffing-inspired model for bounding agentic AI autonomy with scopes, permissions, oversight, budgets, escalation paths, and accountability.]]></summary></entry><entry><title type="html">Day-1 vs. Day-2 in the Era of Agentic AI</title><link href="https://yifei.sg/jekyll/update/2026/05/07/day1-vs-day2-agentic-ai.html" rel="alternate" type="text/html" title="Day-1 vs. Day-2 in the Era of Agentic AI" /><published>2026-05-07T12:00:00+08:00</published><updated>2026-05-07T12:00:00+08:00</updated><id>https://yifei.sg/jekyll/update/2026/05/07/day1-vs-day2-agentic-ai</id><content type="html" xml:base="https://yifei.sg/jekyll/update/2026/05/07/day1-vs-day2-agentic-ai.html"><![CDATA[<p>The recent Forbes article (<a href="https://www.forbes.com/sites/jasonwingard/2026/04/23/vibe-coding-will-break-your-company/">Vibe Coding Will Break Your Company</a>) warns that vibe coding goes beyond a software-development trend: it is also a stress test for organizational judgment. LLMs and agentic AI can generate code faster than organizations can decide who owns, reviews, secures, or maintains the result.</p>

<p>From an engineering perspective, this is analogous to the gap between day-1 creation and day-2 operation. A working prototype proves that something can be built. It does not prove that the surrounding organization can safely operate it.</p>

<p>Either way, the issue is not generation. The issue is premature trust.</p>

<blockquote>
  <p>Day-1 proves something can work. Day-2 proves whether the system deserves to exist.</p>
</blockquote>

<h2 id="day-1-the-happy-path">Day-1: The Happy Path</h2>

<p>The purpose of a proof-of-concept (or pilot, prototype, minimum-viable-product) is to demonstrate that a concept can be implemented quickly and cheaply. This is the “happy path”, where everything works as expected under ideal stress-free conditions.</p>

<p>In the context of AI-assisted development, barriers-to-entry for day-1 creation have been dramatically reduced. This is great for work with no ongoing operational dependency because the story can end at the day-1 product: one-off scripts, exploratory notebooks, and static content.</p>

<p>Also, to an increasingly large extent, this day-1 happy path can be executed by non-technical users as long as they can describe what they want. In other words, a significant democratization of the ability to <em>create</em>.</p>

<h2 id="day-2-the-operational-reality">Day-2: The Operational Reality</h2>

<p>Once something is required to run in production, the story changes.</p>

<p>“Production” does not only mean a public-facing application. It can also mean any internal dashboard, recurring report, automation, data pipeline, database, or workflow that has become part of normal operations.</p>

<p>The key distinction is not whether failure has consequences—even a bug in a one-off script can have major consequences—but whether the artifact has become an <em>ongoing dependency</em>. If people expect it to keep working, refresh, run again, inform decisions, or support downstream workflows, it is production in the operational sense.</p>

<p>Two practical examples illustrate this point:</p>

<ul>
  <li>
    <p>A CSV-backed app may demo well. But the moment multiple people treat it as shared infrastructure, the problem is no longer the interface, but state: concurrency, permissions, validation, recovery, and source of truth.</p>
  </li>
  <li>
    <p>A vibe-coded front end may look complete while the actual operating model is still missing. If non-technical users are expected to maintain it, the system needs content models, editing workflows, publishing rules, media handling, access control, and handover documentation.</p>
  </li>
</ul>

<p>These are aspects that day-1 prompts cannot fully capture because prompts usually describe the desired artifact, not the environment it must survive in.</p>

<p>To survive in production means that failure has somewhere to go. The system has to absorb changing data, imperfect users, broken assumptions, permission boundaries, dependency changes, monitoring gaps, handovers, and recovery. Once there is dependency, the artifact has entered the organization’s failure surface.</p>

<blockquote>
  <p><strong>TL;DR:</strong> Day-2 proves whether that artifact can be safely operated and/or maintained. This goes far beyond code generation, and requires engineering judgment, environmental awareness, long-term ownership, and ongoing governance.</p>
</blockquote>

<h2 id="the-historical-pattern">The Historical Pattern</h2>

<p>The current trend is an extension to earlier abstraction shifts in software engineering.</p>

<p>As the industry moved from machine code to higher-level languages, frameworks, managed runtimes, cloud platforms, and code generators, the value of manually producing lower-level mechanics declined, while the value of system understanding increased.</p>

<p>LLMs extend this pattern by compressing boilerplate and first-pass implementation even further. But they differ from earlier abstraction layers in one major way: they are less deterministic. A compiler, framework, or traditional code generator transforms known inputs into <em>predictable</em> outputs; LLMs and agentic systems infer intent, fill gaps, invent structure, choose libraries, make assumptions, and produce <em>plausible</em> artifacts that may or may not reflect the real operating constraints.</p>

<p>That matters because production environments are usually built around deterministic expectations: fixed interfaces, repeatable deployments, stable data contracts, known permissions, predictable failure modes, auditable changes, and recoverable states.</p>

<p>Generated code can be made reliable, but only after review, testing, constraint, and integration. To some extent, this can actually be assisted with further LLM prompts, but this requires clear developer <em>intent</em>. Until then, AI-generated code is a proposal, not a system.</p>

<p>The risk becomes even sharper when the LLM or agent remains inside the runtime, because the system is now probabilistic in operation. That requires bounded actions, logging, human review, fallbacks, evaluations, and clear operating limits.</p>

<blockquote>
  <p><strong>TL;DR:</strong> LLMs continue the historical pattern of abstraction moving work upward, but their non-determinism makes review, testing, integration, and operational judgment even more important.</p>
</blockquote>

<h2 id="the-evolving-role-of-engineers">The Evolving Role of Engineers</h2>

<p>That is why the engineering role does not disappear, because engineering judgment is the ability to decide not just whether something can be built, but whether it <em>should</em> be built, shipped, operated, maintained, and trusted.</p>

<p>As code generation becomes cheaper, engineering value moves upward: toward system design, operational ownership, architectural review, risk management, and production stewardship—the work traditionally associated with senior, staff, and lead engineers.</p>

<p>Correspondingly, the value proposition of junior developers also shifts toward understanding generated code, testing, debugging, documenting assumptions, maintaining bounded components, and learning how artifacts behave inside real systems.</p>

<blockquote>
  <p>Sidenote: This does spell some trouble for entry-level roles: can one pick up sound judgment when the first step is to review and test without knowing how to write?</p>
</blockquote>

<h2 id="conclusion">Conclusion</h2>

<p>The main issue is that LLMs and agentic AI do not just make it easier to build, they make it easier to create artifacts that <em>look</em> like systems before anyone has decided whether they <em>should</em> be.</p>

<p>Ultimately, the development advantage will not go to those who generate the most code or move the fastest, but to those who can judge which artifacts deserve to enter production and which should remain prototypes.</p>

<blockquote>
  <p>Agentic AI makes day-1 easier. It also makes day-2 more important.</p>
</blockquote>]]></content><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[Why agentic AI makes prototypes easier but production stewardship harder, using a day-1 versus day-2 frame for engineering judgement.]]></summary></entry><entry><title type="html">Year in Review (2025): Systems Engineering</title><link href="https://yifei.sg/jekyll/update/2025/12/03/year-in-review-2025.html" rel="alternate" type="text/html" title="Year in Review (2025): Systems Engineering" /><published>2025-12-03T19:30:00+08:00</published><updated>2025-12-03T19:30:00+08:00</updated><id>https://yifei.sg/jekyll/update/2025/12/03/year-in-review-2025</id><content type="html" xml:base="https://yifei.sg/jekyll/update/2025/12/03/year-in-review-2025.html"><![CDATA[<!-- Toggle Buttons -->
<div>
    <strong>View Mode Toggles:</strong>
  <button onclick="toggleVersion('original')">Original</button>
  <button onclick="toggleVersion('english')">Plain English (via LLM)</button>
</div>
<p><br /></p>

<!-- DENSE VERSION -->
<div id="original" style="display: block;">

  <p>The theme for 2025 was Systems Engineering—not just writing code, but architecting and deploying end-to-end systems which are secure by design, resource-efficient, and robust in data-handling. Each of the following projects pushes this principle in a different direction.</p>

  <h2 id="scalable-architecture-for-identity-linked-data">Scalable Architecture for Identity-Linked Data</h2>

  <p>Overarching the 2025 roadmap was an infrastructure platform engineered to link consumer wearable biometrics with verified national identity layers. This system bridges Polar AccessLink (biometrics) and SingPass MyInfo (identity), solving a critical challenge of ingesting high-volume and identity-sensitive health data—without exposing identity fields to intermediate states or database administrators.</p>

  <p>The platform was designed as a strictly event-driven, serverless ecosystem on AWS, emphasizing granular scalability and isolation.</p>

  <ul>
    <li>
      <p><strong>Identity Federation &amp; Data Privacy (updated)</strong>: One significant engineering complexity was the handling of NRIC data. A layered security flow was implemented: sensitive data is transmitted between services via secure HTTP POST (TLS)—preventing exposure in browser history or server logs—and is persisted in DynamoDB as an RSA-2048–encrypted payload. This ensures that only designated data custodians holding the private-key can recover the plaintext NRIC, while backend services only process plaintext in volatile memory during the transaction.</p>
    </li>
    <li>
      <p><strong>OLTP vs. OLAP Separation</strong>: Operational and analytical data planes were strictly separated. Ingestion is write-optimized, asynchronously routing payloads to DynamoDB and S3. Downstream analytical data is read-optimized via a scheduled incremental load process which transforms JSON into columnar Parquet format, and made queryable via Amazon Athena. Large-scale queries can thus be performed downstream without impacting live ingestion workloads, while also preserving cost-efficiency.</p>
    </li>
    <li>
      <p>Other areas which have broadly been covered in a <a href="/jekyll/update/2025/05/01/data-systems-infrastructure.html">previous post</a> include:</p>

      <ul>
        <li>Asynchronous concurrency, SQS buffering, rate-limiting</li>
        <li>Infrastructure as Code</li>
        <li>CI/CD and Containerization</li>
        <li>Observability and Diagnostics</li>
        <li>Development Environment and Tooling</li>
        <li>LLM-Assisted Development</li>
      </ul>
    </li>
  </ul>

  <h2 id="traffic-engineering-for-llm-fleet">Traffic Engineering for LLM Fleet</h2>

  <p><a href="https://github.com/zyf0717/llm-control-plane">llm-control-plane</a> is about managing a distributed fleet of self-deployed LLMs on heterogeneous hardware. A centralized smart proxy and routing layer was constructed.</p>

  <ul>
    <li>
      <p><strong>Semantic &amp; Hardware-Aware Routing</strong>: The system routes traffic intelligently—a <code class="language-plaintext highlighter-rouge">WorkloadClassifier</code> categorizes incoming prompts by intent (e.g., reasoning, programming, high-throughput etc.) with a lightweight LLM, enabling the <code class="language-plaintext highlighter-rouge">LLMRouter</code> to dispatch requests appropriately. By aligning task complexity with hardware capabilities, the routing layer assigns heavy inferential workloads to high-VRAM clusters and trivial queries to agile, low-overhead instances, ensuring optimal utilization of heterogeneous compute resources. These routing decisions assume that LLM nodes are themselves well-optimized. For example, one class of endpoints is backed by custom Vulkan builds of <code class="language-plaintext highlighter-rouge">llama.cpp</code> on Strix Halo, described in <a href="/jekyll/update/2025/08/27/building-llamacpp-strix-halo.html">this post</a>.</p>
    </li>
    <li>
      <p><strong>Streaming Proxy with Protocol Normalization</strong>: The core of the system is a custom FastAPI proxy designed to intercept and normalize OpenAI-compatible requests. A notable engineering challenge was unifying divergent model output formats—in particular separating internal “reasoning” traces (chain-of-though) from final text output. A robust stream processor that buffers and detects provider-specific delimiters (including <code class="language-plaintext highlighter-rouge">&lt;think&gt;</code> tags or proprietary control tokens) in real-time was implemented. This logic dynamically bifurcates the incoming Server-Sent Events (SSE) stream into distinct “reasoning” and “content” channels for the client for easier and standardized client-side consumption, while an <code class="language-plaintext highlighter-rouge">SSEAccumulator</code> simultaneously reconstructs the complete response server-side for conversation history logging (e.g. for context).</p>
    </li>
    <li>
      <p><strong>Real-Time Observability Plane</strong>: To validate these orchestration decisions, a dedicated observability interface was developed using Shiny for Python. This dashboard was specifically engineered to expose the router’s internal state, visualizing the rationale behind specific dispatches and surface other performance metrics. This mechanism provides transparent verification that complex workloads are correctly landing on high-compute targets, allowing for subsequent feedback-based fine-tuning and troubleshooting of the smart routing.</p>
    </li>
    <li>
      <p><strong>Stateless Core with Config-Driven Topology</strong>: The control-plane routing layer is stateless at the process level, and all LLM nodes are fully stateless. Endpoints are (re-)discovered and configured via a registry, while conversation state is stored centrally in persistent storage accessible only by the control plane. For each request, the router reconstructs the conversation from storage and sends the full context to a selected LLM node; these LLM nodes never hold or read conversational state themselves. This keeps the entire system horizontally scalable and simplifies failure recovery: any failed LLM nodes can be restarted or replaced without affecting in-memory session state.</p>
    </li>
    <li>
      <p><strong>Extensibility and Contract-First Design</strong>: The entire system is built around explicit contracts:</p>

      <ul>
        <li>a contract for how workloads are classified;</li>
        <li>a contract for how endpoints advertise capabilities; and</li>
        <li>a contract for how streams (reasoning + content) are emitted and consumed.</li>
      </ul>

      <p>By treating these as stable interfaces, it becomes straightforward to add new workloads (e.g., evaluation, embedding-only, high-token “dump” modes), new endpoints (new nodes, new runtimes), or new visualizations in the observability layer without destabilizing the existing system.</p>
    </li>
  </ul>

  <h2 id="algorithmic-rigor-in-signal-processing">Algorithmic Rigor in Signal Processing</h2>

  <p><a href="https://github.com/zyf0717/fit-diff">fit-diff</a> is a specialized analytical application for the validation of wearable sensor data. The complexity lies in transforming messy, asynchronous sensor data into a format suitable for statistical comparison—it employs mathematical optimization to reconcile asynchronous hardware clocks and proprietary PPG algorithmic variance.</p>

  <ul>
    <li>
      <p><strong>Adaptive Step-Size Search</strong>: The <code class="language-plaintext highlighter-rouge">determine_optimal_shift</code> function implements a search algorithm that iteratively shifts the “test” signal against the “reference.” It calculates an adaptive step size based on the greatest common divisor (GCD) of the sampling intervals of both files, ensuring the shift resolution matches the data density while attempting to minimize computational overhead.</p>
    </li>
    <li>
      <p><strong>Loss Function Optimization</strong>: The alignment isn’t arbitrary; it minimizes a specific loss function (Mean Absolute Error, Mean Squared Error) or maximizes a correlation function (Pearson, Concordance Correlation Coefficient) to find an appropriate temporal offset.</p>
    </li>
  </ul>

  <p>Ongoing work includes an algorithmic validation engine designed to detect anomalies within asynchronous data streams—without relying on “benchmark” devices. As participants are expected to perform similar activities, physiological patterns are also expected to somewhat converge. By treating heart rate streams as signals, and applying z-normalization and global cross-correlation, signal overlap can be maximized mathematically to establish a baseline of “expected” behavior. This process will serve primarily as a quality gate: rather than just aligning data, potential “outlier” devices or sensor failure incidents are flagged, and subsequently excluded from final analysis.</p>

  <h2 id="robust-data-acquisition-via-browser-automation">Robust Data Acquisition via Browser Automation</h2>

  <p>To correlate physiological metrics with environmental stressors, a custom Robotic Process Automation (RPA) engine was engineered to retrieve weather data locked behind a legacy portal lacking modern API access.</p>

  <p>HTTP scraping was insufficient due to the target’s frame-based architecture and session management. A highly-customized and extensible automation layer was thus built using Selenium to autonomously navigates the legacy interface, handling multi-step selection logic for weather stations and parameters, effectively creating a “phantom API” over a GUI-only backend.</p>

  <p>The system was designed to operate autonomously in a headless environment, orchestrating the scheduled download of TSV-like files as they are generated. These artifacts are subsequently normalized and synced to an S3 Data Lake, decoupling the downstream analytical layers from the fragility of the acquisition method and ensuring that recent environmental data is always available for correlation.</p>

  <p>Some key features of the system include:</p>

  <ul>
    <li>
      <p><strong>Fluent Interface Pattern</strong>: The core Automata class wraps the raw Selenium WebDriver in a Fluent Interface. This allows for method chaining, making the automation scripts readable and declarative. Instead of verbose Selenium boilerplate, the code reads like a set of instructions:</p>

      <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">automata</span><span class="p">.</span><span class="nb">open</span><span class="p">(</span><span class="n">url</span><span class="p">).</span><span class="n">select</span><span class="p">(...).</span><span class="n">input_text</span><span class="p">(...).</span><span class="n">click</span><span class="p">(...).</span><span class="n">wait</span><span class="p">(...).</span><span class="n">click</span><span class="p">.(...)</span>
</code></pre></div>      </div>

      <p>Even logic for logins and navigation is encapsulated in high-level methods.</p>
    </li>
    <li>
      <p><strong>Idempotent Merging Logic</strong>: The entire pipeline is designed to be re-runnable. The <code class="language-plaintext highlighter-rouge">_merge_with_existing_data</code> method checks S3 for existing Parquet partitions, loads them, merges newly retrieved data, and deduplicates based on timestamp and station ID. This ensures that re-running the scraper for overlapping dates does not corrupt the dataset, failed runs will automatically be retried without data loss, and the entire historical dataset can be rebuilt from scratch if absolutely necessary.</p>
    </li>
  </ul>

  <h2 id="data-lake-and-onboarding">Data Lake and Onboarding</h2>

  <p>This project is primarily a Shiny (Python) application, allowing users to explore AWS Data Lake through a reactive web interface rather than looking at metadata, schemas and even raw data files.</p>

  <ul>
    <li>
      <p><strong>State Management via Reactive Caching</strong>: Race conditions—where UI elements renders before schemas resolves—were fixed by preloading the AWS Glue catalog into memory at startup via <code class="language-plaintext highlighter-rouge">initialize_data_cache</code>. Despite the built-in reactivity, the reactive graph was also hardened by enforcing explicit dependencies in analysis functions, ensuring stale computations are invalidated only when the full context is available.</p>
    </li>
    <li>
      <p><strong>Hybrid Compute &amp; Dual-Layer Caching</strong>: The platform bridges serverless and local compute. Heavy data retrieval is offloaded to AWS Athena, while fine-grained statistical tests (ANOVA, OLS) run in-memory. To support this, a dual-layer caching strategy was implemented: global schema caching for UI responsiveness, and per-session query result caching to prevent redundant S3 scans and preserve Athena concurrency limits.</p>
    </li>
    <li>
      <p><strong>Semantic Data Integrity</strong>: To address “schema-on-read” ambiguity, instead of relying on automated detection, the system relies on configurations (<code class="language-plaintext highlighter-rouge">ui_config.py</code>) to enforce data types. This guarantees that identifiers like numeric IDs are treated as categorical variables, disallowing erroneous regression analyses and ensuring robustness of the automated statistical engine.</p>
    </li>
    <li>
      <p><strong>Client-Side Infrastructure Automation</strong>: A PowerShell automation script was used to encapsulate complex ODBC connection parameters (e.g., AWS region, IAM profiles, S3 output locations) into a single execution. It ensures every analyst’s local environment is identical, effectively decoupling connectivity troubleshooting from the actual data analysis work.</p>
    </li>
  </ul>

</div>

<!-- ENGLISH VERSION -->
<div id="english" style="display: none;">

  <p>The theme for 2025 was Systems Engineering—not just writing code, but architecting and deploying end-to-end systems that are secure by design, resource-efficient, and robust in data-handling. Each project that follows pushes that principle in a different direction.</p>

  <p><em>Note: the following sections are almost completely LLM-generated based on the version marked “Original”.</em></p>

  <h2 id="scalable-architecture-for-identity-linked-biometric-data">Scalable Architecture for Identity-Linked Biometric Data</h2>

  <p>The core platform for 2025 was a secure bridge between consumer wearables and verified national identity. In practical terms: it connects Polar (for heart rate and activity) with SingPass MyInfo (for identity), while making sure that sensitive details like NRIC never appear in plain text to anyone operating or maintaining the system.</p>

  <p>The platform is built as a collection of micro-services on AWS that only activate when needed, instead of one big always-on server. This makes it easier to scale, isolate problems, and keep the system maintainable over time.</p>

  <ul>
    <li>
      <p><strong>Hybrid Encryption &amp; Identity Federation</strong>
Handling NRIC data required extra care. The system leverages TLS-encrypted POST requests for secure transit between components—preventing leakage in logs or browser history—while ensuring that only an asymmetrically encrypted, locked version is stored in the database. A small group of authorised data custodians hold the RSA private key needed to unlock and read the NRIC; backend services see it in readable form only briefly in memory during processing, and never store it in plain text.</p>
    </li>
    <li>
      <p><strong>OLTP vs. OLAP Separation</strong>
The system keeps “live operations” and “analytics” strictly apart. Incoming data is written immediately into a fast operational store (DynamoDB and S3) optimised for many small writes and high concurrency.
Separately, a scheduled process transforms this raw JSON into columnar Parquet files and builds a simple “lakehouse” for analysis. This means analysts can query large historical datasets cheaply and quickly (via tools like Athena), without affecting the live ingestion workload.</p>
    </li>
    <li>
      <p>Other areas which have broadly been covered in a <a href="/jekyll/update/2025/05/01/data-systems-infrastructure.html">previous post</a> include:</p>

      <ul>
        <li>Asynchronous concurrency, SQS buffering, rate-limiting</li>
        <li>Infrastructure as Code</li>
        <li>CI/CD and Containerization</li>
        <li>Observability and Diagnostics</li>
        <li>Development Environment and Tooling</li>
        <li>LLM-Assisted Development</li>
      </ul>
    </li>
  </ul>

  <h2 id="traffic-engineering-for-llm-endpoints">Traffic Engineering for LLM Endpoints</h2>

  <p>This project is about running and orchestrating a private fleet of LLMs across multiple machines. At the centre is a layer that behaves less like a “smart traffic controller” for AI workloads.</p>

  <ul>
    <li>
      <p><strong>Semantic &amp; Hardware-Aware Routing</strong>
Incoming requests are first classified by a lightweight model (<code class="language-plaintext highlighter-rouge">WorkloadClassifier</code>) that identifies what kind of job they represent—deep reasoning, code generation, quick chat, batch-style throughput, and so on.
Based on that classification, an <code class="language-plaintext highlighter-rouge">LLMRouter</code> sends the request to the most appropriate backend machine. Heavy, long-running tasks go to nodes with large GPUs and high VRAM; lighter queries go to smaller, faster nodes. This prevents a single demanding request from clogging up resources meant for fast responses, and improves overall throughput and latency.
These routing decisions assume that the backend nodes are themselves well-tuned. For example, one class of endpoints is backed by custom Vulkan builds of <code class="language-plaintext highlighter-rouge">llama.cpp</code> on Strix Halo, described in <a href="/jekyll/update/2025/08/27/building-llamacpp-strix-halo.html">this post</a>.</p>
    </li>
    <li>
      <p><strong>Streaming Proxy with Protocol Normalization</strong>
A custom FastAPI proxy acts as the single entry point for clients, speaking an OpenAI-compatible API. Behind it, different backends may have slightly different response formats, especially around “reasoning traces” or chain-of-thought output.
The proxy’s stream processor watches the Server-Sent Events (SSE) stream in real time, detecting provider-specific markers (for example, tags like <code class="language-plaintext highlighter-rouge">&lt;think&gt;</code>). It splits the stream into two parallel channels:</p>

      <ul>
        <li>one for internal reasoning traces (which can be logged, analysed, or hidden from end users),</li>
        <li>one for the final answer sent back to the client.</li>
      </ul>

      <p>At the same time, an <code class="language-plaintext highlighter-rouge">SSEAccumulator</code> rebuilds the full response on the server to support conversation history, debugging, and offline analysis.</p>
    </li>
    <li>
      <p><strong>Real-Time Observability Plane</strong>
To make sure routing decisions remain transparent and trustworthy, the system exposes an observability dashboard built with Shiny for Python.
Instead of just showing chat messages, this dashboard displays <em>why</em> a particular node was chosen: the classification result, confidence scores, and the hardware profile of the endpoint that handled the request (GPU type, VRAM, etc.).
This creates a feedback loop: one can see, in real time, that complex reasoning queries do indeed land on the most capable nodes, while light traffic is efficiently routed elsewhere.</p>
    </li>
    <li>
      <p><strong>Stateless Core with Config-Driven Topology</strong>
The central “traffic controller” is the only part of the system that remembers your past messages. It stores conversation history in a durable, shared store that it can look up when needed. The AI machines behind it don’t keep any memory at all—they simply receive the full context from the controller for each request and send back a reply. Because of this, any AI machine can be added, removed, or restarted at any time without losing conversations or moving fragile in-memory data around.</p>
    </li>
    <li>
      <p><strong>Extensibility and Clear-Rule Design</strong>
The system is built around a few very clear, shared rules for how parts talk to each other:</p>

      <ul>
        <li>a rule for how incoming requests are labelled (e.g. “needs deep reasoning” vs “simple and fast”);</li>
        <li>a rule for how each machine describes what it can handle; and</li>
        <li>a rule for how model responses are split into “thinking process” and “final answer.”</li>
      </ul>

      <p>Because these rules stay stable even as the system evolves, it is easy to plug in new kinds of work, new machines, or new dashboards without breaking what already exists. New pieces just have to follow the same rules to fit in.</p>
    </li>
  </ul>

  <h2 id="algorithmic-rigor-in-signal-processing-1">Algorithmic Rigor in Signal Processing</h2>

  <p><a href="https://github.com/zyf0717/fit-diff">fit-diff</a> is a tool built to compare and validate sensor data from different wearables—say, a chest strap vs. a smartwatch—without assuming that either one is perfect.</p>

  <p>The main difficulty is that each device records at its own sampling rate and starts at its own time. One cannot simply line up timestamps and subtract; doing so would produce misleading statistics.</p>

  <ul>
    <li>
      <p><strong>Adaptive Step-Size Search</strong>
The <code class="language-plaintext highlighter-rouge">determine_optimal_shift</code> function tackles this by treating one signal as a reference and sliding the other forward and backward in time. At each small time shift, it computes a score describing how well the two signals line up.
The step size isn’t arbitrary: it is based on the greatest common divisor (GCD) of the sampling intervals of both devices. This gives a sensible minimum shift that respects the data’s resolution while keeping the search computationally manageable.</p>
    </li>
    <li>
      <p><strong>Loss Function Optimization</strong>
To decide which time shift is “best,” the algorithm minimises or maximises a chosen metric—for example:</p>

      <ul>
        <li>minimising Mean Absolute Error or Mean Squared Error, or</li>
        <li>maximising correlation measures like Pearson or Concordance Correlation Coefficient.</li>
      </ul>

      <p>The result is a principled estimate of how much one device’s timeline should be shifted to align with another’s, enabling fair comparisons of heart rate, cadence, or other metrics.</p>
    </li>
  </ul>

  <p>Future work extends this from <em>alignment</em> to <em>validation</em>. Instead of comparing devices against a designated “gold standard,” it looks at how similar participants’ heart rate signals are under similar activities. By standardising (z-normalising) each time series and applying global cross-correlation, the system can estimate how well a given device’s data matches the overall pattern.
Devices with consistently low correlations—i.e., they don’t resemble anyone else’s physiology in comparable conditions—can be flagged as likely outliers or failing sensors, and excluded before formal analysis.</p>

  <h2 id="robust-data-acquisition-via-browser-automation-1">Robust Data Acquisition via Browser Automation</h2>

  <p>To relate physiology to environmental conditions (such as heat stress), the system needed  weather data that was only available through an old, complex web portal. There was no usable API, and traditional HTTP scraping does not work on frames, redirects, or session handling.</p>

  <p>To work around this, a dedicated Robotic Process Automation (RPA) engine was built on top of Selenium to runs in a headless environment (no visible browser window), routinely logging into the portal, selecting the right weather stations and parameters, and downloading data files as they become available. Once downloaded, these files are standardised and stored in S3 so that downstream analytics can treat them as a clean, reliable data source without worrying about how they were obtained.</p>

  <p>Key design elements include:</p>

  <ul>
    <li>
      <p><strong>Fluent Interface Pattern</strong>
The core <code class="language-plaintext highlighter-rouge">Automata</code> class wraps Selenium in a more readable, chainable API. Instead of low-level driver calls, automation scripts read almost like a set of instructions:</p>

      <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">automata</span><span class="p">.</span><span class="nb">open</span><span class="p">(</span><span class="n">url</span><span class="p">).</span><span class="n">select</span><span class="p">(...).</span><span class="n">input_text</span><span class="p">(...).</span><span class="n">click</span><span class="p">(...).</span><span class="n">wait</span><span class="p">(...).</span><span class="n">click</span><span class="p">(...)</span>
</code></pre></div>      </div>

      <p>Even multi-step flows such as login, navigation, and file downloads are encapsulated as high-level operations, reducing complexity and making the automation easier to maintain.</p>
    </li>
    <li>
      <p><strong>Idempotent Merging Logic</strong>
The ingestion pipeline is designed to tolerate retries and partial failures. When new data arrives, a merge step checks if there are existing Parquet files in S3 for the same stations and time ranges.
It then loads the existing data, combines it with the new batch, and removes duplicates based on timestamp and station ID. This means one can safely re-run the scraper for overlapping periods, restart after failures, or even rebuild the entire historical dataset from scratch—without corrupting data or double-counting records.</p>
    </li>
  </ul>

  <h2 id="data-lake-and-onboarding-1">Data Lake and Onboarding</h2>

  <p>This project is essentially a web dashboard that allows users to easily explore and analyze data stored in the cloud (AWS). Instead of requiring users to write code or look at raw, messy files, it provides a visual interface for clicking through data and running statistics.</p>

  <ul>
    <li>
      <p><strong>Stable &amp; Reliable Interface</strong>
“Glitches” where the interface attempted to display information prematurely were eliminated by pre-loading the table of contents (catalog) upon startup. The system also ensures that calculations do not execute mid-change; it waits until option selection is completely finished before updating results, effectively preventing transient errors.</p>
    </li>
    <li>
      <p><strong>Smart Processing &amp; Speed</strong>
The platform efficiently splits the workload. It leverages the massive power of the cloud to find and retrieve large chunks of data, while detailed statistical calculations are handled instantly by the application itself. Results are also “saved” for the duration of the session, preventing the waste of time and resources on re-downloading the exact same files.</p>
    </li>
    <li>
      <p><strong>Data Type Clarity</strong>
Automated systems sometimes misinterpret what data represents (for example, treating a “User ID” number as a value to be added or subtracted). Instead of relying on guesswork, the application uses a strict rulebook (<code class="language-plaintext highlighter-rouge">ui_config.py</code>) that explicitly defines each piece of data. This prevents nonsensical mathematical errors, such as attempting to calculate the “average” of a User ID.</p>
    </li>
    <li>
      <p><strong>One-Click Setup</strong>
Connecting local environments to the cloud usually involves complex, error-prone configuration. This manual process was replaced with a single automated script. This ensures that every analyst’s computer is set up identically, allowing teams to focus on actual analysis rather than troubleshooting connectivity issues.</p>
    </li>
  </ul>

</div>

<!-- Toggle Script -->
<script>
function toggleVersion(id) {
  document.getElementById('original').style.display = (id === 'original') ? 'block' : 'none';
  document.getElementById('english').style.display = (id === 'english') ? 'block' : 'none';
}
</script>]]></content><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[A 2025 year-in-review of systems engineering work across identity-linked data architecture, LLM fleet routing, signal processing, and data acquisition.]]></summary></entry><entry><title type="html">Strix Halo Matrix Cores with llama.cpp</title><link href="https://yifei.sg/jekyll/update/2025/08/27/building-llamacpp-strix-halo.html" rel="alternate" type="text/html" title="Strix Halo Matrix Cores with llama.cpp" /><published>2025-08-27T23:30:00+08:00</published><updated>2025-08-27T23:30:00+08:00</updated><id>https://yifei.sg/jekyll/update/2025/08/27/building-llamacpp-strix-halo</id><content type="html" xml:base="https://yifei.sg/jekyll/update/2025/08/27/building-llamacpp-strix-halo.html"><![CDATA[<blockquote>
  <p><strong>UPDATE (2026-07-09):</strong> Refer to my published <a href="https://github.com/zyf0717/llama-strix-halo"><code class="language-plaintext highlighter-rouge">llama-strix-halo</code></a>, a reproducible workspace for a Strix Halo setup with pinned <code class="language-plaintext highlighter-rouge">llama.cpp</code>, Vulkan/HIP build helpers, benchmark wrappers, env capture, and multi-instance <code class="language-plaintext highlighter-rouge">llama-server</code> management. The instructions below are outdated, and should only serve as a historical reference.</p>
</blockquote>

<p>Off-the-shelf GUI frontends such as LM Studio ship their own <code class="language-plaintext highlighter-rouge">llama.cpp</code> builds for convenience. These builds are designed for broad compatibility, not peak performance. Depending on how they’re compiled and how the GUI orchestrates generation, key optimizations (e.g. cooperative-matrix matmul, Flash Attention, KV cache quantization) may or may not be in play.</p>

<p>To see what Strix Halo can really do, it helps to build <code class="language-plaintext highlighter-rouge">llama.cpp</code> locally with Vulkan shaders compiled against a recent RADV + Vulkan SDK toolchain. The sections below walk through setup, build, and benchmark results.</p>

<h2 id="1-drivers-and-sdks">1. Drivers and SDKs</h2>

<p>On Linux, RADV (Mesa’s AMD Vulkan driver) exposes <code class="language-plaintext highlighter-rouge">VK_KHR_cooperative_matrix</code>. A local Vulkan build of <code class="language-plaintext highlighter-rouge">llama.cpp</code> can make direct use of this path. The build requires:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt update

<span class="c"># base toolchain</span>
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> build-essential git cmake ninja-build pkg-config libcurl4-openssl-dev

<span class="c"># Vulkan user/runtime + diagnostics</span>
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> libvulkan1 vulkan-tools mesa-vulkan-drivers

<span class="c"># For shader compiles/validation</span>
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> glslang-tools spirv-tools
</code></pre></div></div>

<p>For shader compilation:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="nt">-y</span> g++ python3 python3-pip

git clone https://github.com/google/shaderc.git
<span class="nb">cd </span>shaderc
git submodule update <span class="nt">--init</span>
python3 utils/git-sync-deps

<span class="nb">mkdir </span>build <span class="o">&amp;&amp;</span> <span class="nb">cd </span>build
cmake <span class="nt">-GNinja</span> .. <span class="nt">-DCMAKE_BUILD_TYPE</span><span class="o">=</span>Release
ninja
<span class="nb">sudo install</span> <span class="nt">-m</span> 0755 ./glslc/glslc /usr/local/bin/glslc
<span class="nb">hash</span> <span class="nt">-r</span>
glslc <span class="nt">--version</span>

<span class="o">&gt;</span> shaderc v2023.8 v2025.3-4-g402a16e
<span class="o">&gt;</span> spirv-tools v2025.3 v2022.4-874-gb8b90dba
<span class="o">&gt;</span> glslang 11.1.0-1277-g0d614c24

<span class="o">&gt;</span> Target: SPIR-V 1.0
</code></pre></div></div>

<h2 id="2-building-llamacpp">2. Building llama.cpp</h2>

<p>Enable Vulkan and allow shader generation:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/ggerganov/llama.cpp.git
<span class="nb">cd </span>llama.cpp

cmake <span class="nt">-S</span> <span class="nb">.</span> <span class="nt">-B</span> build <span class="nt">-G</span> Ninja <span class="se">\</span>
  <span class="nt">-DGGML_VULKAN</span><span class="o">=</span>ON <span class="se">\</span>
  <span class="nt">-DLLAMA_BUILD_SERVER</span><span class="o">=</span>ON <span class="se">\</span>
  <span class="nt">-DLLAMA_BUILD_EXAMPLES</span><span class="o">=</span>ON <span class="se">\</span>
  <span class="nt">-DCMAKE_BUILD_TYPE</span><span class="o">=</span>Release
ninja <span class="nt">-C</span> build

<span class="o">&gt;</span> <span class="o">[</span>43/261] Performing build step <span class="k">for</span> <span class="s1">'vulkan-shaders-gen'</span>
<span class="o">&gt;</span> <span class="o">[</span>1/2] Building CXX object CMakeFiles/vulkan-shaders-gen.dir/vulkan-shaders-gen.cpp.o
<span class="o">&gt;</span> <span class="o">[</span>2/2] Linking CXX executable vulkan-shaders-gen
<span class="o">&gt;</span> <span class="o">[</span>44/261] Performing <span class="nb">install </span>step <span class="k">for</span> <span class="s1">'vulkan-shaders-gen'</span> <span class="nt">--</span> Installing: ...
<span class="o">&gt;</span> <span class="o">[</span>46/261] Generate vulkan shaders ggml_vulkan: Generating and compiling shaders to SPIR-V
<span class="o">&gt;</span> <span class="o">[</span>261/261] Linking CXX executable bin/llama-server
</code></pre></div></div>

<p>At build time, the shaders include cooperative-matrix variants if the driver supports it.</p>

<h2 id="3-verify-with-llama-bench">3. Verify with llama-bench</h2>

<p>GPU support for cooperative-matrix matmul is confirmed when benchmark logs show <code class="language-plaintext highlighter-rouge">matrix cores: HR_coopmat</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">GGML_LOG_LEVEL</span><span class="o">=</span>2
<span class="nb">export </span><span class="nv">GGML_VK_VISIBLE_DEVICES</span><span class="o">=</span>0  <span class="c"># might need if multiple GPUs present</span>
<span class="nb">export </span><span class="nv">AMD_VULKAN_ICD</span><span class="o">=</span>RADV

<span class="nv">MODEL_DIR</span><span class="o">=</span><span class="s2">"/path/to/model/dir"</span>  <span class="c"># insert accordingly</span>
<span class="nv">MODEL</span><span class="o">=</span><span class="s2">"</span><span class="nv">$MODEL_DIR</span><span class="s2">/gpt-oss-120b-MXFP4-00001-of-00002.gguf"</span>

./build/bin/llama-bench <span class="nt">-m</span> <span class="s2">"</span><span class="nv">$MODEL</span><span class="s2">"</span> <span class="nt">-ngl</span> 999 <span class="nt">-t</span> 8 <span class="nt">-pg</span> 256,128 <span class="nt">-b</span> 512 <span class="nt">-ub</span> 256 <span class="nt">-fa</span> 1

<span class="o">&gt;</span> ggml_vulkan: Found 1 Vulkan devices:
<span class="o">&gt;</span> ggml_vulkan: 0 <span class="o">=</span> AMD Radeon Graphics <span class="o">(</span>RADV GFX1151<span class="o">)</span> <span class="o">(</span>radv<span class="o">)</span> | uma: 1 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 1 | matrix cores: KHR_coopmat
<span class="o">&gt;</span> | model                          |...| n_batch | n_ubatch | fa |            <span class="nb">test</span> |                  t/s |
<span class="o">&gt;</span> | <span class="nt">------------------------------</span> |...| <span class="nt">------</span>: | <span class="nt">-------</span>: | -: | <span class="nt">--------------</span>: | <span class="nt">-------------------</span>: |
<span class="o">&gt;</span> | gpt-oss 120B MXFP4 MoE         |...|     512 |      256 |  1 |           pp512 |       339.22 ± 14.16 |
<span class="o">&gt;</span> | gpt-oss 120B MXFP4 MoE         |...|     512 |      256 |  1 |           tg128 |         48.88 ± 0.05 |
<span class="o">&gt;</span> | gpt-oss 120B MXFP4 MoE         |...|     512 |      256 |  1 |     pp256+tg128 |        111.92 ± 0.24 |
</code></pre></div></div>

<p>And for a longer-context benchmark:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./build/bin/llama-bench <span class="nt">-m</span> <span class="s2">"</span><span class="nv">$MODEL</span><span class="s2">"</span> <span class="nt">-ngl</span> 999 <span class="nt">-t</span> 8 <span class="nt">-pg</span> 2048,128 <span class="nt">-b</span> 4096 <span class="nt">-ub</span> 256 <span class="nt">-fa</span> 1

<span class="o">&gt;</span> ggml_vulkan: Found 1 Vulkan devices:
<span class="o">&gt;</span> ggml_vulkan: 0 <span class="o">=</span> AMD Radeon Graphics <span class="o">(</span>RADV GFX1151<span class="o">)</span> <span class="o">(</span>radv<span class="o">)</span> | uma: 1 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 1 | matrix cores: KHR_coopmat
<span class="o">&gt;</span> | model                          |...| n_batch | n_ubatch | fa |            <span class="nb">test</span> |                  t/s |
<span class="o">&gt;</span> | <span class="nt">------------------------------</span> |...| <span class="nt">------</span>: | <span class="nt">-------</span>: | -: | <span class="nt">--------------</span>: | <span class="nt">-------------------</span>: |
<span class="o">&gt;</span> | gpt-oss 120B MXFP4 MoE         |...|    4096 |      256 |  1 |           pp512 |       339.07 ± 15.09 |
<span class="o">&gt;</span> | gpt-oss 120B MXFP4 MoE         |...|    4096 |      256 |  1 |           tg128 |         48.95 ± 0.02 |
<span class="o">&gt;</span> | gpt-oss 120B MXFP4 MoE         |...|    4096 |      256 |  1 |    pp2048+tg128 |        242.73 ± 0.67 |
</code></pre></div></div>

<p>The above results hold up reasonably well in deployment, e.g. with <code class="language-plaintext highlighter-rouge">-c 32768 -b 4096 -ub 256 --flash-attn</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>prompt_n: 1996
prompt_ms: 7023.54
prompt_per_token_ms: 3.52
prompt_per_second: 284.19
predicted_n: 3514
predicted_ms: 77176.74
predicted_per_token_ms: 21.96
predicted_per_second: 45.53
</code></pre></div></div>

<p>For reference, deployments with LM Studio (CLI v0.0.46) and its shipped <code class="language-plaintext highlighter-rouge">llama.cpp-linux-x86_64-vulkan-avx2-1.46.0</code> runtime never yielded more than ~30 t/s, even with trivially short contexts.</p>

<h2 id="4-conclusion">4. Conclusion</h2>

<p>On Strix Halo, local Vulkan builds of <code class="language-plaintext highlighter-rouge">llama.cpp</code> consistently sustain ~45–50 tok/s on 120B-class MoE models, while LM Studio’s packaged runtimes are capped at ~30 tok/s.</p>

<p>A combination of several factors potentially contribute to this gap, including:</p>

<ul>
  <li><strong>Shader paths</strong>. A custom build guarantees cooperative-matrix kernels are present and can be selected for dense workloads. Packaged binaries may omit them or fall back to generic matmuls, especially on less common quantization formats.</li>
  <li><strong>Runtime orchestration</strong>. LM Studio introduces additional IPC and JSON streaming layers. This simplifies GUI and API integration but adds measurable per-token latency.</li>
  <li><strong>Defaults and flags</strong>. Local builds can be tuned with larger batch/ubatch sizes, Flash Attention toggles, and architecture-specific flags. GUI defaults are more conservative.</li>
</ul>

<p>Regardless, the takeaway is clear:</p>

<ul>
  <li>For maximum performance and control, local compilation remains mandatory.</li>
  <li>For convenience, LM Studio’s bundled builds are sufficient but capped.</li>
</ul>]]></content><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[How to build and benchmark llama.cpp with Vulkan on AMD Strix Halo, including driver setup, shader compilation, and performance validation.]]></summary></entry><entry><title type="html">Year-to-Date: Data, Systems, and Infrastructure</title><link href="https://yifei.sg/jekyll/update/2025/05/01/data-systems-infrastructure.html" rel="alternate" type="text/html" title="Year-to-Date: Data, Systems, and Infrastructure" /><published>2025-05-01T21:30:00+08:00</published><updated>2025-05-01T21:30:00+08:00</updated><id>https://yifei.sg/jekyll/update/2025/05/01/data-systems-infrastructure</id><content type="html" xml:base="https://yifei.sg/jekyll/update/2025/05/01/data-systems-infrastructure.html"><![CDATA[<!-- Toggle Buttons -->
<div>
    <strong>View Mode Toggles:</strong>
  <button onclick="toggleVersion('original')">Original</button>
  <button onclick="toggleVersion('english')">Plain English</button>
</div>
<p><br /></p>

<!-- DENSE VERSION -->
<div id="original" style="display: block;">

  <p>The year 2025 marked a foundational shift—not merely in tooling, but in how systems are architected, governed, and evolved.</p>

  <h2 id="data-engineering">Data Engineering</h2>

  <p>The focus transitioned from constructing pipelines to engineering resilient, observable data flows through properly-structured orchestration. The core ingestion patterns include asynchronous concurrency and Pandas-based dataframes for transformation. Data is preprocessed near source, upserted into DynamoDB to ensure each record reflects the most recent valid state, and versioned to S3 for auditability, recovery, and downstream analysis.</p>

  <p>Jobs are triggered through a mix of event-driven and scheduled mechanisms. Buffers like SQS decouple execution, allowing workloads to scale flexibly, remaining resilient under variable load.</p>

  <p>Error handling is proactive: early-stage validation, task-scoped failure boundaries, and immediate termination on rate-limiting (HTTP 429) ensure fault isolation and data integrity.</p>

  <p>Authentication-bound flows, such as SingPass integration, are implemented through stateless AWS Lambda chains—including PKCE flows, secure token exchanges, and claim decryption—with no persistent context between invocations.</p>

  <h2 id="infrastructure-as-code-iac">Infrastructure as Code (IaC)</h2>

  <p>Infrastructure is deliberately and declaratively provisioned using Terraform. Workspaces (<code class="language-plaintext highlighter-rouge">dev</code>, <code class="language-plaintext highlighter-rouge">stg</code>, <code class="language-plaintext highlighter-rouge">prd</code>) isolate environments, with deployments automated via GitHub Actions. Modules are parameterized to maximize reuse, and remote state with locking is managed through S3.</p>

  <p>Security follows the principle of least privilege: IAM roles are scoped tightly, environment-specific credentials are stored in AWS SSM Parameter Store, and ECR repository was bootstrapped for container promotion across environments.</p>

  <p>Static front-end applications (e.g., S3 + CloudFront SPAs) are also codified in Terraform, enabling full-stack reproducibility.</p>

  <h2 id="cicd-and-containerization">CI/CD and Containerization</h2>

  <p>CI/CD pipelines are managed through GitHub Actions. Docker images are built and tagged using Git commit SHAs and environment labels, then pushed to ECR. Lambda artifacts are packaged, versioned, and uploaded to S3 buckets. Promotion across environments is handled by reference—passing ECR image tags or copying S3-versioned Lambda artifacts without rebuilding. Subsequent Terraform <code class="language-plaintext highlighter-rouge">plan</code> and <code class="language-plaintext highlighter-rouge">apply</code> steps are gated behind environment-specific approvals.</p>

  <p>Unit tests are implemented with pytest, and executed during the CI process prior to any build or deployment steps. Test coverage includes core data logic and Lambda handlers, with failure conditions blocking downstream stages.</p>

  <h2 id="observability-and-diagnostics">Observability and Diagnostics</h2>

  <p>Logging is structured and traceable, and includes correlation IDs, standardized timestamps, and context tags. CloudWatch is the central log sink, with log group lifecycle policies configured to balance traceability and storage cost.</p>

  <p>Planned enhancements include lightweight telemetry on Lambda functions (e.g., execution duration, memory usage, throttle counts), and diagnostics to pinpoint ingest failures, malformed inputs, or data anomalies.</p>

  <h2 id="development-environment-and-tooling">Development Environment and Tooling</h2>

  <p>Local and cloud development environments are standardized for consistency and rapid context switching. Secure Cloudflare Tunnels enable remote access to headless environments, while IDE support is delivered through code-server, VS Code remote tunnels, and SSH.</p>

  <h2 id="llm-assisted-development">LLM-Assisted Development</h2>

  <p>LLMs are used primarily as structured audit tools, not generative agents. Their role is to validate assumptions, surface inconsistencies across configuration, infrastructure, and code, and reinforce architectural coherence. Design suggestions are secondary—only introduced after core logic and structural contracts are clearly established. Generation is used mainly for boilerplate or tightly scoped tasks, and is otherwise applied sparingly—and never ahead of deliberate intent.</p>

  <h2 id="looking-ahead">Looking Ahead</h2>

  <p>The emphasis remains on architectural clarity: separating orchestration from business logic, behavior from configuration, and system design from runtime execution. The direction is not toward scaling complexity, but toward containing it through clean interfaces, observable systems, and composition by intent.</p>

</div>

<!-- ENGLISH VERSION -->
<div id="english" style="display: none;">

  <p>The year 2025 marked a major shift—not just in tools, but in how systems are planned, built, and improved. The emphasis moved toward creating systems that are dependable, understandable, and built for long-term maintainability.</p>

  <h2 id="data-engineering-1">Data Engineering</h2>

  <p>Rather than building pipelines as a goal in itself, attention was directed toward shaping how data flows through systems. Key characteristics of the new pattern include:</p>

  <ul>
    <li>Data is processed as it arrives, using asynchronous execution.</li>
    <li>Raw data is processed and deduplicated early through Python Pandas.</li>
    <li>Records are further streamlined by leveraging DynamoDB’s conditional insert or update.</li>
    <li>Backups are written to S3 with version control, supporting traceability and recovery.</li>
  </ul>

  <p>Tasks are triggered either by events or on a fixed schedule, with message queues like SQS used as buffers. This setup helps the system handle more tasks in parallel and stay stable even when demand increases suddenly.</p>

  <p>Failures are isolated to specific tasks. This prevents errors from spreading across the system.</p>

  <p>Authentication-related workflows (such as SingPass integration) are handled using stateless Lambda sequences. This includes secure handling of login protocols (PKCE), access tokens, and encrypted identity claims—all without compromising sensitivity between steps.</p>

  <h2 id="infrastructure-as-code-iac-1">Infrastructure as Code (IaC)</h2>

  <p>All infrastructure was defined using Terraform. Distinct environments—development, staging, and production—are deployed using GitHub Actions.</p>

  <ul>
    <li>Remote state is maintained per environment.</li>
    <li>Secrets are stored and accessed securely via SSM Parameter Store.</li>
    <li>IAM roles follow least-privilege principles.</li>
    <li>Modules are parameterized to eliminate duplication.</li>
  </ul>

  <p>Front-end deployments (e.g., SPAs on S3 served through CloudFront) were brought under the same infrastructure-as-code model to support full lifecycle automation.</p>

  <h2 id="cicd-and-containerization-1">CI/CD and Containerization</h2>

  <p>Code deployments were moved from manual processes to automated GitHub Actions workflows.</p>

  <ul>
    <li>Docker images are built and tagged with Git SHAs on relevant commits.</li>
    <li>Lambda artifacts are packaged, versioned, and uploaded to S3 buckets</li>
    <li>Infrastructure changes are gated behind review and approval workflows using Terraform’s planning and application steps.</li>
    <li>Tests are run automatically—failures stop the process early, ensuring only working code reaches production.</li>
  </ul>

  <p>This transition ensures deployments reflect deliberate architectural intent rather than accumulated iteration.</p>

  <h2 id="observability-and-diagnostics-1">Observability and Diagnostics</h2>

  <p>Structured logging formats were introduced, featuring consistent timestamps, request identifiers, and event metadata. These enable trace-level inspection across asynchronous workloads.</p>

  <p>Log verbosity is controlled per environment. Logs are routed through CloudWatch with lifecycle policies configured to control retention and cost.</p>

  <p>Planned additions include:</p>

  <ul>
    <li>Collecting telemetry data on function runtime behavior (e.g., execution time, memory use, throttling).</li>
    <li>Building diagnostic tools to assist with failure triage and data anomaly detection.</li>
  </ul>

  <h2 id="development-environment-and-tooling-1">Development Environment and Tooling</h2>

  <p>Development workflows were aligned across local and remote contexts, enabled through Cloudflare Tunnel, Code-server, and VS Code via SSH or remote tunnel. This alignment reduces context-switching overhead and promotes operational consistency.</p>

  <h2 id="llm-assisted-development-1">LLM-Assisted Development</h2>

  <p>Language models (LLMs) are used selectively—their role is mainly post-design validation, not simply code generation.</p>

  <ul>
    <li>Edge cases and overlooked conditions are more easily identified.</li>
    <li>Infrastructure, configuration, and implementation are cross-checked for consistency.</li>
    <li>Code generation is limited to simple, clearly defined tasks.</li>
    <li>Further suggestions are offered only after internal structures are defined.</li>
  </ul>

  <p>They function more as code reviewers than authors—by helping to confirm what’s built, not invent from scratch.</p>

  <h2 id="looking-ahead-1">Looking Ahead</h2>

  <p>Emphasis remains on architectural separation:</p>

  <ul>
    <li>Orchestration and logic are kept distinct.</li>
    <li>Implementation reflects clarified intent.</li>
    <li>Systems are made increasingly observable and failure-tolerant.</li>
  </ul>

  <p>Complexity is not avoided, but contained—through deliberate structure, bounded interfaces, and context-aware execution.</p>

</div>

<!-- Toggle Script -->
<script>
function toggleVersion(id) {
  document.getElementById('original').style.display = (id === 'original') ? 'block' : 'none';
  document.getElementById('english').style.display = (id === 'english') ? 'block' : 'none';
}
</script>]]></content><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[A 2025 systems update covering resilient data pipelines, Terraform infrastructure, CI/CD, observability, development tooling, and LLM-assisted audits.]]></summary></entry><entry><title type="html">Shiny Dashboard Development</title><link href="https://yifei.sg/jekyll/update/2024/09/07/shiny-dashboard-development.html" rel="alternate" type="text/html" title="Shiny Dashboard Development" /><published>2024-09-07T10:15:00+08:00</published><updated>2024-09-07T10:15:00+08:00</updated><id>https://yifei.sg/jekyll/update/2024/09/07/shiny-dashboard-development</id><content type="html" xml:base="https://yifei.sg/jekyll/update/2024/09/07/shiny-dashboard-development.html"><![CDATA[<p>Shiny for Python is now available, and so I am taking this opportunity to rewrite my <a href="/jekyll/update/2024/05/07/dashboard-deployment-aws.html">dashboard</a>.</p>

<p>Updates so far: group-chats are supported, WhatsApp-support for a greater variety of Android devices have been implemented, and UI changes are being made to make the front-end more user-friendly.</p>

<p><img src="/assets/images/shiny-chats-dashboard.png" alt="Shiny Dashboard" /></p>

<p>A future blogpost will further detail this process as there are still ongoing changes.</p>

<p>The updated and functional dashboard is currently accessible at <a href="https://shiny-dashboard.yifei.sg">https://shiny-dashboard.yifei.sg</a>.</p>]]></content><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[Shiny for Python is now available, and so I am taking this opportunity to rewrite my dashboard.]]></summary></entry><entry><title type="html">Dashboard Deployment on AWS</title><link href="https://yifei.sg/jekyll/update/2024/05/07/dashboard-deployment-aws.html" rel="alternate" type="text/html" title="Dashboard Deployment on AWS" /><published>2024-05-07T12:15:00+08:00</published><updated>2024-05-07T12:15:00+08:00</updated><id>https://yifei.sg/jekyll/update/2024/05/07/dashboard-deployment-aws</id><content type="html" xml:base="https://yifei.sg/jekyll/update/2024/05/07/dashboard-deployment-aws.html"><![CDATA[<h2 id="update">Update</h2>

<p>Following my <a href="/jekyll/update/2020/08/18/dashboard-deployment.html">previous post</a>, the current dashboard has been updated to support Telegram (JSON exports via desktop app) in addition to WhatsApp (text file exports via phone, so only confirmed to work for certain devices and/or settings).</p>

<h2 id="migration-and-redeployment">Migration and redeployment</h2>

<p>After having familiarised myself with the AWS stack over the past few years, I chose to use the free-tier EC2 on AWS for this current redeployment.</p>

<p><em>Update: The deployed dashboard has since been taken down to make way for an updated version. Details can be found <a href="/jekyll/update/2024/09/07/shiny-dashboard-development.html">here</a>.</em></p>]]></content><author><name>Yifei Zheng</name><email>zyf0717@gmail.com</email></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[Update]]></summary></entry></feed>