<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Vardhman Gupta]]></title><description><![CDATA[Technical case studies, open-source engineering, and systems architecture by Vardhman Gupta. Deep dives into AI security, performance optimization, and distributed ML workflows.]]></description><link>https://kap10.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6aed55f785cd1b8ef5a0f98d/1e95654d-d0f7-4997-82ba-c84be817c7e2.jpg</url><title>Vardhman Gupta</title><link>https://kap10.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 09:18:43 GMT</lastBuildDate><atom:link href="https://kap10.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How I Built Universal AI Agent Harnesses for Apache Magpie]]></title><description><![CDATA[How I built vendor-neutral AI agent runtime adapters for Cursor, Aider, Copilot CLI, Goose, and local LLMs in Apache Magpie.]]></description><link>https://kap10.hashnode.dev/ai-agent-harnesses-for-apache-magpie</link><guid isPermaLink="true">https://kap10.hashnode.dev/ai-agent-harnesses-for-apache-magpie</guid><category><![CDATA[AI]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[cybersecurity]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[Developer Tools]]></category><dc:creator><![CDATA[Vardhman Gupta]]></dc:creator><pubDate>Sun, 08 Nov 2026 03:14:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aed55f785cd1b8ef5a0f98d/2711f172-67ee-450f-b9f6-39f701421b24.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When software teams adopt AI coding assistants, they quickly run into a major roadblock: every developer uses a different tool.</p>
<p>Some engineers work inside Cursor. Others live in the terminal using Aider or GitHub Copilot CLI. In high-security environments, developers are not even allowed to use cloud-based models; they run local models via Ollama or vLLM on private workstations.</p>
<p>For open source ecosystems like the Apache Software Foundation, this fragmentation creates a serious dilemma. If you build AI workflows for only one specific agent harness, you alienate contributors and lock your project into a proprietary vendor. If you rewrite every workflow for every tool, you end up with duplicated, brittle code that becomes impossible to maintain.</p>
<p>In the <a href="https://github.com/apache/magpie">Apache Magpie</a> project, an open source framework designed for security automation across Apache projects, I tackled this problem by designing and landing swappable runtime adapters for five major AI agent harnesses.</p>
<p>Here is how the system was built, the technical hurdles solved, and the measurable impact on open source security operations.</p>
<hr />
<h2>[1] The Fragmentation Problem in Open Source Security</h2>
<p>The Apache Software Foundation oversees hundreds of critical open source projects, including Airflow, Kafka, Spark, and Tomcat. Companies around the world depend on these projects daily.</p>
<p>When a critical security vulnerability is reported in an Apache project, maintainers cannot handle it like a standard bug:</p>
<ol>
<li>Strict confidentiality: Vulnerability discussions cannot happen in public GitHub issues before a patch is ready. If exploit details leak prematurely, attackers can target production systems before a fix is released.</li>
<li>Complex release governance: Handling a vulnerability requires requesting CVE numbers, preparing private patches, coordinating voting across project management committees, and drafting public security advisories.</li>
<li>High manual effort: Managing a single security advisory can take up to twenty manual review cycles.</li>
</ol>
<p>To assist maintainers, Apache Magpie provides automated agentic workflows called skills. However, maintainers have different setups. Some wanted to run triage from Cursor. Others wanted terminal pair programming in Aider or Copilot. Security teams handling highly sensitive embargoed bugs required completely air-gapped local machines without any network traffic leaving the building.</p>
<p>The project faced an immediate challenge: how do we empower maintainers to use their preferred AI agent harnesses while guaranteeing absolute data confidentiality and zero vendor lock-in?</p>
<hr />
<h2>[2] The Architectural Challenge</h2>
<p>The objective was straightforward but technically demanding:</p>
<ul>
<li>Maintain one canonical source of truth: Write every security skill once in clean, vendor-neutral markdown, without mentioning proprietary vendor names or hardcoding API calls.</li>
<li>Support five diverse agent harnesses: Connect IDE assistants, terminal CLI tools, declarative recipe runners, and offline local inference engines.</li>
<li>Guarantee strict human oversight: Ensure that no AI model can autonomously push code to protected branches, merge pull requests, or send outbound messages without explicit human approval.</li>
<li>Enforce air-gapped data boundaries: Guarantee that confidential vulnerability data and private repository tokens never leak through cloud telemetry.</li>
</ul>
<p>To solve this, we decoupled the workflow definition from the execution engine. Skills define what needs to be done. Capabilities define abstract operations like sending mail or querying advisories. Runtime adapters bind those capabilities to concrete developer harnesses.</p>
<p>My objective was to engineer the runtime adapters and operational guides for five distinct AI agent harnesses to achieve 100% harness neutrality.</p>
<hr />
<h2>[3] Engineering the Five Agent Harnesses</h2>
<p>Each agent harness has its own execution model, configuration layout, and permission mechanics. I implemented custom runtime adapters tailored to each environment.</p>
<h3>1. GitHub Copilot CLI and Coding Agent (<a href="https://github.com/apache/magpie/pull/1287">PR #1287</a>)</h3>
<p>GitHub Copilot serves developers in two distinct ways: interactive command line sessions in the terminal and server-side cloud coding agents running on GitHub infrastructure.</p>
<p>I addressed two critical requirements for this harness:</p>
<ul>
<li>Establishing a strict Privacy boundary: Server-side cloud agents were explicitly forbidden from operating on private security trackers and embargoed vulnerability discussions. This ensures private exploit code is never sent to external cloud model servers.</li>
<li>Enforcing human confirmation: In the terminal, all shell commands require interactive operator approval before execution. On GitHub servers, the agent is restricted strictly to creating draft pull requests; it has zero permission to push to protected branches or merge changes autonomously.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6aed55f785cd1b8ef5a0f98d/624341dc-1d0d-4b77-b673-bf9b08fe43ba.png" alt="" style="display:block;margin:0 auto" />

<h3>2. Aider Terminal Pair Programming (<a href="https://github.com/apache/magpie/pull/1286">PR #1286</a>)</h3>
<p>Aider is a fast, terminal-based pair programming harness that interacts directly with local git working trees.</p>
<p>To make Aider work reliably with dozens of complex workflows, I solved two engineering problems:</p>
<ul>
<li>Preventing context window flooding: Magpie contains over seventy specialized skills. Loading all skill files at startup exhausts model context windows and degrades reasoning quality. I implemented a progressive loading pattern where Aider ingests only the specific skill markdown file needed for the immediate task.</li>
<li>Enforcing human-reviewed commits: By default, Aider can automatically commit file modifications to git. In security engineering, unreviewed autonomous commits are unacceptable. I configured strict rules disabling automatic and dirty commits, guaranteeing that maintainers inspect every diff before a commit is created.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6aed55f785cd1b8ef5a0f98d/db7e873c-3741-4aee-9978-8be45b300385.png" alt="" style="display:block;margin:0 auto" />

<h3>3. Block Goose and Model Context Protocol (<a href="https://github.com/apache/magpie/pull/1217">PR #1217</a>)</h3>
<p>Goose, developed by Block, is an extensible open source CLI agent harness that automates complex engineering tasks through recipes and extensions.</p>
<p>For the Goose harness, I focused on deep protocol integration:</p>
<ul>
<li>Native Model Context Protocol (MCP) integration: I configured native stdio MCP bridges connecting Goose directly to custom Magpie servers for mail triage, project metadata, and security tooling.</li>
<li>Declarative recipe workflows: I created reusable YAML recipe templates that allow maintainers to launch multi-step security triage routines with a single terminal command.</li>
<li>PreToolUse guardrails: I leveraged Goose extension hooks to intercept tool execution, giving maintainers the ability to inspect and approve operations before the agent modifies files.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6aed55f785cd1b8ef5a0f98d/f9b69b07-8e5b-42c9-a66c-a119ec6befa6.png" alt="" style="display:block;margin:0 auto" />

<h3>4. Air-Gapped Local LLMs via Ollama, llama.cpp, and vLLM (<a href="https://github.com/apache/magpie/pull/1206">PR #1206</a>)</h3>
<p>For security teams handling top-secret vulnerability disclosures, commercial cloud APIs are out of the question.</p>
<p>In <a href="https://github.com/apache/magpie/pull/1206">PR #1206</a>, I documented and calibrated the harness for 100% offline, sovereign operations:</p>
<ul>
<li>Universal local serving: Documented complete integration with Ollama, llama.cpp server, and vLLM exposing local OpenAI-compatible endpoints.</li>
<li>Calibrating empirical model capability floors: Through rigorous evaluation testing, I established clear guidance on model sizes:<ul>
<li>70B+ parameter reasoning models are required for multi-step security triage, where the agent must evaluate exploit mechanics and follow multi-step governance rules.</li>
<li>8B to 14B parameter models are calibrated for bounded, single-file audits, checklist verifications, and formatting tasks.</li>
</ul>
</li>
<li>Air-gapped workstations: Enabled maintainers to run fully capable AI assistants on isolated machines with zero internet connectivity and zero cloud telemetry.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6aed55f785cd1b8ef5a0f98d/c71dcd1e-a5f0-4478-ba78-4d0c6a5d00db.png" alt="" style="display:block;margin:0 auto" />

<h3>5. Cursor Composer and Agent CLI (<a href="https://github.com/apache/magpie/pull/1204">PR #1204</a>)</h3>
<p>Cursor is one of the most widely used AI development harnesses, featuring both an in-editor Composer and a standalone terminal agent.</p>
<p>To integrate Cursor into our universal model:</p>
<ul>
<li>Canonical skill discovery: Mapped canonical skill directory paths so Cursor automatically discovers and recommends workflows without requiring duplicated configuration files.</li>
<li>Modal approval alignment: Mapped Cursor's per-action approval modals to our proposal-then-confirm standard, ensuring the agent never applies destructive changes without consent.</li>
<li>Sandboxed CLI execution: Wrapped CLI invocations in clean-environment isolation containers to prevent unintended access to local developer tokens.</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6aed55f785cd1b8ef5a0f98d/98de1a6c-51de-4338-bec9-f4b743d90070.png" alt="" style="display:block;margin:0 auto" />

<hr />
<h2>[4] Production Impact and Measurable Outcomes</h2>
<p>Landing these five agent harnesses delivered immediate, concrete benefits to the Apache Magpie framework and the wider open source community:</p>
<ul>
<li>100% Harness Neutrality Score: All five harnesses passed our automated vendor neutrality test suites with a perfect 10/10 score across capability contracts and 24/24 substrate tools.</li>
<li>Multi-Harness Support: Maintainers across diverse Apache projects can now execute identical security workflows from Cursor, Aider, Copilot, Goose, or fully offline local LLMs.</li>
<li>Zero Leaked Credentials: By wrapping runtimes with ambient credential stripping, local API keys and cloud tokens remain completely isolated from model sessions.</li>
<li>Proven Air-Gapped Feasibility: Established empirical benchmarks proving that open-weight 70B models can handle complex, multi-step vulnerability triage without sending a single byte to cloud servers.</li>
<li>Complete Maintainer Governance: Strict human-in-the-loop policies guarantee that every git mutation, draft advisory, and outbound communication remains under human authority.</li>
</ul>
<hr />
<h2>[5] Key Takeaways for Engineering Teams</h2>
<p>Building adapters across five distinct agent harnesses highlighted three vital lessons for the future of software engineering:</p>
<ol>
<li>Decouple workflows from harnesses: AI developer tools change constantly. If your automation relies on proprietary features from a single harness, your codebase will age poorly. Writing workflows in clean, tool-agnostic markdown protects your engineering investment.</li>
<li>Safety is more important than autonomy: Fully autonomous agents that write code, commit to git, and deploy without confirmation are a liability in production. Real-world enterprise systems require proposal-then-confirm patterns where the agent drafts and the human decides.</li>
<li>Sovereign AI is ready for production: You no longer need to compromise between powerful AI capabilities and strict confidentiality. With properly calibrated open-weight models, teams can run enterprise-grade security workflows on entirely private infrastructure.</li>
</ol>
<p>Open source software succeeds when developers have freedom of choice. By building universal agent harnesses for Apache Magpie, we showed that AI automation can be flexible, secure, and truly vendor-neutral.</p>
<hr />
<p>Huge thanks to <a href="https://github.com/potiuk"><strong>Jarek Potiuk</strong></a> for the rigorous architectural reviews, thoughtful feedback, and continuous push for production-grade engineering quality across these PRs. He is the final boss of code reviews in <strong>Apache Magpie</strong> :)</p>
<hr />
<h3>Thanks for reading, Connect with me :)</h3>
<ul>
<li><a href="https://www.linkedin.com/in/vardhman-gupta/">LinkedIn</a> and <a href="http://github.com/Kaap10">GitHub</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Refactoring GCG Adversarial Attacks in Microsoft PyRIT: Architecture, VRAM Optimization and Clean Systems Design]]></title><description><![CDATA[How I decoupled Microsoft PyRIT's GCG attack pipeline into a modular 3-tier architecture, optimizing GPU VRAM and preventing multi-model OOM crashes.]]></description><link>https://kap10.hashnode.dev/refactoring-gcg-adversarial-attacks-in-microsoft-pyrit</link><guid isPermaLink="true">https://kap10.hashnode.dev/refactoring-gcg-adversarial-attacks-in-microsoft-pyrit</guid><category><![CDATA[Python]]></category><category><![CDATA[AI]]></category><category><![CDATA[Security]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[guardrails]]></category><category><![CDATA[Machine Learning]]></category><dc:creator><![CDATA[Vardhman Gupta]]></dc:creator><pubDate>Fri, 06 Nov 2026 07:12:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6aed55f785cd1b8ef5a0f98d/2b1b9fcd-8d90-41ad-bd98-6386d3251d96.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<details data-node-type="hn-details-summary">
<summary>TL;DR</summary>
<p>Refactored Microsoft PyRIT’s monolithic 1,000-line Greedy Coordinate Gradient (GCG) adversarial optimization loop into 3 typed, modular components (<code>CandidateProposer</code>, <code>CandidateEvaluator</code>, and <code>ProgressiveScheduleController</code>).</p><p>Bounded GPU VRAM consumption to $O(1)$ by introducing sequential candidate evaluation with immediate tensor cleanup <code>del logits, ids</code>), completely eliminating multi-model Out-Of-Memory (OOM) crashes.</p><p>Maintained 100% backward compatibility with zero breaking changes, supported by 300+ deterministic automated tests running across Windows, macOS, and Linux runners without requiring active GPUs.</p>
</details>

<blockquote>
<ul>
<li>Tracking Issue: Closes <a href="https://github.com/microsoft/PyRIT/issues/2665">#2665</a></li>
<li>Merged PRs: <a href="https://github.com/microsoft/PyRIT/pull/2671">#2671</a>, <a href="https://github.com/microsoft/PyRIT/pull/2700">#2700</a>, <a href="https://github.com/microsoft/PyRIT/pull/2720">#2720</a></li>
</ul>
</blockquote>
<h2>[1] <u>Introduction</u></h2>
<p>Large language models (LLMs) are deployed with safety guardrails to prevent harmful, dangerous, or unauthorized outputs. In AI security and red teaming, testing whether these guardrails can be bypassed is an essential part of defensive engineering.</p>
<p>One of the most effective white-box algorithms for this is <strong>Greedy Coordinate Gradient (GCG)</strong>. It searches for specific suffix tokens that, when appended to a prompt, guide the neural network into bypassing its safety constraints.</p>
<p>While GCG is mathematically powerful, running it reliably at scale is a difficult engineering challenge. In production red teaming frameworks, GCG must handle multiple models, diverse tokenizers, and heavy GPU tensor computations.</p>
<p>This article walks through how I refactored the GCG optimization engine inside <a href="https://github.com/microsoft/PyRIT">Microsoft PyRIT (Python Risk Identification Tool for Generative AI)</a> across three merged pull requests under Issue #2665, resolving critical VRAM spikes and transforming a monolithic loop into a maintainable, typed architecture.</p>
<hr />
<h2>[2] <u>Background: How GCG Works</u></h2>
<p>To understand the engineering constraints, it helps to understand how GCG operates under the hood:</p>
<ol>
<li><strong>Target Loss Formulation:</strong> The attacker specifies a target phrase (for example: <em>"Sure, here is how to..."</em>). The goal is to minimize the model loss on this target string.</li>
<li><strong>Gradient Computation:</strong> The algorithm computes the gradient of the loss with respect to the one-hot token embeddings of the attack suffix.</li>
<li><strong>Candidate Proposal:</strong> Using the gradient values, the algorithm identifies the top candidate tokens most likely to decrease the loss at each token position.</li>
<li><strong>Candidate Evaluation:</strong> The algorithm evaluates a large batch of these candidate sequences across the target models using forward passes.</li>
<li><strong>Selection:</strong> The candidate producing the lowest loss is selected, and the loop repeats until the model produces the target output.</li>
</ol>
<p>In theory, this is a clean optimization loop. In practice, running hundreds of candidates across multiple neural networks quickly creates memory and architectural bottlenecks.</p>
<hr />
<h2>[3] <u>System Bottlenecks in the Original Architecture</u></h2>
<p>Before the refactoring, the optimization logic was concentrated inside two oversized methods: <code>GCGMultiPromptAttack.step()</code> and <code>ProgressiveMultiPromptAttack.run()</code>.</p>
<p>This monolithic design introduced three major engineering problems:</p>
<h3>1. High Coupling and Test Fragility</h3>
<p>A single function was responsible for dispatching worker tasks across GPUs, aggregating incompatible gradient tensor shapes, sampling replacement tokens, executing model forward passes, and deciding step budgets. Because every phase was interwoven, writing fast unit tests was impossible without spinning up heavy model instances.</p>
<h3>2. VRAM Spikes and Out-Of-Memory Crashes</h3>
<p>Evaluating candidate tokens requires computing model logits across prompts and models. The original implementation held intermediate tensors in GPU memory throughout the step loop. During multi-prompt or multi-model runs, this caused sudden Out-Of-Memory (OOM) failures on GPU clusters.</p>
<h3>3. Entangled Scheduling State</h3>
<p>GCG uses a progressive curriculum: it starts on a single prompt and single model, then gradually admits more prompts, more models, and increases control weights. These state transitions were managed through nested conditionals, leading to edge cases where non-finite losses (<code>NaN</code> or <code>Inf</code>) could inadvertently trigger early exit.</p>
<hr />
<h2>[4] <u>Architecture Redesign - Phase Extraction Strategy</u></h2>
<p>Under tracking issue <a href="https://github.com/microsoft/PyRIT/issues/2665">#2665</a>, Microsoft maintainer <a href="https://github.com/romanlutz"><strong>Roman Lutz</strong></a> outlined the architectural requirements: decouple the pipeline into three independently reviewable, typed modules while maintaining 100% backward compatibility for all existing public APIs.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aed55f785cd1b8ef5a0f98d/e67f006e-fe25-4967-baea-bcaf8789c5c3.png" alt="" style="display:block;margin:0 auto" />

<p>The refactoring was executed across three distinct engineering phases.</p>
<hr />
<h2>[5] Phase 1: <u>Decoupling Candidate Proposal</u></h2>
<p>Reference -&gt; <a href="https://github.com/microsoft/PyRIT/pull/2671">PR #2671</a></p>
<p>The first phase focused on extracting gradient aggregation, normalization, and candidate sampling logic out of <code>GCGMultiPromptAttack.step()</code>.</p>
<h3>Technical Implementation</h3>
<p>First, I created an immutable data container using <code>@dataclass(frozen=True, slots=True)</code> to cleanly pass proposed candidates between modules:</p>
<pre><code class="language-python">@dataclass(frozen=True, slots=True)
class CandidateProposalBatch:
    """Grouped candidate control tokens and filtered text strings across compatible workers."""
    control_candidates_by_group: list[list[str]]
    group_worker_indices: list[int]

    @property
    def num_groups(self) -&gt; int:
        return len(self.control_candidates_by_group)
</code></pre>
<p>Inside <code>GCGCandidateProposer</code>, we dispatch gradient computations across worker threads and normalize the gradients with an \(L_2\) norm so models with different loss magnitudes contribute equally:</p>
<pre><code class="language-python"># Pull gradient from worker and migrate to primary device
new_grad: torch.Tensor = worker.results.get().to(self._main_device)

# L2 normalization across embedding dimensions
new_grad = new_grad / new_grad.norm(dim=-1, keepdim=True)
</code></pre>
<p>Next came handling heterogeneous models. If an attack targets both LLaMA and Mistral simultaneously, their vocabularies and gradient tensor shapes differ. Instead of crashing, the proposer groups compatible shapes together:</p>
<pre><code class="language-python"># If vocabulary or tensor shape differs, flush the preceding group
if grad.shape != new_grad.shape or vocab != new_vocab:
    with torch.no_grad():
        sampled = self._sample_group(worker_idx=j - 1, gradient=grad, batch_size=batch_size, topk=topk)
        filtered = self._filter_group(worker_idx=j - 1, control_cand=sampled)
        control_cands.append(filtered)
        group_worker_indices.append(j - 1)
    grad = new_grad
    vocab = new_vocab
else:
    grad += new_grad
</code></pre>
<p>By extracting this into <code>GCGCandidateProposer</code>, we gained the ability to write 6 fast, deterministic unit tests for gradient aggregation and shape grouping without allocating real neural networks.</p>
<hr />
<h2>[6] Phase 2: <u>VRAM-Bounded Candidate Evaluation</u></h2>
<p>Reference -&gt; <a href="https://github.com/microsoft/PyRIT/pull/2700">PR #2700</a></p>
<p>The second phase addressed GPU memory exhaustion by isolating candidate evaluation into a dedicated, memory-efficient component: <code>GCGCandidateEvaluator</code>.</p>
<h3>Technical Implementation</h3>
<p>When evaluating 512 or 1,024 candidate suffixes across multiple models, storing all prediction logits simultaneously quickly exceeds the 24GB or 80GB VRAM limits of modern GPUs.</p>
<p>To fix this, the evaluation loop evaluates prompts sequentially and explicitly frees temporary tensors from GPU memory immediately after their losses are accumulated:</p>
<pre><code class="language-python"># pyrit/executor/promptgen/gcg/attack/gcg/candidate_evaluator.py

with torch.no_grad():
    for j, cand in enumerate(control_candidates_by_group):
        for i in range(num_prompts):
            # Dispatch logits computation to worker models
            for k, worker in enumerate(self._workers):
                worker(self._prompts[k][i], ModelWorkerOperation.LOGITS, cand, return_ids=True)

            # Retrieve worker logits and token IDs
            logits, ids = zip(*[worker.results.get() for worker in self._workers], strict=True)

            # Accumulate loss on main_device across all models
            loss[j * batch_size : (j + 1) * batch_size] += sum(
                self._loss_function.compute_loss(
                    logits=logit,
                    token_ids=token_ids,
                    target_slice=self._prompts[k][i]._target_slice,
                    control_slice=self._prompts[k][i]._control_slice,
                ).to(self._main_device)
                for k, (logit, token_ids) in enumerate(zip(logits, ids, strict=True))
            )

            # Explicit cleanup: delete bulky intermediate tensors immediately
            del logits, ids
</code></pre>
<p>Why does <code>del logits, ids</code> matter here? In Python, temporary variables inside loops can remain referenced until the next iteration. In PyTorch, logits tensors for large batch sizes occupy gigabytes of memory. Calling <code>del logits, ids</code> informs Python's garbage collector and the PyTorch caching allocator that these memory blocks can be reused immediately, bounding VRAM consumption to a constant size.</p>
<p>Additionally, the evaluator accounts for numerical edge cases such as <code>NaN</code> or <code>Inf</code> losses, ensuring stability during extreme optimization steps.</p>
<hr />
<h2>[7] Phase 3: <u>Progressive Scheduling and State Machine</u></h2>
<p>Reference -&gt; <a href="https://github.com/microsoft/PyRIT/pull/2720">PR #2720</a></p>
<p>The final phase extracted multi-model curriculum scheduling and budget accounting into <code>ProgressiveScheduleController</code>.</p>
<h3>Technical Implementation</h3>
<p>Instead of maintaining nested <code>if/else</code> flags inside <code>ProgressiveMultiPromptAttack.run()</code>, we created an explicit state machine and state container:</p>
<pre><code class="language-python">class ScheduleTransitionAction(Enum):
    """Action to take following an inner attack result in progressive scheduling."""
    CONTINUE = auto()
    FINALIZE_AND_STOP = auto()


@dataclass
class ProgressiveScheduleState:
    """Typed schedule state tracking curriculum admission and steps."""
    goals_admitted: int
    workers_admitted: int
    steps_completed: int = 0
    loss: float = float("inf")
    stop_inner_on_success: bool = False
</code></pre>
<p>The controller enforces an invariant in multi-model attacks: <strong>goals must be admitted before target model workers are expanded</strong>.</p>
<pre><code class="language-python"># pyrit/executor/promptgen/gcg/attack/base/progressive_schedule.py

def update_on_success(self, state: ProgressiveScheduleState) -&gt; ScheduleTransitionAction:
    """Advance the progressive curriculum when an attack threshold is met."""
    if state.goals_admitted &lt; self._total_goals:
        # Admit next attack goal
        state.goals_admitted += 1
        return ScheduleTransitionAction.CONTINUE
    elif state.workers_admitted &lt; self._total_workers:
        # Admit next target model worker
        state.workers_admitted += 1
        return ScheduleTransitionAction.CONTINUE
    
    # All goals and workers admitted: finalize attack
    return ScheduleTransitionAction.FINALIZE_AND_STOP
</code></pre>
<p>The controller also manages exact step budgeting, guaranteeing the attack terminates cleanly when the user's iteration limit is reached without leaving hanging tasks.</p>
<hr />
<h2>[8] <u>Code Quality</u>, <u>Reviews</u>, and <u>Maintainer Collaboration</u></h2>
<p>Working on core infrastructure in a major open-source repository requires close collaboration with maintainers. Throughout this project, Microsoft maintainer <strong>Roman Lutz</strong> reviewed each pull request.</p>
<p>During the execution of Phase 2, Roman acknowledged the development velocity:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aed55f785cd1b8ef5a0f98d/cea1c43d-e466-4c04-a648-69f66d7b8fcf.png" alt="" />

<p>In Phase 3 (<a href="https://github.com/microsoft/PyRIT/pull/2720">PR #2720</a>), Roman identified an important compatibility detail regarding Python class definitions. When <code>ProgressiveScheduleState</code> was moved into the new module, an existing import in <code>attack_manager.py</code> shadowed the class. As a result, caller <code>isinstance()</code> checks returned <code>False</code>:</p>
<blockquote>
<p><em>"The original</em> <code>ProgressiveScheduleState</code> <em>definition still shadows the new import. That means</em> <code>controller.state</code> <em>is a different class from the one exposed through the existing import path... Dataclass equality also fails between the two classes even when every field matches... A regression assertion using the existing import path would catch this."</em></p>
</blockquote>
<p>I resolved this by removing the duplicate definition, re-exporting the imported class directly, and adding explicit test assertions covering both <code>isinstance</code> and dataclass equality:</p>
<pre><code class="language-python"># tests/unit/executor/promptgen/gcg/test_run_state.py

assert isinstance(attack.last_schedule_state, attack_manager.ProgressiveScheduleState)
assert attack.last_schedule_state == expected_state
</code></pre>
<p>Roman verified the fix and approved the PR:</p>
<blockquote>
<p><em>"Confirmed in 2138122 that both import paths now expose the same class, restoring</em> <code>isinstance</code> <em>checks and dataclass equality for the returned state. This addresses the compatibility issue."</em></p>
</blockquote>
<p>With all three PRs merged, Issue #2665 was officially closed, setting the stage for future performance optimizations:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6aed55f785cd1b8ef5a0f98d/b1aeacd6-caf3-4f61-9bab-74ce6b2a0f6c.png" alt="" />

<hr />
<h2>[9] <u>Results and Engineering Impact</u></h2>
<p>The refactoring achieved significant measurable improvements:</p>
<ul>
<li><strong>Clean Separation of Concerns:</strong> Replaced a 1,000-line monolithic loop with three modular, single-responsibility components.</li>
<li><strong>Eliminated VRAM Spikes:</strong> Sequential evaluation paired with immediate tensor disposal resolved GPU memory exhaustion across multi-model benchmarks.</li>
<li><strong>Zero Breaking Changes:</strong> Maintained complete backward compatibility for all public methods, parameter signatures, and configuration classes.</li>
<li><strong>Comprehensive Test Suite:</strong> Over 300 automated unit and integration tests now run deterministically across Windows, macOS, and Linux runners without requiring active GPUs.</li>
</ul>
<hr />
<h2>[10] <u>My Takeaways</u></h2>
<ol>
<li><strong>Decouple Complex Algorithms Early:</strong> Large research algorithms tend to accumulate responsibilities over time. Separating proposal, evaluation, and scheduling makes codebases easier to maintain and extend.</li>
<li><strong>Deterministic Cleanup in Accelerated Workloads:</strong> In PyTorch applications, relying solely on Python automatic garbage collection is insufficient for high-throughput loops. Explicitly deleting intermediate tensors prevents memory fragmentation.</li>
<li><strong>Guard Class Contracts During Refactoring:</strong> Moving a dataclass or type definition can silently break <code>isinstance</code> checks if legacy export paths are not strictly validated with regression tests.</li>
</ol>
<hr />
<blockquote>
<p>Thanks for Reading :)</p>
<p>Connect with me on <a href="https://www.linkedin.com/in/vardhman-gupta/">LinkedIn</a></p>
</blockquote>
]]></content:encoded></item></channel></rss>