<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://vogella.com/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://vogella.com/blog/" rel="alternate" type="text/html" /><updated>2026-10-07T08:25:24+00:00</updated><id>https://vogella.com/blog/feed.xml</id><title type="html">vogella Blog</title><subtitle>Blog posts about Java, Eclipse, OSGi and  Open Source</subtitle><author><name>Lars</name></author><entry><title type="html">A batch of SWT performance fixes and cleanups</title><link href="https://vogella.com/blog/swt-performance-fixes/" rel="alternate" type="text/html" title="A batch of SWT performance fixes and cleanups" /><published>2026-10-07T00:00:00+00:00</published><updated>2026-10-07T00:00:00+00:00</updated><id>https://vogella.com/blog/swt-performance-fixes</id><content type="html" xml:base="https://vogella.com/blog/swt-performance-fixes/"><![CDATA[<p>A while ago we went through the core classes of SWT looking for hot spots and dead weight for our custom Eclipse IDE build, which we and some of our clients use to have a faster Eclipse IDE.
The candidates came from an AI-assisted review, but none of them was taken on trust: every claim was checked against the code and benchmarked where possible.
Several ideas did not survive that check, for example savings of about 70 ns per mouse motion event that are not worth the extra code.
After using them for a while, we contributed them upstream to <a href="https://github.com/eclipse-platform/eclipse.platform.swt">eclipse.platform.swt</a>, and all of them are now merged into SWT master.</p>

<h2 id="the-biggest-wins">The biggest wins</h2>

<p>Clearing a <code class="language-plaintext highlighter-rouge">SWT.MULTI</code> <code class="language-plaintext highlighter-rouge">List</code> with 100,000 entries took almost a minute on GTK and now takes under a second, which also speeds up JFace <code class="language-plaintext highlighter-rouge">ListViewer.setInput</code> and <code class="language-plaintext highlighter-rouge">refresh</code>.
Disposing tree items one by one, as JFace <code class="language-plaintext highlighter-rouge">AbstractTreeViewer</code> does on remove and refresh, no longer walks all root rows on every call, so 50,000 items go away in about 0.15 s instead of 8 to 10 s.
On GTK, <code class="language-plaintext highlighter-rouge">Tree.getItemCount()</code> and <code class="language-plaintext highlighter-rouge">TreeItem.getItemCount()</code> are now cached instead of walking the sibling list on every call, so 40,000 calls on an item with 40,000 children take about 1 ms instead of 7 to 12 s.
<code class="language-plaintext highlighter-rouge">GridLayout</code> grows its grid geometrically instead of four rows at a time, which turns a quadratic <code class="language-plaintext highlighter-rouge">computeSize</code> into a linear one for composites with many children.
<code class="language-plaintext highlighter-rouge">List.setSelection(String[])</code> stops copying the whole list natively for every string.</p>

<h2 id="how-it-was-measured">How it was measured</h2>

<p>All numbers are from Linux with GTK 3.24 on X11 under Xvfb, Java 21 and 25, comparing the old and the new class on the same build on a single machine.
Ranges show run-to-run variation.
Most hot spots are GTK specific; #3640, #3642, #3646, #3649, #3650 and #3654 are platform independent or touch all platforms.</p>

<h2 id="performance-changes">Performance changes</h2>

<table>
  <thead>
    <tr>
      <th>PR</th>
      <th>Change</th>
      <th>Scenario</th>
      <th>Before</th>
      <th>After</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3655">#3655</a></td>
      <td>[GTK] <code class="language-plaintext highlighter-rouge">List.removeAll</code>/<code class="language-plaintext highlighter-rouge">setItems</code> on <code class="language-plaintext highlighter-rouge">SWT.MULTI</code> lists switch to browse selection mode around <code class="language-plaintext highlighter-rouge">gtk_list_store_clear</code>, as <code class="language-plaintext highlighter-rouge">Table.removeAll</code> does</td>
      <td><code class="language-plaintext highlighter-rouge">removeAll</code>, 20k / 50k / 100k rows</td>
      <td>1.29 s / 10.05 s / 54.8 s</td>
      <td>0.19 s / 0.46 s / 0.98 s</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">setItems</code> (one item), 20k / 50k / 100k rows</td>
      <td>1.26 s / 10.0 s / 55.9 s</td>
      <td>0.19 s / 0.48 s / 0.97 s</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3643">#3643</a></td>
      <td>[GTK] Tree counts root rows for <code class="language-plaintext highlighter-rouge">EmptinessChanged</code> only when observed, then with an O(1) iterator check</td>
      <td>Insert at index 0, 40k / 160k items</td>
      <td>3.3 s / 148 s</td>
      <td>0.15 s / 0.59 s</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td>Append, 10k / 50k items</td>
      <td>0.46-0.71 s / 17-22 s</td>
      <td>0.34-0.41 s / about 14.5 s (rest is <code class="language-plaintext highlighter-rouge">GtkTreeStore</code> itself)</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3656">#3656</a></td>
      <td>[GTK] <code class="language-plaintext highlighter-rouge">TreeItem.dispose</code> drops an always-true <code class="language-plaintext highlighter-rouge">getItemCount() &gt; 0</code> check that walked all root rows</td>
      <td>Dispose 50k root items from the front</td>
      <td>8.0-10.6 s</td>
      <td>4.7-6.5 s alone, 0.12-0.15 s with #3643</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td>Dispose 10k root items, with #3643</td>
      <td>about 180 ms</td>
      <td>31-45 ms</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3664">#3664</a></td>
      <td>[GTK] <code class="language-plaintext highlighter-rouge">Tree</code> and <code class="language-plaintext highlighter-rouge">TreeItem</code> cache their child counts behind a counter bumped on every row insertion or removal, instead of calling <code class="language-plaintext highlighter-rouge">gtk_tree_model_iter_n_children</code>; a partial fix for <a href="https://github.com/eclipse-platform/eclipse.platform.swt/issues/882">#882</a>, as <code class="language-plaintext highlighter-rouge">getItem(int)</code> and <code class="language-plaintext highlighter-rouge">indexOf</code> still use linear GTK lookups</td>
      <td>40k <code class="language-plaintext highlighter-rouge">getItemCount()</code> calls, item with 40k children</td>
      <td>7-12 s</td>
      <td>about 1 ms</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td>Reveal-like loop of <code class="language-plaintext highlighter-rouge">getItemCount()</code> plus <code class="language-plaintext highlighter-rouge">getItem(i)</code></td>
      <td>baseline</td>
      <td>3 to 5 times faster</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3642">#3642</a></td>
      <td><code class="language-plaintext highlighter-rouge">GridLayout</code> grows its grid geometrically and allocates rows lazily</td>
      <td><code class="language-plaintext highlighter-rouge">computeSize</code>, 1,000 labels</td>
      <td>4.4-7.1 ms, 3.4 MB allocated</td>
      <td>0.27-0.40 ms, 44 KB</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">computeSize</code>, 5,000 labels</td>
      <td>70-100 ms, 85.7 MB allocated</td>
      <td>1.0-1.3 ms, 245 KB</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">layout()</code>, 5,000 labels</td>
      <td>about 100-130 ms</td>
      <td>46-52 ms</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3657">#3657</a></td>
      <td>[GTK] <code class="language-plaintext highlighter-rouge">List.setSelection(String[])</code> fetches the items once; also fixes <code class="language-plaintext highlighter-rouge">indexOf(String, int)</code> throwing for a negative start</td>
      <td>100 strings, 10k / 50k items</td>
      <td>587 ms / 3.17 s</td>
      <td>7 ms / 33 ms</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3640">#3640</a></td>
      <td><code class="language-plaintext highlighter-rouge">Control.sort(int[])</code> uses <code class="language-plaintext highlighter-rouge">Arrays.sort</code> instead of a shell sort that was always O(n²); used by <code class="language-plaintext highlighter-rouge">Table.remove(int[])</code> and <code class="language-plaintext highlighter-rouge">List.remove(int[])</code></td>
      <td>50,000 indices, ascending / random</td>
      <td>2,416 ms / 2,598 ms</td>
      <td>2.5 ms / 10.7 ms</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3650">#3650</a></td>
      <td>StyledText <code class="language-plaintext highlighter-rouge">Bullet.indexOf</code> uses binary search</td>
      <td><code class="language-plaintext highlighter-rouge">indexOf</code> over all lines, 100k-line document</td>
      <td>0.6-1.2 s</td>
      <td>4-15 ms</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td>Full measurement, numbered bullet on every line</td>
      <td>3.8-5.0 s</td>
      <td>1.7-2.5 s (rest is text layout)</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3654">#3654</a></td>
      <td><code class="language-plaintext highlighter-rouge">StyledTextRenderer.reset(int,int)</code> loops over the range instead of building a boxed <code class="language-plaintext highlighter-rouge">TreeSet</code>, plus a condition fix</td>
      <td><code class="language-plaintext highlighter-rouge">setFont</code>, 100k / 1M lines</td>
      <td>17.3 ms / 187.6 ms</td>
      <td>6.4 ms / 4.6 ms</td>
    </tr>
    <tr>
      <td> </td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">setTabs</code>, 100k / 1M lines</td>
      <td>12.9 ms / 198.6 ms</td>
      <td>2.0 ms / 3.6 ms</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3641">#3641</a></td>
      <td>[GTK] <code class="language-plaintext highlighter-rouge">Display.getClosure</code> reuses signal closures again, a guard lost in 2019</td>
      <td>Widget creation</td>
      <td>about 20 new <code class="language-plaintext highlighter-rouge">GClosure</code>s per control</td>
      <td>a new closure every 255 connections, roughly 80 JNI calls and native allocations less per widget</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3651">#3651</a></td>
      <td>[GTK] Combo removes itself from its parent’s <code class="language-plaintext highlighter-rouge">fixClipMap</code> on dispose; also fixes clipping after <code class="language-plaintext highlighter-rouge">setParent</code></td>
      <td>200 Combos disposed</td>
      <td>200 leaked entries with listeners and data</td>
      <td>0</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3645">#3645</a></td>
      <td>[GTK] Cheap Java checks before <code class="language-plaintext highlighter-rouge">OS.isWayland()</code> in <code class="language-plaintext highlighter-rouge">sendMouseEvent</code></td>
      <td>Mouse events without listener</td>
      <td>up to 6 JNI calls per event</td>
      <td>none</td>
    </tr>
    <tr>
      <td><a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3652">#3652</a></td>
      <td>[GTK] <code class="language-plaintext highlighter-rouge">Control.gtk_draw</code> reads the cairo clip rectangle only when a Paint listener exists</td>
      <td>Each draw of a control without Paint listener</td>
      <td>1 allocation and 1 JNI call</td>
      <td>none, about 70 ns saved</td>
    </tr>
  </tbody>
</table>

<h2 id="code-simplification">Code simplification</h2>

<p>Alongside the performance work, a handful of pull requests remove about 245 lines without changing behaviour.
<a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3648">#3648</a> drops unused internal members on GTK (-95 lines).
<a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3653">#3653</a> shares the cell renderer lookups of Table, Tree and List in <code class="language-plaintext highlighter-rouge">Scrollable</code> (-51 lines).
<a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3646">#3646</a> simplifies the CTabFolder gradient setters with <code class="language-plaintext highlighter-rouge">Arrays</code> utilities, checked equivalent on 4 million random inputs (-42 lines).
<a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3649">#3649</a> replaces 28 hand-written array copies with <code class="language-plaintext highlighter-rouge">Arrays.copyOf</code> and <code class="language-plaintext highlighter-rouge">clone</code> (-32 lines).
<a href="https://github.com/eclipse-platform/eclipse.platform.swt/pull/3647">#3647</a> extracts the search column update that was copied three times in Table and Tree (-18 lines), and #3654 above removes another 7.</p>

<h2 id="interested">Interested?</h2>

<p>If you are also looking to improve your code base, have a look at our <a href="https://vogella.com/services/">AI-based services</a>.</p>]]></content><author><name>Lars Vogel</name></author><category term="eclipse" /><category term="java" /><summary type="html"><![CDATA[A while ago we went through the core classes of SWT looking for hot spots and dead weight for our custom Eclipse IDE build, which we and some of our clients use to have a faster Eclipse IDE. The candidates came from an AI-assisted review, but none of them was taken on trust: every claim was checked against the code and benchmarked where possible. Several ideas did not survive that check, for example savings of about 70 ns per mouse motion event that are not worth the extra code. After using them for a while, we contributed them upstream to eclipse.platform.swt, and all of them are now merged into SWT master.]]></summary></entry><entry><title type="html">Gruvbox Material theme for the Eclipse IDE</title><link href="https://vogella.com/blog/eclipse-gruvbox-material-theme/" rel="alternate" type="text/html" title="Gruvbox Material theme for the Eclipse IDE" /><published>2026-09-30T00:00:00+00:00</published><updated>2026-09-30T00:00:00+00:00</updated><id>https://vogella.com/blog/eclipse-gruvbox-material-theme</id><content type="html" xml:base="https://vogella.com/blog/eclipse-gruvbox-material-theme/"><![CDATA[<p>Our <a href="/blog/eclipse-themes/">set of Eclipse themes</a> has a new member: Gruvbox Material.
It follows sainnhe’s popular Gruvbox Material palette, which tones down the warm retro colors of the original Gruvbox to something that is easy on the eyes for a full working day.</p>

<figure class="">
  <img src="/blog/assets/images/eclipse-theme-gruvbox-material.png" alt="The Eclipse IDE with the Gruvbox Material theme" /></figure>

<h2 id="the-palette">The palette</h2>

<p>The theme is based on the VS Code extension of Gruvbox Material with its default medium background and the material foreground.
Editors and views sit on a soft dark brown <code class="language-plaintext highlighter-rouge">#292828</code>, while the surrounding window chrome uses the darker <code class="language-plaintext highlighter-rouge">#1C1C1C</code> of the extension’s flat workbench.
Text is a warm <code class="language-plaintext highlighter-rouge">#D4BE98</code>, and Java code uses the extension’s syntax roles: red keywords, yellow strings and green functions.</p>

<p>We made one deliberate change to the original.
The extension’s lighter greys, which it uses for muted text, fail the WCAG AA contrast ratio on these surfaces.
The Eclipse theme uses the brighter grey <code class="language-plaintext highlighter-rouge">#A89984</code> for muted text and the tab keyline instead, so secondary information stays readable.</p>

<p>Like the other themes, Gruvbox Material also writes its editor, syntax highlighting and console colors into the matching preferences, so the Java editor, the console and the views all follow the palette.</p>

<p>The screenshot was taken through our <a href="/blog/eclipse-mcp-server/">MCP server for the Eclipse IDE</a>: an agent installed the theme from the update site, restarted the IDE, switched the theme and captured the window.</p>

<h2 id="installing">Installing</h2>

<p>In Eclipse, choose <em>Help &gt; Install New Software</em> and add the update site:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://vogellacompany.github.io/eclipse-themes/
</code></pre></div></div>

<p>Select the Gruvbox Material feature, restart, and switch to it under <em>Preferences &gt; General &gt; Appearance</em>.
If you already added the update site for one of our other themes, Gruvbox Material is listed there as well.</p>

<p>Sources and issues: <a href="https://github.com/vogellacompany/eclipse-themes">github.com/vogellacompany/eclipse-themes</a>.
Please open an issue there if the theme renders something wrong on your platform.</p>]]></content><author><name>Lars Vogel</name></author><category term="eclipse" /><summary type="html"><![CDATA[Our set of Eclipse themes has a new member: Gruvbox Material. It follows sainnhe’s popular Gruvbox Material palette, which tones down the warm retro colors of the original Gruvbox to something that is easy on the eyes for a full working day.]]></summary></entry><entry><title type="html">Performance optimization in Eclipse based applications</title><link href="https://vogella.com/blog/performance-optimization-eclipse-applications/" rel="alternate" type="text/html" title="Performance optimization in Eclipse based applications" /><published>2026-09-24T00:00:00+00:00</published><updated>2026-09-24T00:00:00+00:00</updated><id>https://vogella.com/blog/performance-optimization-eclipse-applications</id><content type="html" xml:base="https://vogella.com/blog/performance-optimization-eclipse-applications/"><![CDATA[<p>A performance pull request usually comes with an argument, not with a measurement.
This post walks through one such PR, shows how we measured it in a running IDE, and what the numbers say about the argument.</p>

<h2 id="the-change">The change</h2>

<p>The Dependencies page of the PDE manifest editor lists the Import-Package entries of a bundle.
For every row the label provider asks whether some bundle in the target platform exports that package, and picks the icon accordingly.</p>

<p>The stock implementation answered that question through <code class="language-plaintext highlighter-rouge">State.getExportedPackages()</code>.
That method is not cached: every call takes the state monitor and copies the exports of all resolved bundles into a freshly allocated array.
With an SDK sized target platform that is about 3200 exports copied per row, on the UI thread, for every refresh of the table.</p>

<p><a href="https://github.com/eclipse-pde/eclipse.pde/pull/2444">eclipse.pde#2444</a> replaces the copy with a walk over the resolved bundles that reads the exports in place and stops at the first match.
The PR description argues with reference stores: roughly 9600 per call before, 731 after.</p>

<p>On the related Equinox change, <a href="https://github.com/eclipse-equinox/equinox/pull/1347">equinox#1347</a>, the reviewer <a href="https://github.com/eclipse-equinox/equinox/pull/1347#issuecomment-5509523286">asked for performance metrics</a> instead: fewer reference stores do not always result in a measurable improvement.
That is a fair question, and it needs numbers from a real IDE rather than a microbenchmark.</p>

<h2 id="setup">Setup</h2>

<p>Eclipse SDK 4.42 I-build 20260902-2327, stock <code class="language-plaintext highlighter-rouge">org.eclipse.osgi</code> 3.24, running headless under Xvfb.
The target platform is the running platform: 850 bundles, about 3200 exported packages.</p>

<p>Before: stock <code class="language-plaintext highlighter-rouge">org.eclipse.pde.core</code> 3.21.500.v20260902-1925.
After: <code class="language-plaintext highlighter-rouge">org.eclipse.pde.core</code> built from the PR branch, everything else identical, IDE started with <code class="language-plaintext highlighter-rouge">-clean</code>.</p>

<p>Two manifests: <code class="language-plaintext highlighter-rouge">org.eclipse.equinox.p2.tests</code> with 48 Import-Package rows and a copy of the <code class="language-plaintext highlighter-rouge">org.apache.ant</code> manifest with 105 rows.
The table refreshes twice per open, once on creation and once on page activation, so each open is 96 or 210 calls of <code class="language-plaintext highlighter-rouge">isResolved()</code>.</p>

<p>The scenario, ten iterations per run: close the manifest editor, open <code class="language-plaintext highlighter-rouge">META-INF/MANIFEST.MF</code>, switch to the Dependencies page, wait until the UI thread is idle.</p>

<h2 id="results">Results</h2>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>48 rows before</th>
      <th>48 rows after</th>
      <th>105 rows before</th>
      <th>105 rows after</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>UI thread samples in <code class="language-plaintext highlighter-rouge">getExportedPackages</code> / all samples</td>
      <td>5 / 533</td>
      <td>0 / 529</td>
      <td>12 / 543</td>
      <td>0 / 462</td>
    </tr>
    <tr>
      <td>Estimated UI thread time in <code class="language-plaintext highlighter-rouge">isResolved</code> per page open</td>
      <td>about 5 ms</td>
      <td>below 1 ms</td>
      <td>about 12 ms</td>
      <td>below 1 ms</td>
    </tr>
    <tr>
      <td>Allocation per <code class="language-plaintext highlighter-rouge">isResolved</code> call</td>
      <td>about 360 KB</td>
      <td>about 7 KB</td>
      <td>about 220 KB</td>
      <td>not sampled</td>
    </tr>
    <tr>
      <td>JFR allocation weight under <code class="language-plaintext highlighter-rouge">isResolved</code>, ten opens</td>
      <td>175 MB</td>
      <td>4 MB</td>
      <td>470 MB</td>
      <td>none sampled</td>
    </tr>
    <tr>
      <td>Median switch to the Dependencies page</td>
      <td>68 ms</td>
      <td>66 ms</td>
      <td>57 ms</td>
      <td>55 ms</td>
    </tr>
    <tr>
      <td>GC events during the run</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
    </tr>
  </tbody>
</table>

<p>Before the change, every sampled stack is one path: the Dependencies page refreshes its table viewer, the label provider asks for the image, <code class="language-plaintext highlighter-rouge">ImportPackageObject.isResolved</code> calls <code class="language-plaintext highlighter-rouge">StateImpl.getExportedPackages</code>.
The allocation under it is the fresh <code class="language-plaintext highlighter-rouge">ExportPackageDescription[]</code> plus the <code class="language-plaintext highlighter-rouge">ArrayList</code> growth that builds it.</p>

<figure class="">
  <img src="/blog/assets/images/pde-import-package-getexportedpackages-flame-before.png" alt="Flame graph of the UI thread samples containing StateImpl.getExportedPackages before the change" /><figcaption>
      UI thread samples containing getExportedPackages before the change, 48 rows. After the change there is no such sample.

    </figcaption></figure>

<p>After the change the method no longer appears in the 10 ms samples at all.
What JFR still attributes to <code class="language-plaintext highlighter-rouge">isResolved</code> is the <code class="language-plaintext highlighter-rouge">BundleDescription[]</code> copy returned by <code class="language-plaintext highlighter-rouge">State.getResolvedBundles()</code>.</p>

<h2 id="what-the-numbers-say">What the numbers say</h2>

<p>The work and the allocation scale with the row count, as the PR says, and the change removes them.
Both are real: a few hundred kilobytes of garbage per row, on every refresh, on the UI thread.</p>

<p>The page does not get measurably faster at 48 or 105 rows.
The 5 to 12 ms per open were already below the cost of the page switch itself, and no garbage collection was triggered on either side, so the removed allocation never turned into a pause during these runs.
A visible difference would need several hundred imports, or the garbage coinciding with a collection.</p>

<p>So the reviewer’s caution holds up as well as the PR does.
The change is an improvement in work done and memory allocated, not a latency win a user would notice on a typical manifest.
That is the answer a reviewer asking for metrics needs, and it is a better basis for the discussion than either “fewer reference stores” or “no visible difference” alone.</p>

<h2 id="how-we-measured">How we measured</h2>

<p>The measurement was driven by an agent through our <a href="/blog/eclipse-mcp-server/">MCP server for the Eclipse IDE</a>, against the real IDE instead of a benchmark harness.</p>

<p><code class="language-plaintext highlighter-rouge">eclipse_run_script</code> runs the scenario: close the editor, open the manifest, switch to the Dependencies tab, then <code class="language-plaintext highlighter-rouge">eclipse_wait_until_settled</code> until the UI thread is idle.
It repeats this ten times and reports the milliseconds per step, which gives the page switch row of the table.</p>

<p><code class="language-plaintext highlighter-rouge">eclipse_start_sampling</code> and <code class="language-plaintext highlighter-rouge">eclipse_stop_sampling</code> sample the UI thread at 10 ms.
A <code class="language-plaintext highlighter-rouge">frameFilter</code> restricts the result to samples that contain the frame of interest, here <code class="language-plaintext highlighter-rouge">getExportedPackages</code>, and <code class="language-plaintext highlighter-rouge">eclipse_show_trace</code> renders them as the flame graph above.</p>

<p><code class="language-plaintext highlighter-rouge">eclipse_start_flight_recording</code> and <code class="language-plaintext highlighter-rouge">eclipse_stop_flight_recording</code> record the same ten iterations with JFR allocation sampling.
The same <code class="language-plaintext highlighter-rouge">frameFilter</code> attributes the allocation by stack, which gives the allocation rows.</p>

<p>For the after side, <code class="language-plaintext highlighter-rouge">eclipse_substitute_bundle</code> installed a <code class="language-plaintext highlighter-rouge">org.eclipse.pde.core</code> jar built from the PR branch and restarted the IDE with <code class="language-plaintext highlighter-rouge">-clean</code>.
Everything else stayed the same, so the two runs differ only in the bundle under test.</p>

<p>The server is open source: <a href="https://github.com/vogellacompany/eclipse-mcp-server">github.com/vogellacompany/eclipse-mcp-server</a>.</p>]]></content><author><name>Lars Vogel</name></author><category term="eclipse" /><category term="performance" /><summary type="html"><![CDATA[A performance pull request usually comes with an argument, not with a measurement. This post walks through one such PR, shows how we measured it in a running IDE, and what the numbers say about the argument.]]></summary></entry><entry><title type="html">An AsciiDoc editor for the Eclipse IDE</title><link href="https://vogella.com/blog/eclipse-asciidoc-editor/" rel="alternate" type="text/html" title="An AsciiDoc editor for the Eclipse IDE" /><published>2026-09-16T00:00:00+00:00</published><updated>2026-09-16T00:00:00+00:00</updated><id>https://vogella.com/blog/eclipse-asciidoc-editor</id><content type="html" xml:base="https://vogella.com/blog/eclipse-asciidoc-editor/"><![CDATA[<p>We have published an AsciiDoc editor for the Eclipse IDE.
It is built on LSP4E and TM4E and uses a language server of its own, so editing, diagnostics and the preview all work from one model of the document.</p>

<figure class="">
  <img src="/blog/assets/images/eclipse-asciidoc-editor-preview.png" alt="The live preview of the AsciiDoc editor in the Eclipse IDE" /></figure>

<p>The editor has two tabs: <em>Source</em> for editing and <em>Preview</em> for the rendered HTML, which updates as you type.
The preview covers lists, tables, admonitions, source blocks, images and includes.</p>

<p>Beyond the preview, the editor offers:</p>

<ul>
  <li>Errors for missing included files, and warnings for missing images, broken cross-references, duplicate anchors and broken file links</li>
  <li>Code completion for snippets, image and include paths, anchors and document attributes</li>
  <li>Go to Definition for anchors, headings and included files, plus clickable <code class="language-plaintext highlighter-rouge">include::</code>, <code class="language-plaintext highlighter-rouge">image::</code> and URL links</li>
  <li>Folding for sections, comment blocks and delimited blocks</li>
  <li>Image lookup through <code class="language-plaintext highlighter-rouge">imagesdir</code>, with a fallback to a sibling <code class="language-plaintext highlighter-rouge">images/</code> or <code class="language-plaintext highlighter-rouge">img/</code> folder</li>
</ul>

<h2 id="installing">Installing</h2>

<p>In Eclipse, choose <em>Help &gt; Install New Software</em> and add the update site:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://vogellacompany.github.io/eclipse-asciidoc/
</code></pre></div></div>

<p>The editor is also listed on the <a href="https://marketplace.eclipse.org/content/asciidoc-editor">Eclipse Marketplace</a>.</p>

<p>Sources and issues: <a href="https://github.com/vogellacompany/eclipse-asciidoc">github.com/vogellacompany/eclipse-asciidoc</a>.
Please open an issue there if a document does not render the way you expect.</p>]]></content><author><name>Lars Vogel</name></author><category term="eclipse" /><summary type="html"><![CDATA[We have published an AsciiDoc editor for the Eclipse IDE. It is built on LSP4E and TM4E and uses a language server of its own, so editing, diagnostics and the preview all work from one model of the document.]]></summary></entry><entry><title type="html">New themes for the Eclipse IDE</title><link href="https://vogella.com/blog/eclipse-themes/" rel="alternate" type="text/html" title="New themes for the Eclipse IDE" /><published>2026-09-08T00:00:00+00:00</published><updated>2026-09-08T00:00:00+00:00</updated><id>https://vogella.com/blog/eclipse-themes</id><content type="html" xml:base="https://vogella.com/blog/eclipse-themes/"><![CDATA[<p>We have published a set of additional themes for the Eclipse IDE: AI Neon, Dracula, VS Code Dark, GitHub Dark, Nord and One Light.
Each one is a pure resource plug-in, CSS and preference definitions only, installable from an update site.</p>

<p>Every theme builds on the platform stylesheets of <code class="language-plaintext highlighter-rouge">org.eclipse.ui.themes</code> and recolors them through shared color definitions, so a theme change is a palette change rather than a rewritten stylesheet.
A theme also writes its editor, syntax highlighting and console colors into the affected preferences, the same way the built-in dark theme does.</p>

<h2 id="ai-neon">AI Neon</h2>

<p>Near black surfaces with cyan and magenta accents on active elements.</p>

<figure class="">
  <img src="/blog/assets/images/eclipse-theme-ai-neon.png" alt="The Eclipse IDE with the AI Neon theme" /></figure>

<h2 id="dracula">Dracula</h2>

<p>The official Dracula palette on purple tinted surfaces with pink and purple accents.</p>

<figure class="">
  <img src="/blog/assets/images/eclipse-theme-dracula.png" alt="The Eclipse IDE with the Dracula theme" /></figure>

<h2 id="vs-code-dark">VS Code Dark</h2>

<p>The VS Code Dark Modern look, including its tab styling and the Dark+ syntax colors.</p>

<figure class="">
  <img src="/blog/assets/images/eclipse-theme-vscode-dark.png" alt="The Eclipse IDE with the VS Code Dark theme" /></figure>

<p>The screenshots show the same workspace, the same file and the same window, captured on Linux with GTK.
They were taken through our <a href="/blog/eclipse-mcp-server/">MCP server for the Eclipse IDE</a>: an agent switched the theme, restarted the IDE and captured the window, three times in a row.</p>

<h2 id="why-we-build-themes">Why we build themes</h2>

<p>The themes are also our test bed for the theming capabilities of the Eclipse platform itself.
Every palette that does not fit exposes a widget the CSS engine cannot reach, a color that is hard coded somewhere in SWT or JFace, or a preference that is not styled at all.
We fix those in the platform, which benefits every theme and every Eclipse RCP application.</p>

<p>If your RCP application needs a modern look or an upgrade to a current Eclipse release, we offer this as a service: <a href="https://vogella.com/services/#eclipse-rcp-upgrade">Eclipse RCP upgrade</a>.</p>

<h2 id="installing">Installing</h2>

<p>In Eclipse, choose <em>Help &gt; Install New Software</em> and add the update site:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://vogellacompany.github.io/eclipse-themes/
</code></pre></div></div>

<p>Every theme is a feature of its own, so install one, several or all six.
Then switch under <em>Preferences &gt; General &gt; Appearance</em>.</p>

<p>Sources and issues: <a href="https://github.com/vogellacompany/eclipse-themes">github.com/vogellacompany/eclipse-themes</a>.
Please open an issue there if a theme renders something wrong on your platform.</p>]]></content><author><name>Lars Vogel</name></author><category term="eclipse" /><summary type="html"><![CDATA[We have published a set of additional themes for the Eclipse IDE: AI Neon, Dracula, VS Code Dark, GitHub Dark, Nord and One Light. Each one is a pure resource plug-in, CSS and preference definitions only, installable from an update site.]]></summary></entry><entry><title type="html">Dead code removal in Eclipse platform and PDE</title><link href="https://vogella.com/blog/dead-code-removal-eclipse-platform-ui/" rel="alternate" type="text/html" title="Dead code removal in Eclipse platform and PDE" /><published>2026-08-28T00:00:00+00:00</published><updated>2026-08-28T00:00:00+00:00</updated><id>https://vogella.com/blog/dead-code-removal-eclipse-platform-ui</id><content type="html" xml:base="https://vogella.com/blog/dead-code-removal-eclipse-platform-ui/"><![CDATA[<p>Every large Java code base carries code nobody calls any more.
For future maintenance it is important to remove such dead code: every unused class still has to be read by the next developer, still shows up in searches and refactorings, still compiles against APIs that keep changing, and still needs to be migrated when the platform moves on, for example to a new Java version or a new SWT API.
Finding candidates is easy.
Proving that a class is really dead is the hard part, because references hide in other repositories, in <code class="language-plaintext highlighter-rouge">plugin.xml</code>, in <code class="language-plaintext highlighter-rouge">.e4xmi</code> models and in reflective calls, and a text search cannot see any of that.</p>

<p>We now offer <a href="https://www.vogella.com/services/#dead-code-removal">dead code removal</a> as a fixed-scope service for Eclipse RCP and Java code bases.
This post shows what it found in the Eclipse Platform, Platform UI and PDE repositories.</p>

<h2 id="example-eclipse-platform-platform-ui-and-pde">Example: Eclipse Platform, Platform UI and PDE</h2>

<p>As a dry run we cleaned the internal API of three Eclipse repositories, <code class="language-plaintext highlighter-rouge">eclipse.platform</code>, <code class="language-plaintext highlighter-rouge">eclipse.platform.ui</code> and <code class="language-plaintext highlighter-rouge">eclipse.pde</code>, with the complete Eclipse SDK open in the workspace so that references from other projects count.
The sweep removed 12,676 lines in 147 files: 126 classes, among them a complete About dialog package that was never wired to anything, plus the message keys and package exports that only served them.
The workspace showed no additional warning or error afterwards, and a full build of the Eclipse SDK aggregator passed.</p>

<figure class="">
  <img src="/blog/assets/images/dead-code-platform-ui.svg" alt="Bar chart of dead code lines removed per bundle of the Eclipse Platform, Platform UI and PDE" /></figure>

<p>The safety net mattered as much as the deletions.
Dozens of classes had no reference inside their own repository but were used from other Eclipse projects.
Two classes are referenced only from another plug-in’s <code class="language-plaintext highlighter-rouge">plugin.xml</code>, through their nested classes; deleting them would compile and fail at runtime.
A few e4 addons exist only through <code class="language-plaintext highlighter-rouge">bundleclass://</code> URIs in <code class="language-plaintext highlighter-rouge">.e4xmi</code> files.
All of them were kept, and public API was excluded from the start, because zero references in one workspace proves nothing for an exported package.</p>

<h2 id="interested">Interested?</h2>

<p>Details and a contact form: <a href="https://www.vogella.com/services/#dead-code-removal">Dead code removal</a>.</p>]]></content><author><name>Lars Vogel</name></author><category term="eclipse" /><category term="java" /><summary type="html"><![CDATA[Every large Java code base carries code nobody calls any more. For future maintenance it is important to remove such dead code: every unused class still has to be read by the next developer, still shows up in searches and refactorings, still compiles against APIs that keep changing, and still needs to be migrated when the platform moves on, for example to a new Java version or a new SWT API. Finding candidates is easy. Proving that a class is really dead is the hard part, because references hide in other repositories, in plugin.xml, in .e4xmi models and in reflective calls, and a text search cannot see any of that.]]></summary></entry><entry><title type="html">An MCP server for the Eclipse IDE</title><link href="https://vogella.com/blog/eclipse-mcp-server/" rel="alternate" type="text/html" title="An MCP server for the Eclipse IDE" /><published>2026-08-20T00:00:00+00:00</published><updated>2026-08-20T00:00:00+00:00</updated><id>https://vogella.com/blog/eclipse-mcp-server</id><content type="html" xml:base="https://vogella.com/blog/eclipse-mcp-server/"><![CDATA[<p>We have released an <a href="https://modelcontextprotocol.io">MCP</a> server for the Eclipse IDE.
It lets external LLM clients such as Claude Code or Cursor ask a running IDE for things they cannot cheaply reconstruct from files alone.</p>

<p>An agent with a shell already has files, grep and git.
What it does not have is the resolved Java model, the problem markers of the incremental builder and the file you are currently looking at.
Those are the capabilities exposed here.</p>

<p>It exposes the JDT search engine for references and type hierarchies, the source of types from jars, the compilation errors of the workspace, the entries of the Error Log including the stack traces of UI freezes, the preferences with the scope each value comes from, and the active editor with its cursor and selection.
A few tools write: organize imports, format, run a build, open and close projects, and set the execution environment of plug-in projects together with the matching compiler settings.
There is no general file writing, no refactoring, no terminal and no debugger control.</p>

<p>The server is disabled by default, binds to the loopback interface only and rejects every request without a bearer token.
Enable it in <em>Preferences &gt; General &gt; MCP Server</em>.</p>

<p>Install it from the update site:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://vogellacompany.github.io/eclipse-mcp-server/
</code></pre></div></div>

<p>Sources, documentation and issues: <a href="https://github.com/vogellacompany/eclipse-mcp-server">github.com/vogellacompany/eclipse-mcp-server</a>.
Please open an issue there if something does not work or if a tool you need is missing.</p>

<p>For help, support or commercial enquiries, write to <a href="mailto:info@vogella.com">info@vogella.com</a>.</p>]]></content><author><name>Lars Vogel</name></author><category term="eclipse" /><summary type="html"><![CDATA[We have released an MCP server for the Eclipse IDE. It lets external LLM clients such as Claude Code or Cursor ask a running IDE for things they cannot cheaply reconstruct from files alone.]]></summary></entry><entry><title type="html">Visual Studio Code GitHub Copilot vs. Eclipse Theia AI</title><link href="https://vogella.com/blog/vscode_copilot_theia_ai_comparison/" rel="alternate" type="text/html" title="Visual Studio Code GitHub Copilot vs. Eclipse Theia AI" /><published>2026-03-31T00:00:00+00:00</published><updated>2026-03-31T00:00:00+00:00</updated><id>https://vogella.com/blog/vscode_copilot_theia_ai_comparison</id><content type="html" xml:base="https://vogella.com/blog/vscode_copilot_theia_ai_comparison/"><![CDATA[<p>If you plan to build applications with AI support, especially for software development, you first need to decide which platform to use. You can build a plain web application from scratch and implement everything yourself, or build on top of an existing platform. In my previous blog posts, I explained how to customize the AI experience in Visual Studio Code and how to create a custom AI extension in Eclipse Theia. While writing those posts, I noticed many similarities, both from a user perspective and from an extension-development perspective. In this post, I compare both approaches to give you a clearer view of where they align and where they differ.
If you have not read my previous blog posts and want more details, have a look at:</p>

<ul>
  <li><a href="https://vogella.com/blog/vscode_copilot_extension/">Extending Copilot in Visual Studio Code</a></li>
  <li><a href="https://vogella.com/blog/theia_ai_getting_started/">Getting Started with Theia AI</a></li>
</ul>

<p><em><strong>Note:</strong></em><br />
This blog post is based on Visual Studio Code 1.113.0 and Eclipse Theia 1.70.0. There may be differences if you read it when newer versions have been released.</p>

<h2 id="strategic-comparison">Strategic Comparison</h2>

<p>In the article <a href="https://blogs.eclipse.org/post/thomas-froment/why-extending-github-copilot-vs-code-may-not-be-best-fit-your-ai-native">Why Extending GitHub Copilot in VS Code May Not Be the Best Fit for Your AI-Native Development Tool</a>, you can already find a comprehensive overview of the differences between GitHub Copilot in Visual Studio Code and Theia AI. The article is around half a year old and is therefore not fully up to date with the latest features. However, the strategic perspective is still well described and remains valid, so the article is still worth reading if you plan to adopt either technology.</p>

<p>The following table extracts and summarizes an initial comparison of key aspects from that article:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>VS Code GitHub Copilot</th>
      <th>Eclipse Theia AI</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Platform</td>
      <td>Product you can extend</td>
      <td>Platform you can own</td>
    </tr>
    <tr>
      <td>License</td>
      <td>Mixture of open source parts (MIT) and proprietary parts (Microsoft products)</td>
      <td>Completely open source (EPL)</td>
    </tr>
    <tr>
      <td>AI customization options</td>
      <td>Chat only</td>
      <td>any part of the application</td>
    </tr>
    <tr>
      <td>Licensing</td>
      <td>Copilot subscription required</td>
      <td>-</td>
    </tr>
    <tr>
      <td>LLM Support</td>
      <td>Depends on Copilot subscription<br /><a href="https://code.visualstudio.com/docs/copilot/customization/language-models">AI language models in VS Code</a></td>
      <td>Depends on existing providers<br /><a href="https://theia-ide.org/docs/user_ai/#llm-providers-overview">LLM Providers Overview</a></td>
    </tr>
  </tbody>
</table>

<h2 id="ai-extension-comparison">AI Extension Comparison</h2>

<p>Visual Studio Code GitHub Copilot extensions and Eclipse Theia AI extensions target similar use cases: extending the AI capabilities of the IDE.
There are differences in terminology, implementation, extension capabilities, and features. In previous blog posts, I explained how to contribute <em>Tools</em>, <em>MCP Servers</em>, and <em>Chat Extensions</em>. The concepts already differ in naming, as shown in the following table:</p>

<table>
  <thead>
    <tr>
      <th>VS Code GitHub Copilot</th>
      <th>Eclipse Theia AI</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Language Model Tool</td>
      <td>Tool Function</td>
    </tr>
    <tr>
      <td>MCP</td>
      <td>MCP</td>
    </tr>
    <tr>
      <td>Chat Participant</td>
      <td>Agent</td>
    </tr>
  </tbody>
</table>

<p>The following sections will explain the differences in more detail.</p>

<h3 id="language-model-tool-vs-tool-function">Language Model Tool vs. Tool Function</h3>

<p><em>Tools</em> provide additional capabilities to perform specialized tasks when interacting with an LLM. In Visual Studio Code, they are called <a href="https://code.visualstudio.com/api/extension-guides/ai/tools"><em>Language Model Tools</em></a>; in Theia, they are called <a href="https://theia-ide.org/docs/theia_ai/#tool-functions"><em>Tool Functions</em></a>.</p>

<h4 id="visual-studio-code">Visual Studio Code</h4>

<p>To contribute a <em>Language Model Tool</em> via Copilot Extension, you follow the typical contribution pattern in Visual Studio Code Extensions:</p>

<ul>
  <li>Configure the contribution via <code class="language-plaintext highlighter-rouge">contributes/languageModelTools</code> section in the extensions <em>package.json</em></li>
  <li>Implement a new class that implements the <code class="language-plaintext highlighter-rouge">vscode.LanguageModelTool</code> interface</li>
  <li>Register it in the extension via <code class="language-plaintext highlighter-rouge">vscode.lm.registerTool()</code></li>
</ul>

<p>Further details can be found in <a href="https://vogella.com/blog/vscode_copilot_extension/#language-model-tool">Extending Copilot in Visual Studio Code - Language Model Tool</a>.</p>

<h4 id="eclipse-theia">Eclipse Theia</h4>

<p>To contribute a <em>Tool Function</em> via Theia Extension, you follow the typical contribution pattern in Theia:</p>

<ul>
  <li>Implement a new class that implements the <code class="language-plaintext highlighter-rouge">ToolProvider</code> interface from the <code class="language-plaintext highlighter-rouge">@theia/ai-core</code> package</li>
  <li>Register it in a <code class="language-plaintext highlighter-rouge">ContainerModule</code> for injection via <code class="language-plaintext highlighter-rouge">bindToolProvider()</code> from <code class="language-plaintext highlighter-rouge">@theia/ai-core/lib/common</code></li>
</ul>

<p>Further details can be found in <a href="https://vogella.com/blog/theia_ai_getting_started/#tool-functions">Getting Started with Theia AI - Tool Functions</a>.</p>

<h3 id="mcp-support">MCP Support</h3>

<p><em>MCP Servers</em> are typically configured by users via a configuration file. The usage comparison is described later in <a href="#configuring-mcp-servers">Configuring MCP Servers</a>. <em>MCP Servers</em> can also be contributed and configured programmatically, for example when you contribute an extension that provides an advanced AI use case requiring dedicated tools via MCP.</p>

<p><em><strong>Note:</strong></em><br />
MCP servers that are programmatically registered via a Visual Studio Code Copilot extension can also be installed in a Theia application if the <code class="language-plaintext highlighter-rouge">@theia/plugin-ext</code> extension is available.</p>

<h4 id="visual-studio-code-1">Visual Studio Code</h4>

<p>To contribute a <em>MCP Server</em> via Copilot Extension, you follow the typical contribution pattern in Visual Studio Code Extensions:</p>

<ul>
  <li>Configure the contribution via <code class="language-plaintext highlighter-rouge">contributes/mcpServerDefinitionProviders</code> section in the extensions <em>package.json</em></li>
  <li>Register a <code class="language-plaintext highlighter-rouge">McpServerDefinitionProvider</code> in the extension via <code class="language-plaintext highlighter-rouge">vscode.lm.registerMcpServerDefinitionProvider()</code></li>
  <li>Add MCP servers via <code class="language-plaintext highlighter-rouge">McpServerDefinitionProvider#provideMcpServerDefinitions()</code>
    <ul>
      <li>A local MCP server can be configured by creating a <a href="https://code.visualstudio.com/api/references/vscode-api#McpStdioServerDefinition"><code class="language-plaintext highlighter-rouge">vscode.McpStdioServerDefinition</code></a></li>
      <li>A remote MCP server can be configured by creating a <a href="https://code.visualstudio.com/api/references/vscode-api#McpHttpServerDefinition"><code class="language-plaintext highlighter-rouge">vscode.McpHttpServerDefinition</code></a></li>
    </ul>
  </li>
  <li>Dynamic resolution (e.g. asking the user for an authorization token) can be added via <code class="language-plaintext highlighter-rouge">McpServerDefinitionProvider#resolveMcpServerDefinition()</code></li>
</ul>

<p>There are some limitations:</p>

<ul>
  <li>To ensure that the MCP servers are registered automatically on start, you need to configure the <code class="language-plaintext highlighter-rouge">activationEvents</code> accordingly, e.g. <code class="language-plaintext highlighter-rouge">onStartupFinished</code></li>
  <li>There is no API to programmatically start or stop an <em>MCP Server</em> in Visual Studio Code.</li>
  <li>Programmatically registered MCP servers do not show up in the <em>MCP Servers</em> section of the <em>Extensions</em> view.</li>
  <li>Programmatically registered MCP servers do not get <a href="https://modelcontextprotocol.info/docs/concepts/roots/">roots</a> set based on the current workspace directory.</li>
</ul>

<p>Further details can be found in <a href="https://vogella.com/blog/vscode_copilot_extension/#add-mcp-server-programmatically-via-vs-code-extension">Extending Copilot in Visual Studio Code - Add MCP server programmatically via VS Code Extension</a>.</p>

<h4 id="eclipse-theia-1">Eclipse Theia</h4>

<p>To contribute a <em>MCP Server</em> via Theia Extension, you follow the typical contribution pattern in Theia:</p>

<ul>
  <li>Implement a <code class="language-plaintext highlighter-rouge">FrontendApplicationContribution</code></li>
  <li>Get the <code class="language-plaintext highlighter-rouge">MCPFrontendService</code> injected</li>
  <li>Add MCP servers via <code class="language-plaintext highlighter-rouge">MCPFrontendService#addOrUpdateServer()</code>
    <ul>
      <li>A local MCP server can be configured by creating a <code class="language-plaintext highlighter-rouge">LocalMCPServerDescription</code></li>
      <li>A remote MCP server can be configured by creating a <code class="language-plaintext highlighter-rouge">RemoteMCPServerDescription</code></li>
    </ul>
  </li>
  <li>Dynamic resolution (e.g. asking the user for an authorization token) can be added directly to the <code class="language-plaintext highlighter-rouge">MCPServerDescription</code> via the <code class="language-plaintext highlighter-rouge">MCPServerDescription#resolve()</code> function</li>
  <li>Register it in a <code class="language-plaintext highlighter-rouge">ContainerModule</code> via <code class="language-plaintext highlighter-rouge">bind()</code></li>
</ul>

<p>Compared to the limitations in Visual Studio Code, programmatically registered MCP servers in Theia</p>

<ul>
  <li>are automatically registered if the <code class="language-plaintext highlighter-rouge">FrontendApplicationContribution</code> is registered correctly in a <code class="language-plaintext highlighter-rouge">ContainerModule</code></li>
  <li>can be started and stopped programmatically via <code class="language-plaintext highlighter-rouge">MCPFrontendService#startServer()</code> and <code class="language-plaintext highlighter-rouge">MCPFrontendService#stopServer()</code></li>
  <li>do show up in the <em>AI Configuration</em> view</li>
  <li>Since Theia 1.69.0, <a href="https://modelcontextprotocol.info/docs/concepts/roots/">roots</a> are supported. This support was added via <a href="https://github.com/eclipse-theia/theia/pull/16911">PR</a>. You can configure whether the workspace should be used as a <em>Root</em> via the preference <code class="language-plaintext highlighter-rouge">ai-features.mcp.useWorkspaceAsRoot</code>.</li>
</ul>

<p>Further details can be found in <a href="https://vogella.com/blog/theia_ai_getting_started/#add-mcp-server-programmatically-via-theia-extension">Getting Started with Theia AI - Add MCP server programmatically via Theia Extension</a>.</p>

<h3 id="chat-participant-vs-custom-agent">Chat Participant vs. Custom Agent</h3>

<p>To extend the AI capabilities of an IDE or application with a specialized assistant that provides domain-specific expert knowledge, you can implement and contribute a <em>Chat Participant</em> in Visual Studio Code or a <em>Custom Agent</em> in Eclipse Theia. In both cases, the benefits of such a programmatically created AI assistant are:</p>

<ul>
  <li>you can manage the end-to-end prompt/response conversation</li>
  <li>you have access to the respective API, which allows deep integration with the underlying platform</li>
  <li>you can distribute and deploy it via extensions</li>
</ul>

<p><em>Custom Agents</em> defined via configuration files can only interact with the underlying platform if the required functionality is available as a <em>Language Model Tool</em> or <em>Tool Function</em>, or via <em>MCP</em>. They are also available only when the corresponding configuration files are present in the user’s workspace. <em>Custom Agents</em> from a user’s perspective are <a href="#custom-agents">covered in a later section</a>. In this section, I focus on the differences in programmatically created AI extensions.</p>

<h4 id="visual-studio-code-2">Visual Studio Code</h4>

<p>As the name implies, a <em>Chat Participant</em> contributed via a Visual Studio Code Copilot extension is intended for and limited to chat. There are no other use cases where a <em>Chat Participant</em> can be integrated, such as the terminal.</p>

<p>To contribute a <em>Chat Participant</em> via Copilot Extension, you follow the typical contribution pattern in Visual Studio Code Extensions:</p>

<ul>
  <li>Configure the contribution via <code class="language-plaintext highlighter-rouge">contributes/chatParticipants</code> section in the extensions <em>package.json</em></li>
  <li>Define a <code class="language-plaintext highlighter-rouge">vscode.ChatRequestHandler</code> which sends the chat request and handles the response</li>
  <li>Create a <code class="language-plaintext highlighter-rouge">vscode.ChatParticipant</code> via <code class="language-plaintext highlighter-rouge">vscode.chat.createChatParticipant()</code> by using the id defined in the <em>package.json</em> and the defined <code class="language-plaintext highlighter-rouge">vscode.ChatRequestHandler</code></li>
  <li>Register the <code class="language-plaintext highlighter-rouge">vscode.ChatParticipant</code> in the extension by pushing it to the <code class="language-plaintext highlighter-rouge">context.subscriptions</code></li>
</ul>

<p>To use the <em>Chat Participant</em>, you need to invoke it with the <code class="language-plaintext highlighter-rouge">@</code> syntax, e.g. <code class="language-plaintext highlighter-rouge">@joker tell me a joke</code>.</p>

<p>Further details can be found in <a href="https://vogella.com/blog/vscode_copilot_extension/#chat-participant">Extending Copilot in Visual Studio Code - Chat Participant</a>.</p>

<h4 id="eclipse-theia-2">Eclipse Theia</h4>

<p>Compared to Visual Studio Code, a <em>Custom Agent</em> in Theia is not limited to the chat. It can also be integrated into other parts of the IDE, such as the editor, the terminal, or a custom widget.</p>

<p>To contribute a <em>Custom Agent</em> via a Theia Extension, you follow the typical contribution pattern in Theia:</p>

<ul>
  <li>Implement an <code class="language-plaintext highlighter-rouge">AbstractStreamParsingChatAgent</code> and define the necessary attributes such as <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">name</code>, <code class="language-plaintext highlighter-rouge">description</code>, prompt(s), <em>Tool Functions</em>, and <em>Agent-Specific Variables</em>, if needed.</li>
  <li>Register it in a <code class="language-plaintext highlighter-rouge">ContainerModule</code> via <code class="language-plaintext highlighter-rouge">bind()</code></li>
</ul>

<p>When implementing a <em>Custom Agent</em>, there are several advanced features that can improve the user experience, for example:</p>

<ul>
  <li><em>Prompt Variants</em></li>
  <li><em>Agent-specific Variables</em></li>
  <li><em>Agent-to-Agent Delegation</em></li>
  <li><em>Custom Response Part Rendering</em></li>
</ul>

<p>To use a <em>Custom Agent</em> in Theia, you need to invoke it with the <code class="language-plaintext highlighter-rouge">@</code> syntax, e.g. <code class="language-plaintext highlighter-rouge">@Joker a joke about scarecrow</code>.</p>

<p>Further details can be found in <a href="https://vogella.com/blog/theia_ai_getting_started/#implement-a-custom-agent">Getting Started with Theia AI - Implement a Custom Agent</a>.</p>

<h2 id="ai-usage-comparison">AI Usage Comparison</h2>

<p>The sections above covered differences in programmatic AI customization. In the following sections, I compare AI usage differences from a user’s point of view.</p>

<h3 id="using-tools-in-prompts">Using tools in prompts</h3>

<p>How tools are used in prompts differs between Visual Studio Code and Theia. In general, there are three types of tools:</p>

<ul>
  <li>Built-in tools provided by the platform</li>
  <li>MCP tools added via MCP servers</li>
  <li>Extension tools provided programmatically (<em>Language Model Tool</em> vs. <em>Tool Function</em>)</li>
</ul>

<h4 id="visual-studio-code-3">Visual Studio Code</h4>

<p>In Visual Studio Code, a <em>Tool</em> is used when processing a prompt if:</p>

<ul>
  <li>the tool is in the list of enabled tools and is selected automatically
    <figure class="">
<img src="/blog/assets/images/copilot_tools_configuration.png" alt="" /></figure>
  </li>
  <li>the tool is used explicitly by mentioning it using a leading <code class="language-plaintext highlighter-rouge">#</code>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#jokeFileCreator create a file that contains a joke in the folder test
</code></pre></div>    </div>
  </li>
</ul>

<p>For tools provided via MCP, the tool is also selected by name. Assuming you added the <em>fetch MCP server</em>, you can use the provided tool via <code class="language-plaintext highlighter-rouge">#fetch</code>. Unfortunately, this is not unique because there is also a built-in <code class="language-plaintext highlighter-rouge">fetch</code> tool in Visual Studio Code.</p>

<p>By default, you will be prompted before a <em>Language Model Tool</em> is executed.</p>

<figure class="">
  <img src="/blog/assets/images/copilot_language_model_tool_allow.png" alt="" /></figure>

<p>In the <em>Allow</em> dropdown, you can choose whether to allow execution once, for the current session, or generally in the current workspace. This setting is not directly accessible at the time of writing this blog post. To reset saved tool approvals, run the <em>Chat: Reset Tool Confirmations</em> command from the Command Palette (<code class="language-plaintext highlighter-rouge">Ctrl+Shift+P</code>).</p>

<p>In Visual Studio Code, it is also possible to configure <a href="https://code.visualstudio.com/docs/copilot/agents/agent-tools#_url-approval">URL approval</a>, which becomes active when a tool attempts to access a URL.</p>

<p>Further details about using tools in chat in Visual Studio Code can be found in <a href="https://code.visualstudio.com/docs/copilot/agents/agent-tools">Use tools with agents</a>.</p>

<h4 id="eclipse-theia-3">Eclipse Theia</h4>

<p>In Eclipse Theia, a <em>Tool</em> is used by mentioning it with a leading <code class="language-plaintext highlighter-rouge">~</code>. There is no automatic tool resolution for prompts, so you always need to mention the tool explicitly.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal create a file that contains a joke in the folder test. Use a file name that relates to the joke. ~jokeFileCreator
</code></pre></div></div>

<p>The name of an MCP server tool in Theia is derived from the server name and function name. It uses the following syntax:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~{mcp_&lt;server-name&gt;_&lt;function-name&gt;}
</code></pre></div></div>

<p>For example, to use the <code class="language-plaintext highlighter-rouge">fetch</code> function of the <code class="language-plaintext highlighter-rouge">fetch</code> MCP server, you could write a prompt like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Universal show me the allowed directories ~{mcp_fetch_fetch}
</code></pre></div></div>

<p>By default, the <em>Tool Confirmation Mode</em> is <strong>Always Allow</strong>. Users can change this setting.</p>

<ul>
  <li>Open the <em>AI Configuration</em> view via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
  <li>Switch to the <em>Tools</em> tab
    <figure class="">
<img src="/blog/assets/images/theia_tools_configuration_confirmation.png" alt="" /></figure>
  </li>
  <li>Alternatively, edit <em>settings.json</em> and configure <code class="language-plaintext highlighter-rouge">ai-features.chat.toolConfirmation</code>. For example, if you want to be prompted for approval for every tool call but allow <code class="language-plaintext highlighter-rouge">jokeFileCreator</code> to execute without confirmation:
    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"ai-features.chat.toolConfirmation"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"*"</span><span class="p">:</span><span class="w"> </span><span class="s2">"confirm"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"jokeFileCreator"</span><span class="p">:</span><span class="w"> </span><span class="s2">"always_allow"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div>    </div>
  </li>
</ul>

<p>The <strong>URL approval feature</strong> is currently not supported in Eclipse Theia.</p>

<p>Further details about using tools in chat in Eclipse Theia can be found in <a href="https://theia-ide.org/docs/theia_ai/#tool-functions">Tool Functions</a>, <a href="https://theia-ide.org/docs/user_ai/#using-mcp-server-functions">Using MCP Server Functions</a>, and <a href="https://theia-ide.org/docs/user_ai/#tool-call-confirmation-ui">Tool Call Confirmation UI</a>.</p>

<h3 id="configuring-mcp-servers">Configuring MCP Servers</h3>

<p>As a user, you typically add MCP servers to your development environment via a configuration file. There are two basic types of MCP servers:</p>

<ul>
  <li>local MCP servers, which are software installed on your system</li>
  <li>remote MCP servers, which are hosted elsewhere and provide functionality without needing local system access.</li>
</ul>

<h4 id="visual-studio-code-4">Visual Studio Code</h4>

<p>In Visual Studio Code, you typically add MCP servers via an <em>mcp.json</em> file, either in the workspace <em>.vscode</em> folder or in the user profile to configure MCP servers for all workspaces.
A <em>mcp.json</em> file has two main sections:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">"servers": {}</code> - Contains the list of MCP servers and their configurations</li>
  <li><code class="language-plaintext highlighter-rouge">"inputs": []</code> - Optional placeholders for sensitive information like API keys</li>
</ul>

<p>You can use predefined variables in the <code class="language-plaintext highlighter-rouge">servers</code> section and the variables defined in the <code class="language-plaintext highlighter-rouge">inputs</code> section.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"servers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"filesystem"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stdio"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"-y"</span><span class="p">,</span><span class="w"> </span><span class="s2">"@modelcontextprotocol/server-filesystem"</span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"fetch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://remote.mcpservers.org/fetch/mcp"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sse"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"X-MCP-Toolsets"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gists"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"Authorization"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Bearer ${env:GITHUB_TOKEN}"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"inputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>From the example above, you can see that you need to specify the <code class="language-plaintext highlighter-rouge">type</code> field to define whether it is a local or a remote MCP server.</p>

<p>You can manage the MCP server either via</p>

<ul>
  <li><strong>MCP SERVERS - INSTALLED</strong> section in the <em>Extensions</em> view
    <figure class="">
<img src="/blog/assets/images/vscode_mcp_servers.png" alt="" /></figure>
  </li>
  <li>Inline actions in the <em>mcp.json</em> editor (codelenses)
    <figure class="">
<img src="/blog/assets/images/mcp_codelens.png" alt="" /></figure>
  </li>
  <li><strong>MCP: List Servers</strong> command from the Command Palette
    <figure class="">
<img src="/blog/assets/images/vscode_mcp_servers_command.png" alt="" /></figure>
  </li>
</ul>

<p>After the first start of an MCP server, the server’s capabilities and tools are discovered and cached. The next time a tool is used in a prompt while the server is not running yet, Visual Studio Code can auto-start the server because of this cached information.</p>

<p>Using the <code class="language-plaintext highlighter-rouge">chat.mcp.autostart</code> setting, you can configure whether MCP servers should be restarted automatically when configuration changes are detected. This autostart setting is available only globally, not per server.</p>

<p>Further information can be found in <a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers">Use MCP servers in VS Code</a>.</p>

<h4 id="eclipse-theia-4">Eclipse Theia</h4>

<p>In Eclipse Theia, you typically add MCP servers via the general <em>settings.json</em> file. There is currently no support for an <em>mcp.json</em> file.</p>

<p>The <em>settings.json</em> file contains many settings that can be applied in Theia, not just MCP-related configuration. There is no support for predefined variables or inputs like the <code class="language-plaintext highlighter-rouge">inputs</code> section in <em>mcp.json</em>.</p>

<p>Comparing Visual Studio Code <em>mcp.json</em> and Eclipse Theia <em>settings.json</em> MCP server configuration, there are a few differences:</p>

<ul>
  <li>In Theia, you do not need to specify the <code class="language-plaintext highlighter-rouge">type</code> field.<br />
For local servers, this is straightforward because only one type is available (<code class="language-plaintext highlighter-rouge">stdio</code>). For remote servers, Theia first tries to connect via <code class="language-plaintext highlighter-rouge">sse</code>, and if that fails, it falls back to <code class="language-plaintext highlighter-rouge">http</code>.</li>
  <li>In Eclipse Theia, there is an <code class="language-plaintext highlighter-rouge">autostart</code> field that lets you configure whether an MCP server should start automatically. The default is <code class="language-plaintext highlighter-rouge">true</code> if it is not set.</li>
  <li>For remote servers, the main URL is set as <code class="language-plaintext highlighter-rouge">url</code> in Visual Studio Code, while in Eclipse Theia the field is called <code class="language-plaintext highlighter-rouge">serverUrl</code>.</li>
  <li>In Eclipse Theia, you can specify the authentication token via <code class="language-plaintext highlighter-rouge">serverAuthToken</code>, which defaults to an <em>Authorization</em> header with <em>Bearer</em>. You can change that via <code class="language-plaintext highlighter-rouge">serverAuthTokenHeader</code>.</li>
</ul>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"ai-features.mcp.mcpServers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"filesystem"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"npx"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"-y"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"@modelcontextprotocol/server-filesystem"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"/home/node/examples"</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"autostart"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"fetch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://remote.mcpservers.org/fetch/mcp"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"autostart"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"serverAuthToken"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;your-token&gt;"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"X-MCP-Toolsets"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gists"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You can manage the MCP server either via</p>

<ul>
  <li><em>AI Configuration</em> view: <em>Menu</em> -&gt; <em>View</em> -&gt; <em>AI Configuration</em> -&gt; <em>MCP Servers</em> tab
    <figure class="">
<img src="/blog/assets/images/theia_mcp_filesystem.png" alt="" /></figure>
  </li>
  <li>Command palette: <em>F1</em>
    <ul>
      <li><em>MCP: Start MCP Server -&gt; filesystem</em></li>
      <li><em>MCP: Stop MCP Server -&gt; filesystem</em></li>
    </ul>
  </li>
</ul>

<p>If an MCP server is not started, its capabilities and tools cannot be used. There is no auto-start based on cached information as in Visual Studio Code. However, you can configure auto-start behavior per server via the <code class="language-plaintext highlighter-rouge">autostart</code> field, which is enabled by default.</p>

<p>Further information can be found in <a href="https://theia-ide.org/docs/user_ai/#mcp-integration">MCP Integration</a>.</p>

<h3 id="chat-variables">Chat Variables</h3>

<p>In Visual Studio Code and Eclipse Theia, it is possible, and usually recommended, to add specific context to your request. To do this explicitly, you can use the <code class="language-plaintext highlighter-rouge">#</code> syntax with some predefined options.</p>

<p>In Theia, it is also possible to implement and provide additional <em>Context Variables</em> as <em>Global Variables</em> or <em>Agent-Specific Variables</em>.</p>

<p>Further information for Visual Studio Code can be found in <a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat#_add-context-to-your-prompts">Add context to your prompts</a>.</p>

<p>Further information for Eclipse Theia can be found in <a href="https://theia-ide.org/docs/user_ai/#context-variables">Context Variables</a>.</p>

<h3 id="prompt-files-vs-prompt-fragments">Prompt Files vs Prompt Fragments</h3>

<p>Visual Studio Code and Eclipse Theia both allow users to define reusable prompts for recurring development tasks. Although the feature is similar, there are differences.</p>

<h4 id="visual-studio-code-5">Visual Studio Code</h4>

<p>In Visual Studio Code, you can create and use <a href="https://code.visualstudio.com/docs/copilot/customization/prompt-files">Prompt Files</a> to define reusable prompts for recurring development tasks.</p>

<p><em>Prompt Files</em> are Markdown files with a <em>.prompt.md</em> file extension. They are located either in the <em>.github/prompts</em> folder in the workspace or in the <em>prompts</em> folder in the user profile (for example on Windows, <em>C:\Users\&lt;username&gt;\AppData\Roaming\Code\User\prompts</em>).</p>

<p>In the optional YAML frontmatter header, the prompt behavior can be configured, for example:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">agent</code> header to define the agent that should be used for running the prompt</li>
  <li><code class="language-plaintext highlighter-rouge">tools</code> header to define the list of tools or tool sets that are available for the prompt</li>
</ul>

<p>You can use variables in the prompt file using the syntax <code class="language-plaintext highlighter-rouge">${variableName}</code>. The following variables can be used:</p>

<ul>
  <li>Workspace variables - <code class="language-plaintext highlighter-rouge">${workspaceFolder}</code>, <code class="language-plaintext highlighter-rouge">${workspaceFolderBasename}</code></li>
  <li>Selection variables - <code class="language-plaintext highlighter-rouge">${selection}</code>, <code class="language-plaintext highlighter-rouge">${selectedText}</code></li>
  <li>File context variables - <code class="language-plaintext highlighter-rouge">${file}</code>, <code class="language-plaintext highlighter-rouge">${fileBasename}</code>, <code class="language-plaintext highlighter-rouge">${fileDirname}</code>, <code class="language-plaintext highlighter-rouge">${fileBasenameNoExtension}</code></li>
  <li>Input variables - <code class="language-plaintext highlighter-rouge">${input:variableName}</code>, <code class="language-plaintext highlighter-rouge">${input:variableName:placeholder}</code></li>
</ul>

<p>Arguments passed in chat via <em>key=value</em> syntax (e.g. <code class="language-plaintext highlighter-rouge">_target=robin</code>) are available in the prompt via <code class="language-plaintext highlighter-rouge">${input:variableName}</code> or <code class="language-plaintext highlighter-rouge">${input:variableName:placeholder}</code>.</p>

<p><em>Tools</em> configured as available in the prompt via the <code class="language-plaintext highlighter-rouge">tools</code> frontmatter header can be referenced in the prompt via <code class="language-plaintext highlighter-rouge">#tool:&lt;tool-name&gt;</code>.</p>

<p>To use a <em>Prompt File</em> in the Chat view, type <em>/</em> followed by the prompt name. For example, if you created a <em>harley.prompt.md</em> file, you could execute it via the slash command <em>/harley</em>.</p>

<p>Further information can be found in <a href="https://vogella.com/blog/vscode_copilot_extension/#prompt-files">Extending Copilot in Visual Studio Code - Further Customization - Prompt Files</a>.</p>

<h4 id="eclipse-theia-5">Eclipse Theia</h4>

<p>In Eclipse Theia, you can create and use <a href="https://theia-ide.org/docs/user_ai/#prompt-fragments">Prompt Fragments</a> to define reusable prompts for recurring development tasks.</p>

<p><em>Prompt Fragments</em> are Markdown files with a <em>.prompttemplate</em> file extension. They are located either in the <em>.prompts</em> folder in the workspace or in user-wide local directories configured in the settings (<em>AI Features -&gt; Prompt Templates</em>).</p>

<p>In the optional YAML frontmatter header, prompts can be configured as a <em>Slash Command</em>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">isCommand</code> header needs to be set to <code class="language-plaintext highlighter-rouge">true</code></li>
  <li><code class="language-plaintext highlighter-rouge">commandName</code> header specifies the name of the command</li>
  <li><code class="language-plaintext highlighter-rouge">commandAgents</code> header can be used to limit the visibility of the command to the specified agents.</li>
</ul>

<p>You can use any available <a href="https://theia-ide.org/docs/user_ai/#context-variables">Context Variable</a> in the prompt.</p>

<p>Command arguments passed via chat can be used in the prompt via <em>Argument placeholders</em> <code class="language-plaintext highlighter-rouge">$ARGUMENTS</code> or <code class="language-plaintext highlighter-rouge">$1</code>, <code class="language-plaintext highlighter-rouge">$2</code>, <code class="language-plaintext highlighter-rouge">$3</code> for arguments by position.</p>

<p>The tools that should be used inside the prompt do not need to be preconfigured in the YAML frontmatter and can be referenced in the prompt via <code class="language-plaintext highlighter-rouge">~&lt;tool-name&gt;</code>.</p>

<p>To use a <em>Prompt Fragment</em> in the Chat view, you can use the special variable <code class="language-plaintext highlighter-rouge">#prompt:promptFragmentID</code>, for example <code class="language-plaintext highlighter-rouge">@Universal #prompt:harley joke about batgirl</code>. When configured as a <em>Slash Command</em>, type <em>/</em> followed by the prompt name, for example <code class="language-plaintext highlighter-rouge">@Universal /harley batgirl</code>.</p>

<p>Further information can be found in <a href="https://vogella.com/blog/theia_ai_getting_started/#prompt-fragments">Getting Started with Theia AI - Further Customization - Prompt Fragments</a> and <a href="https://vogella.com/blog/theia_ai_getting_started/#slash-commands">Getting Started with Theia AI - Further Customization - Slash Commands</a>.</p>

<h3 id="custom-agents">Custom Agents</h3>

<p><em>Custom Agents</em> are a mechanism that allows users to create specialized assistants for specific tasks in the chat. For example, users can create different personas tailored to development roles and tasks such as planning, research, or specialized workflows.</p>

<p><em>Custom Agents</em> are similar to Visual Studio Code <em>Chat Participants</em> or programmatically contributed <em>Custom Agents</em> in Eclipse Theia. However, instead of contributing them programmatically, users define and configure them through dedicated agent files.</p>

<h4 id="visual-studio-code-6">Visual Studio Code</h4>

<p>In Visual Studio Code, <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents">Custom Agents</a> are configured in custom agent files. <em>Custom Agent Files</em> are Markdown files with a <em>.agent.md</em> extension. They are located either in the <em>.github/agents</em> folder in the workspace or in the user profile folder (interestingly, also in the <em>prompts</em> folder). There is one dedicated <em>.agent.md</em> file for each <em>Custom Agent</em>.</p>

<p>Similar to <em>Prompt Files</em>, <em>Custom Agents</em> can be configured via an optional YAML frontmatter header. You can, for example:</p>

<ul>
  <li>select the model that should be used when running the agent prompt via the <code class="language-plaintext highlighter-rouge">model</code> header</li>
  <li>select the tools or tool sets that can be used by the custom agent via the <code class="language-plaintext highlighter-rouge">tools</code> header</li>
  <li>select the agents that are available as subagents in the custom agent via the <code class="language-plaintext highlighter-rouge">agents</code> header</li>
  <li>configure handoffs for orchestrating multi-step workflows via the <code class="language-plaintext highlighter-rouge">handoffs</code> header</li>
</ul>

<p>To use a <em>Custom Agent</em>, select it from the agents dropdown in chat and enter a prompt, for example <code class="language-plaintext highlighter-rouge">joke about alfred</code>.</p>

<figure class="">
  <img src="/blog/assets/images/copilot_select_custom_agent.png" alt="" /></figure>

<p>Further information can be found in <a href="https://vogella.com/blog/vscode_copilot_extension/#custom-agents">Extending Copilot in Visual Studio Code - Further Customization - Custom Agents</a>.</p>

<p>While the usage of agents in Visual Studio Code and Eclipse Theia is quite similar, Visual Studio Code has some features that are currently not available in Eclipse Theia:</p>

<ul>
  <li>You can select an <a href="https://code.visualstudio.com/docs/copilot/agents/overview#_types-of-agents">Agent type</a> to control <em>where</em> and <em>how</em> an agent runs: <a href="https://code.visualstudio.com/docs/copilot/agents/local-agents">local</a>, <a href="https://code.visualstudio.com/docs/copilot/agents/background-agents">background</a>, <a href="https://code.visualstudio.com/docs/copilot/agents/cloud-agents">cloud</a>, or <a href="https://code.visualstudio.com/docs/copilot/agents/third-party-agents">third-party</a>.</li>
  <li>There is a distinction between calling a <a href="https://code.visualstudio.com/docs/copilot/agents/subagents">Subagent</a>, which spawns a child agent within a session to handle a subtask in its own isolated context window, and performing a <a href="https://code.visualstudio.com/docs/copilot/agents/overview#_hand-off-a-session-to-another-agent">Hand off</a>, which transfers a session from one agent type to another while carrying over the conversation history.</li>
</ul>

<p>Further information can be found in <a href="https://code.visualstudio.com/docs/copilot/agents/overview">Using agents in Visual Studio Code</a>.</p>

<h4 id="eclipse-theia-6">Eclipse Theia</h4>

<p>In Eclipse Theia, multiple <a href="https://theia-ide.org/docs/user_ai/#custom-agents">Custom Agents</a> are configured in a single custom agent configuration file. The <em>Custom Agent File</em> is a YAML file named <em>customAgents.yml</em>. It is located either in the <em>.prompts</em> folder in the workspace or in user-wide local directories configured in the settings (<em>AI Features -&gt; Prompt Templates</em>).</p>

<p>Because there is only one file for multiple <em>Custom Agents</em>, there is no frontmatter configuration header. Instead, each <em>Custom Agent</em> configuration has its own attributes. In addition to the obvious fields such as <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">name</code>, <code class="language-plaintext highlighter-rouge">description</code>, and <code class="language-plaintext highlighter-rouge">prompt</code>, you need to specify the model via the <code class="language-plaintext highlighter-rouge">defaultLLM</code> field.</p>

<p>You can use any available <a href="https://theia-ide.org/docs/user_ai/#context-variables">Context Variable</a> in the prompt.</p>

<p>The tools that should be used inside the prompt do not need to be preconfigured in any way. They can be referenced in the prompt via <code class="language-plaintext highlighter-rouge">~&lt;tool-name&gt;</code>.</p>

<p>You can delegate to another agent by using the <code class="language-plaintext highlighter-rouge">delegateToAgent</code> <em>Tool Function</em>.</p>

<p>To use a <em>Custom Agent</em>, you need to use the <code class="language-plaintext highlighter-rouge">@</code> syntax in the chat view, just like for any other agent in Theia, e.g. <code class="language-plaintext highlighter-rouge">@Joker joke about alfred</code>.</p>

<p>If you use a specific agent most of the time and do not want to type the <code class="language-plaintext highlighter-rouge">@</code> syntax every time, you can configure a default agent via the preference <code class="language-plaintext highlighter-rouge">ai-features.chat.defaultChatAgent</code>, for example to use the <em>Universal</em> agent whenever no agent is explicitly mentioned in the chat:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"ai-features.chat.defaultChatAgent": "Universal"
</code></pre></div></div>

<p>Further information can be found in <a href="https://vogella.com/blog/theia_ai_getting_started/#custom-agents">Getting Started with Theia AI - Further Customization - Custom Agents</a> and <a href="https://vogella.com/blog/theia_ai_getting_started/#agent-to-agent-delegation-function">Getting Started with Theia AI - Further Customization - Agent-to-Agent Delegation (function)</a>.</p>

<h3 id="agent-orchestration">Agent orchestration</h3>

<p>When talking about AI agents, we now often refer to <em>Agentic AI workflows</em>, where multiple agents collaborate to achieve complex goals. When creating <em>Custom Agents</em>, you need to consider collaboration options, especially if you design your agents for dedicated tasks rather than having one agent do everything. This is important because you should keep the context of your agent or prompt as small as possible while still providing enough information to achieve the desired results. Creating agents for dedicated tasks with a limited scope or context is similar to the encapsulation principle in object-oriented programming, and likewise, you need to consider processing or orchestration patterns.</p>

<p>Apart from a <em>Single Agent</em> that does all the work alone, you currently have two options:</p>

<ul>
  <li><em>Delegate Pattern</em><br />
Create guided sequential workflows that transition between agents. The agents are called one after the other.</li>
  <li><em>Coordinator and Worker Pattern</em><br />
The main/coordinator agent receives the task, delegates subtasks to subagents, and combines the subagent results into the final result.</li>
</ul>

<p>You can also combine these patterns to have multiple “main” agents that use worker agents for specific tasks. Each “main” agent can delegate to the next “main” agent once it is done, creating a sequential workflow of main tasks.</p>

<p>While the patterns are generally the same, technically there are differences between Visual Studio Code and Eclipse Theia.</p>

<h4 id="visual-studio-code-7">Visual Studio Code</h4>

<p>The <em>Delegate Pattern</em> is supported via <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents#_handoffs">Handoffs</a> when creating <em>Custom Agents</em> in Visual Studio Code.
A handoff is configured in the YAML frontmatter header. The user needs to actively proceed with the handoff by clicking a button in the chat UI.
The token usage with a handoff is similar to having a single agent that processes all the tasks itself, as the whole context is passed from one agent to the next.</p>

<p>The <a href="https://code.visualstudio.com/docs/copilot/agents/subagents#_coordinator-and-worker-pattern">Coordinator and Worker Pattern</a> is supported through <a href="https://code.visualstudio.com/docs/copilot/agents/subagents">Subagents</a>.
To call a subagent, the built-in <code class="language-plaintext highlighter-rouge">agent/runSubagent</code> tool needs to be enabled for the coordinator agent.
Using a subagent means spawning a child agent within a session to handle a subtask in its own isolated context window. This reduces the token usage, as subagents are not called with the whole context compared to a <em>Handoff</em>.</p>

<p>Each subagent call is sequential (the coordinator waits for that call to return), but the coordinator can spawn multiple subagent calls in parallel.</p>

<p>Further information can be found in <a href="https://vogella.com/blog/ai-agent-orchestration/#visual-studio-code">AI Agent Orchestration - Visual Studio Code</a>.</p>

<h4 id="eclipse-theia-7">Eclipse Theia</h4>

<p>Theia does not provide a feature like <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents#_handoffs">Handoffs</a> in Visual Studio Code. Instead, Theia provides the built-in <em>Tool Function</em> <code class="language-plaintext highlighter-rouge">delegateToAgent</code> to support <a href="https://theia-ide.org/docs/user_ai/#agent-to-agent-delegation">Agent-to-Agent Delegation</a>, which is comparable to <a href="https://code.visualstudio.com/docs/copilot/agents/subagents">Subagents</a>. Whether you implement the <em>Delegate Pattern</em> or the <em>Coordinator and Worker Pattern</em> depends on how you use the <code class="language-plaintext highlighter-rouge">delegateToAgent</code> tool in the prompt.</p>

<p>Using a subagent means spawning a child agent within a session to handle a subtask in its own isolated context window. Because Theia has no <em>Handoff</em> concept that forwards processing to another agent, but only the concept of subagents, you do not see the same token-usage difference there.</p>

<p>Each subagent call via <code class="language-plaintext highlighter-rouge">delegateToAgent</code> is sequential (the coordinator waits for that call to return), but the coordinator can spawn multiple subagent calls in parallel.</p>

<p>Further information can be found in <a href="https://vogella.com/blog/ai-agent-orchestration/#eclipse-theia">AI Agent Orchestration - Eclipse Theia</a>.</p>

<h3 id="context-monitoring--token-usage">Context Monitoring / Token Usage</h3>

<p>In Visual Studio Code, it is possible to <a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat-context#_monitor-context-window-usage">monitor context window usage</a>. This helps indicate how much context a conversation uses. Theia does not yet support such context-window monitoring, but this feature has been requested via <a href="https://github.com/eclipse-theia/theia/issues/16779">Context window inspection / analysis command</a>.</p>

<figure class="">
  <img src="/blog/assets/images/copilot_context_window_subagents_codex.png" alt="" /></figure>

<p>In Theia, token usage can be inspected via <em>AI Configuration</em> by switching to the <em>Token Usage</em> tab, at least for LLMs that provide usage metadata for a request. Token usage is accumulated within a session, so you do not get the information per request. Token usage is not persisted and is reset when restarting the application. Visual Studio Code does not support tracking token usage at that level.</p>

<figure class="">
  <img src="/blog/assets/images/theia_token_coordinate.png" alt="" /></figure>

<h3 id="agent-skills">Agent Skills</h3>

<p><a href="https://agentskills.io/home">Agent Skills</a> is a simple, open format for giving agents new capabilities and expertise. Basically, a skill is a folder containing a <em>SKILL.md</em> file that includes metadata in the form of a YAML frontmatter header and instructions that tell an agent how to perform a specific task. It can also contain subfolders with scripts, templates, and referenced materials. Because it is an open format, it is supported in various AI tools.</p>

<p><em>Agent Skills</em> are a fairly new format, and both Visual Studio Code and Eclipse Theia support it, although in Eclipse Theia the feature is still in alpha. There are some differences in where skills are stored and how they are used, which I list in the following sections. If you are interested in examples, have a look at <a href="https://github.com/heilcheng/awesome-agent-skills">awesome-agent-skills</a>.</p>

<h4 id="visual-studio-code-8">Visual Studio Code</h4>

<ul>
  <li>The location of the skills folder for a project in your repository is <em>.github/skills/</em>, <em>.claude/skills/</em> or <em>.agents/skills/</em>.</li>
  <li>The location for personal skills in the user home is <em>~/.copilot/skills/</em>, <em>~/.claude/skills/</em> or <em>~/.agents/skills/</em>.</li>
  <li>You can configure additional locations for skills via the <code class="language-plaintext highlighter-rouge">chat.agentSkillsLocations</code> setting to share skills across projects or keep them in a central location.</li>
  <li>You can use skills directly in the chat via a slash command. For example, if you created a skill with the name <em>webapp-testing</em>, you can use it directly in the chat via <code class="language-plaintext highlighter-rouge">/webapp-testing for the login page</code>.</li>
  <li>Copilot discovers the skills to use automatically by matching the user prompt with the skill description. Only the body of a matching skill will be loaded into the context. Additional resources will only be added if they are referenced in the instructions.</li>
  <li>You can create a new skill manually by creating the required folder and a <em>SKILL.md</em> file in that folder</li>
  <li>You can create a new skill with the help of AI by using the <code class="language-plaintext highlighter-rouge">/create-skill</code> slash command in the chat</li>
  <li>You can use the <em>Chat Customizations</em> dialog by clicking the gear icon in the upper-right corner of the Copilot chat window, select the <em>Skills</em> menu item on the left side and select an option to generate a skill via the dropdown on the upper right corner to guide you through the initial creation.
    <figure class="">
<img src="/blog/assets/images/copilot_chat_menu.png" alt="" /></figure>

    <figure class="">
<img src="/blog/assets/images/copilot_customizations_skills.png" alt="" /></figure>
  </li>
</ul>

<p>You can learn more about the use of <em>Agent Skills</em> in Visual Studio Code in <a href="https://code.visualstudio.com/docs/copilot/customization/agent-skills">Use Agent Skills in VS Code</a> and <a href="https://vogella.com/blog/vscode_copilot_extension/#agent-skills">Extending Copilot in Visual Studio Code - Further Customization - Agent Skills</a>.</p>

<h4 id="eclipse-theia-8">Eclipse Theia</h4>

<p>Support for <em>Agent Skills</em> in Eclipse Theia is still in alpha and currently has known limitations. For example, in Theia 1.70.0, automatic skill discovery is not working reliably. Automatic execution and tool calls can also fail. These issues are likely to improve in upcoming releases.</p>

<ul>
  <li>The location of the skills folder for a project in your repository is <em>.prompts/skills/</em>.</li>
  <li>The location for personal skills in the user home is <em>~/.theia/skills/</em>.</li>
  <li>You can configure additional locations for skills via the <code class="language-plaintext highlighter-rouge">ai-features.skills.skillDirectories</code> setting to share skills across projects or keep them in a central location.</li>
  <li>You can use skills directly in the chat as a slash command. For example, if you created a skill with the name <em>webapp-testing</em>, you can use it directly in the chat via <code class="language-plaintext highlighter-rouge">/webapp-testing for the login page</code>.</li>
  <li>You can enable agents to load skills on demand by using the `` variable in an agent’s prompt to list all available skills, and use the <code class="language-plaintext highlighter-rouge">~getSkillFileContent</code> function to load a selected skill on demand.</li>
  <li>You can create a new skill manually by creating the required folder and a <em>SKILL.md</em> file in that folder</li>
  <li>You can create a new skill with the help of AI by using the built-in <code class="language-plaintext highlighter-rouge">@CreateSkill</code> agent in the chat</li>
  <li>There is a <a href="https://theia-ide.org/docs/user_ai/#skills-and-slash-commands-view">Skills and Slash Commands View</a> available via the <em>AI Configuration</em> via the <em>Skills</em> tab, which provides a convenient overview of the discovered skills and a button to directly open the corresponding <em>SKILL.md</em> file in an editor.
    <figure class="">
<img src="/blog/assets/images/theia_configuration_skills.png" alt="" /></figure>
  </li>
</ul>

<p>You can learn more about the use of <em>Agent Skills</em> in Eclipse Theia via <a href="https://theia-ide.org/docs/user_ai/#agent-skills-alpha">Agent Skills (Alpha)</a> and <a href="https://vogella.com/blog/theia_ai_getting_started/#agent-skills">Getting Started with Theia AI - Further Customization - Agent Skills</a>.</p>

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

<p>As you can see, the AI capabilities of Visual Studio Code GitHub Copilot and Eclipse Theia AI are very similar. Both ecosystems evolve quickly, and while writing this post, I had to update findings and screenshots several times to keep it current.</p>

<p>At a high level, both platforms support the same core building blocks: tools, MCP integration, custom agents, reusable prompts, and skills. The main differences are in product strategy and developer control:</p>

<ul>
  <li>Visual Studio Code with GitHub Copilot gives you a polished product with strong built-in workflows and broad adoption.</li>
  <li>Eclipse Theia gives you an open platform you can own and shape deeply for your own product and domain.</li>
</ul>

<p>From a practical perspective, your choice usually depends less on feature checklists and more on your goals:</p>

<ul>
  <li>Choose Visual Studio Code GitHub Copilot if you want fast adoption, familiar UX, and minimal platform-level ownership.</li>
  <li>Choose Eclipse Theia if you need full control, deeper integration into your own application, and an open customization model.</li>
</ul>

<p>It is also worth noting that AI customization now offers multiple overlapping mechanisms for similar outcomes. Deciding whether to use a <em>Custom Agent</em>, a <em>Prompt File/Fragment</em>, or an <em>Agent Skill</em> is not always straightforward and often depends on scope, reuse, and ownership.</p>

<p>If you want to dive deeper into that topic, there is a valuable discussion in the Theia repository: <a href="https://github.com/eclipse-theia/theia/discussions/17087#discussioncomment-15970074">Agent vs PromptTemplate vs Skill</a>. The response by <a href="https://www.linkedin.com/in/jonas-helming-76303b28/">Jonas Helming</a> provides useful guidance on how these mechanisms differ and when each is a good fit.</p>

<p>In short: both platforms are strong, modern foundations for AI-assisted development. If you optimize for ready-to-use productivity, Visual Studio Code is a strong choice. If you optimize for extensibility and product ownership, Eclipse Theia is compelling.</p>]]></content><author><name>Dirk Fauth</name></author><category term="fipro" /><category term="vscode" /><category term="theia" /><category term="ai" /><category term="agents" /><summary type="html"><![CDATA[If you plan to build applications with AI support, especially for software development, you first need to decide which platform to use. You can build a plain web application from scratch and implement everything yourself, or build on top of an existing platform. In my previous blog posts, I explained how to customize the AI experience in Visual Studio Code and how to create a custom AI extension in Eclipse Theia. While writing those posts, I noticed many similarities, both from a user perspective and from an extension-development perspective. In this post, I compare both approaches to give you a clearer view of where they align and where they differ. If you have not read my previous blog posts and want more details, have a look at:]]></summary></entry><entry><title type="html">AI Agent Orchestration</title><link href="https://vogella.com/blog/ai-agent-orchestration/" rel="alternate" type="text/html" title="AI Agent Orchestration" /><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><id>https://vogella.com/blog/ai-agent-orchestration</id><content type="html" xml:base="https://vogella.com/blog/ai-agent-orchestration/"><![CDATA[<p>When talking about AI agents, we now often refer to <em>Agentic AI workflows</em>, where multiple agents collaborate to achieve complex goals. When creating <em>Custom Agents</em>, you need to consider collaboration options, especially if you design your agents for dedicated tasks rather than having one agent do everything. This is important because you should keep the context of your agent or prompt as minimal as possible while still providing enough information to achieve the desired results. Creating agents for dedicated tasks with a limited scope/context is similar to the encapsulation principle in object-oriented programming, and similarly, you need to consider processing or orchestration patterns.</p>

<p>Apart from a <em>Single Agent</em> that does all the work alone, you currently have two options:</p>

<ul>
  <li><em>Delegate Pattern</em><br />
Create guided sequential workflows that transition between agents. The agents are called one after the other.</li>
  <li><em>Coordinator and Worker Pattern</em><br />
The main/coordinator agent receives the task, delegates subtasks to subagents, and combines the subagent results into the final result.</li>
</ul>

<p>You can also combine these patterns to have multiple “main” agents that use worker agents for specific tasks. Each “main” agent can delegate to the next “main” agent once it is done, creating a sequential workflow of main tasks.</p>

<p>I will explain these patterns and show the differences between Visual Studio Code and Eclipse Theia using the following example: get a list of links for a specific topic. To get those links, first check a GitHub user’s gists for a publication collection. Then inspect the list of publications for links contained in the posts.</p>

<h2 id="visual-studio-code">Visual Studio Code</h2>

<p>In the following sections, I describe how to create <em>Custom Agents</em> in Visual Studio Code and compare the different orchestration patterns.</p>

<h3 id="github-mcp-server">GitHub MCP Server</h3>

<p>As explained earlier, I want to set up an example process where the first step is to retrieve a list of publications from a GitHub Gist. For this, we need to configure the <em>GitHub MCP Server</em> with the <em>gists</em> toolset enabled.</p>

<ul>
  <li>Open the file <em>.vscode/mcp.json</em> (create one if you do not already have that file in your workspace)
    <ul>
      <li>Add the following full configuration (or merge the <code class="language-plaintext highlighter-rouge">github</code> server entry into your existing <code class="language-plaintext highlighter-rouge">servers</code> object):</li>
    </ul>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"servers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sse"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"headers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"X-MCP-Toolsets"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gists"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"inputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>

    <p>If you already have other servers configured in <em>.vscode/mcp.json</em>, keep them and only add the <code class="language-plaintext highlighter-rouge">github</code> entry under <code class="language-plaintext highlighter-rouge">servers</code>.</p>

    <p>This adds the <a href="https://github.com/github/github-mcp-server/blob/main/docs/remote-server.md">Remote GitHub MCP Server</a> with the <em>gists</em> toolset and OAuth authentication.</p>

    <p><em><strong>Note:</strong></em><br />
If you want to use a Personal Access Token (PAT) for the authorization instead of OAuth, have a look at <a href="https://vogella.com/blog/vscode_copilot_extension/#remote-mcp-server-with-authorization">Extending Copilot in Visual Studio Code - Remote MCP Server with authorization</a>.</p>
  </li>
  <li>In the editor, you will see actions provided as <em>CodeLens</em> that let you interact with the server. Click <em>Start</em> to start the GitHub MCP server.
    <figure class="">
<img src="/blog/assets/images/copilot_mcp_github_gists.png" alt="" /></figure>

    <p>The first time, you will be prompted via dialog to authenticate with GitHub. After clicking <em>Allow</em>, a website opens where you can log in to the account you want to connect the MCP server to.</p>
    <figure class="">
<img src="/blog/assets/images/copilot_github_authenticate.png" alt="" /></figure>

    <p>After the authentication succeeds, the server starts and the capabilities and tools provided by the server are discovered.</p>
  </li>
</ul>

<h3 id="single-agent">Single Agent</h3>

<p>We start by creating a single <em>Custom Agent</em> that performs all steps itself. This agent will then be split to explain the orchestration patterns.</p>

<ul>
  <li>Create a new <em>Custom Agent</em> that executes the previously described process to provide the user with a collection of links for a specific topic.
    <ul>
      <li>
        <p>In the Copilot chat window, click the gear icon in the upper right corner (<em>Configure Chat…</em>) and select<br />
<em>Custom Agents</em> -&gt; <em>Create new custom agent…</em> -&gt; <em>.github/agents</em> -&gt; name: research</p>

        <p>This creates the file <em>.github/agents/research.agent.md</em></p>
      </li>
    </ul>
  </li>
  <li>Add the tools <code class="language-plaintext highlighter-rouge">web/fetch</code> and <code class="language-plaintext highlighter-rouge">github/list_gists</code></li>
  <li>Add a prompt that defines the steps to process</li>
  <li>
    <p>The following snippet shows how such an agent could look like</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">collection</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">links</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">specific</span><span class="nv"> </span><span class="s">topic."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">web/fetch</span><span class="pi">,</span> <span class="nv">github/list_gists</span><span class="pi">]</span>
<span class="nn">---</span>

You are an agent that helps the developer by extracting and providing links mentioned in blog posts.

To provide the necessary links execute the following steps:
<span class="p">
1.</span> Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use #tool:github/list_gists to find the correct gist.
<span class="p">2.</span> Use #tool:web/fetch to fetch the content of the gist with a max-length parameter of 15000.
<span class="p">3.</span> Filter the fetched content for links about the requested information.
<span class="p">4.</span> For every found blog post, use #tool:web/fetch to fetch the content of the given blog post with a max-length parameter of 15000.
<span class="p">5.</span> Collect all links that are mentioned in the blog post and relevant for the topic.
<span class="p">6.</span> Filter out duplicate links and links that are not relevant for the topic. Relevance can be determined by the presence of keywords related to the topic in the context of the link.
<span class="p">7.</span> Provide a collection of the extracted filtered links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.
</code></pre></div>    </div>
  </li>
</ul>

<p><em><strong>Hint:</strong></em><br />
The built-in <code class="language-plaintext highlighter-rouge">web/fetch</code> tool asks for approval to execute <code class="language-plaintext highlighter-rouge">fetch</code> and to access the URLs it wants to retrieve, ensuring no malicious content is fetched.
If you use the custom agent prompts that I prepared, it will fetch my gist with my publications and blog posts published at <a href="https://vogella.com/blog/">https://vogella.com/blog/</a>. If you trust these sources (at least I do :smile:), you can configure trust in <em>settings.json</em> by adding the following configuration to reduce the number of prompts during processing:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"chat.tools.urls.autoApprove"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"https://vogella.com/blog/"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="p">}</span><span class="err">,</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">research</code> by selecting it in the agents dropdown in the chat view, then enter a prompt, for example <code class="language-plaintext highlighter-rouge">show links about visual studio code</code>.
    <figure class="">
<img src="/blog/assets/images/copilot_select_custom_agent_research.png" alt="" /></figure>

    <ul>
      <li>When asked to allow fetching the gist and the content of the gist, select <em>Allow and Review Once</em> for the first request, and <em>Allow Once</em> afterwards.<br />
Further information can be found in the official documentation: <a href="https://code.visualstudio.com/docs/copilot/agents/agent-tools#_url-approval">Use tools with agents - URL approval</a></li>
    </ul>
  </li>
</ul>

<p>After the agent finishes its task, you can <a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat-context#_monitor-context-window-usage">monitor the context window usage</a>.
The following screenshots show the context window usage when I ran the <em>Custom Agent</em> with Gemini 2.5 Pro and GPT-5.3-Codex. The values might be different when executing it again.</p>

<p>This is the context window usage with <em>Gemini 2.5 Pro</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_single_gemini.png" alt="" /></figure>

<p>This is the context window usage with <em>GPT-5.3-Codex</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_single_codex.png" alt="" /></figure>

<h3 id="delegate-pattern">Delegate Pattern</h3>

<p>The <em>Delegate Pattern</em> is supported via <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents#_handoffs">Handoffs</a> when creating <em>Custom Agents</em> in Visual Studio Code. For this scenario, we split the previous <em>research</em> agent into two <em>Custom Agents</em>, one per task:</p>

<ul>
  <li>One agent to get the information from a gist to find the blog posts</li>
  <li>
    <p>One agent to extract the links from the found blog posts</p>
  </li>
  <li>Create a new <em>Custom Agent</em> that extracts links from the text of a given blog post.
    <ul>
      <li>
        <p>In the Copilot chat window, click the gear icon in the upper right corner (<em>Configure Chat…</em>) and select<br />
<em>Custom Agents</em> -&gt; <em>Create new custom agent…</em> -&gt; <em>.github/agents</em> -&gt; name: link_extractor</p>

        <p>This creates the file <em>.github/agents/link_extractor.agent.md</em></p>
      </li>
    </ul>
  </li>
  <li>Add the built-in <code class="language-plaintext highlighter-rouge">web/fetch</code> tool to fetch the content</li>
  <li>Add a prompt that defines the steps to process</li>
  <li>
    <p>The following snippet shows how such an agent could look like</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">list</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">links</span><span class="nv"> </span><span class="s">extracted</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">blog</span><span class="nv"> </span><span class="s">posts."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">web/fetch</span><span class="pi">]</span>
<span class="nn">---</span>

You are an agent that helps the developer by extracting links mentioned in a blog post and providing them in a structured format.

To provide the necessary links execute the following steps:
<span class="p">
1.</span> Use #tool:web/fetch to fetch the content of the given blog post with a max-length parameter of 15000.
<span class="p">2.</span> Collect all links that are mentioned in the blog post and relevant for the topic.
<span class="p">3.</span> Filter out duplicate links and links that are not relevant for the topic. Relevance can be determined by the presence of keywords related to the topic in the context of the link.
<span class="p">4.</span> Provide a collection of the extracted filtered links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.
</code></pre></div>    </div>
  </li>
  <li>Create a new <em>Custom Agent</em> that is able to retrieve information from a <em>gist</em>.
    <ul>
      <li>
        <p>In the Copilot chat window, click the gear icon in the upper right corner (<em>Configure Chat…</em>) and select<br />
<em>Custom Agents</em> -&gt; <em>Create new custom agent…</em> -&gt; <em>.github/agents</em> -&gt; name: gists</p>

        <p>This creates the file <em>.github/agents/gists.agent.md</em></p>
      </li>
    </ul>
  </li>
  <li>Add the GitHub MCP tool <code class="language-plaintext highlighter-rouge">github/list_gists</code> to list the gists</li>
  <li>Add the built-in <code class="language-plaintext highlighter-rouge">web/fetch</code> tool to fetch the content</li>
  <li>Configure a <code class="language-plaintext highlighter-rouge">handoff</code> to delegate processing to the <code class="language-plaintext highlighter-rouge">link_extractor</code> agent</li>
  <li>
    <p>The following snippet shows how such an agent could look like</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">list</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">links</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">blog</span><span class="nv"> </span><span class="s">posts</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">GitHub</span><span class="nv"> </span><span class="s">Gist."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">github/list_gists</span><span class="pi">,</span> <span class="nv">web/fetch</span><span class="pi">]</span>
<span class="na">handoffs</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">label</span><span class="pi">:</span> <span class="s">Extract Links from posts</span>
    <span class="na">agent</span><span class="pi">:</span> <span class="s">link_extractor</span>
    <span class="na">prompt</span><span class="pi">:</span> <span class="s">Extract links from the given list of blog posts</span>
    <span class="na">send</span><span class="pi">:</span> <span class="kc">true</span>
<span class="nn">---</span>

You are an agent that helps the developer by providing links to blog posts.

To provide the necessary links execute the following steps:
<span class="p">
1.</span> Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use #tool:github/list_gists to find the correct gist.
<span class="p">2.</span> Use #tool:web/fetch to fetch the content of the gist with a max-length parameter of 15000.
<span class="p">3.</span> Filter the fetched content for links about the requested information.
<span class="p">4.</span> Provide a list of links to the relevant blog posts.
</code></pre></div>    </div>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">gists</code> by selecting it in the agents dropdown in the chat view, then enter a prompt, for example <code class="language-plaintext highlighter-rouge">show links about visual studio code</code>.
    <figure class="">
<img src="/blog/assets/images/copilot_select_custom_agent_gists.png" alt="" /></figure>

    <ul>
      <li>When asked to allow fetching the gist and the content of the gist, select <em>Allow and Review Once</em> for the first request, and <em>Allow Once</em> afterwards.<br />
Further information can be found in the official documentation: <a href="https://code.visualstudio.com/docs/copilot/agents/agent-tools#_url-approval">Use tools with agents - URL approval</a></li>
      <li>After the <code class="language-plaintext highlighter-rouge">gists</code> agent finishes its task, the user must actively proceed with the handoff by clicking the <em>Proceed</em> button in the chat.
        <figure class="">
<img src="/blog/assets/images/copilot_handoff_proceed_gists.png" alt="" /></figure>
      </li>
      <li>You can see that the agent switches to the <code class="language-plaintext highlighter-rouge">link_extractor</code> agent in the chat
        <figure class="">
<img src="/blog/assets/images/copilot_select_custom_agent_link_extractor.png" alt="" /></figure>
      </li>
    </ul>
  </li>
</ul>

<p>After the agent finishes its task, you can <a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat-context#_monitor-context-window-usage">monitor the context window usage</a>.
The following screenshots show the context window usage when I ran the <em>Custom Agent</em> with Gemini 2.5 Pro and GPT-5.3-Codex. The values might be different when executing it again.</p>

<p>This is the context window usage with <em>Gemini 2.5 Pro</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_delegate_gemini.png" alt="" /></figure>

<p>This is the context window usage with <em>GPT-5.3-Codex</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_delegate_codex.png" alt="" /></figure>

<p>You can see that the context window looks quite similar to executing the process with a single agent. The reason is that we stay in the same conversation, so the relevant context is largely the same. This means the context contains information from the first agent that is also available to the second agent. The <em>Delegate Pattern</em> therefore helps create a better structure for a multi-agent system and reusable agents for various scenarios via “encapsulation”, but it has little to no effect on token usage compared to the single-agent solution.</p>

<h3 id="coordinator-and-worker-pattern">Coordinator and Worker Pattern</h3>

<p>In Visual Studio Code, you implement the <a href="https://code.visualstudio.com/docs/copilot/agents/subagents#_coordinator-and-worker-pattern">Coordinator and Worker Pattern</a> by using <a href="https://code.visualstudio.com/docs/copilot/agents/subagents">Subagents</a>. Using a subagent means spawning a child agent within a session to handle a subtask in its own isolated context window. From a pattern perspective, this means there is a main coordinator agent that manages the overall task and delegates subtasks to specialized subagents. To call a subagent, the built-in <code class="language-plaintext highlighter-rouge">agent/runSubagent</code> tool needs to be enabled for the coordinator agent. Each subagent call is sequential (the coordinator waits for that call to return), but the coordinator can spawn multiple subagent calls in parallel.</p>

<p>In this section, the previously created agents are converted into coordinator and worker agents.</p>

<ul>
  <li>Open the file <em>.github/agents/gists.agent.md</em></li>
  <li>Remove the <code class="language-plaintext highlighter-rouge">handoffs</code> header</li>
  <li>Ensure that the agent returns something at the end</li>
  <li>
    <p>The following snippet shows how such an agent could look like</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">list</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">links</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">blog</span><span class="nv"> </span><span class="s">posts</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">GitHub</span><span class="nv"> </span><span class="s">Gist."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">github/list_gists</span><span class="pi">,</span> <span class="nv">web/fetch</span><span class="pi">]</span>
<span class="nn">---</span>

You are an agent that helps the developer by providing links to blog posts.

To provide the necessary links execute the following steps:
<span class="p">
1.</span> Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use #tool:github/list_gists to find the correct gist.
<span class="p">2.</span> Use #tool:web/fetch to fetch the content of the gist with a max-length parameter of 15000.
<span class="p">3.</span> Filter the fetched content for links about the requested information.
<span class="p">4.</span> Provide a list of links to the relevant blog posts.
</code></pre></div>    </div>
  </li>
  <li>Open the file <em>.github/agents/research.agent.md</em></li>
  <li>Add the built-in <code class="language-plaintext highlighter-rouge">agent</code> tool to call subagents and remove the other tools</li>
  <li>Configure the agents <code class="language-plaintext highlighter-rouge">gists</code> and <code class="language-plaintext highlighter-rouge">link_extractor</code> as agents that can be used as subagents</li>
  <li>
    <p>The following snippet shows how such an agent could look like</p>

    <div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">agent</span><span class="nv"> </span><span class="s">provides</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">collection</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">links</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">specific</span><span class="nv"> </span><span class="s">topic."</span>
<span class="na">tools</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">agent</span><span class="pi">]</span>
<span class="na">agents</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">gists"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">link_extractor"</span><span class="pi">]</span>
<span class="nn">---</span>

You are an agent that helps the developer by providing links to blog posts about a specific topic.
To provide the necessary links use subagents to execute the following steps:
<span class="p">
1.</span> Use the gists subagent to fetch a collection of blog posts about the specific topic.
<span class="p">2.</span> For each of the found blog posts use the link_extractor subagent to fetch the content of the blog post and extract all links that are mentioned in the blog post.
<span class="p">3.</span> Provide a collection of the extracted links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.
</code></pre></div>    </div>
  </li>
  <li>
    <p>The <code class="language-plaintext highlighter-rouge">link_extractor</code> agent can stay as it is, since it does not specify a <code class="language-plaintext highlighter-rouge">handoff</code> and already returns the result in its prompt.</p>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">research</code> by selecting it in the agents dropdown in the chat view, then enter a prompt, for example <code class="language-plaintext highlighter-rouge">show links about visual studio code</code>.
    <figure class="">
<img src="/blog/assets/images/copilot_select_custom_agent_research.png" alt="" /></figure>
  </li>
</ul>

<p>When watching the execution, you should notice that:</p>

<ul>
  <li>The <code class="language-plaintext highlighter-rouge">research</code> agent stays active as the coordinator agent</li>
  <li>While the subagents are called, the coordinator waits until they are done</li>
  <li>The <code class="language-plaintext highlighter-rouge">link_extractor</code> agent is called multiple times, once per found blog post. These multiple agent calls are executed in parallel, while the coordinator agent waits until all spawned child agents are finished.</li>
</ul>

<p>After the agent finishes its task, you can <a href="https://code.visualstudio.com/docs/copilot/chat/copilot-chat-context#_monitor-context-window-usage">monitor the context window usage</a>.
The following screenshots show the context window usage when I ran the <em>Custom Agent</em> with Gemini 2.5 Pro and GPT-5.3-Codex. The values might be different when executing it again.</p>

<p>This is the context window usage with <em>Gemini 2.5 Pro</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_subagents_gemini.png" alt="" /></figure>

<p>This is the context window usage with <em>GPT-5.3-Codex</em>:</p>
<figure class="">
  <img src="/blog/assets/images/copilot_context_window_subagents_codex.png" alt="" /></figure>

<p>You can see that the context window is smaller compared to the other patterns, which can be explained by the fact that each subagent is executed in its own isolated context window.</p>

<h2 id="eclipse-theia">Eclipse Theia</h2>

<p>If you are new to Eclipse Theia and want to try out the examples in the following sections, you have three options:</p>

<ul>
  <li><a href="https://try.theia-cloud.io/">Try Theia IDE online</a></li>
  <li><a href="https://theia-ide.org/#theiaidedownload">Get Theia IDE for desktop</a></li>
  <li>Build your own Theia application<br />
Have a look at <a href="https://vogella.com/blog/theia_getting_started/">Getting Started with Eclipse Theia</a> and <a href="https://vogella.com/blog/theia_ai_getting_started/">Getting Started with Theia AI</a> if you are interested in that topic.</li>
</ul>

<p>For the following sections, I assume that you have a Theia application with AI support. To enable AI features inside a Theia application, such as Theia IDE, you need access to an LLM and must configure it accordingly. I will give an example using the Google Gemini free tier. This only works if you have a Google account. If you want to use another LLM, have a look at the <a href="https://theia-ide.org/docs/user_ai/#llm-providers-overview">LLM Providers Overview</a> to see which LLMs are currently supported by Theia. If you have a subscription for an LLM that is not listed here, check whether it is compatible with the <a href="https://github.com/openai/openai-node">OpenAI API</a> and try to configure it as an <a href="https://theia-ide.org/docs/user_ai/#openai-compatible-models-eg-via-vllm">OpenAI Compatible Model</a>.</p>

<ul>
  <li>If you have a Google account, you can try out the <a href="https://aistudio.google.com/">Google AI Studio</a>
    <ul>
      <li>Create a new project via <a href="https://aistudio.google.com/projects">Google AI Studio - Projects</a>
        <ul>
          <li>Click on <strong>Create a new project</strong></li>
          <li>Provide a name like <em>theia-evaluation</em></li>
          <li>Click on <strong>Create project</strong></li>
        </ul>
      </li>
      <li>Create a new API key via <a href="https://aistudio.google.com/api-keys">Google AI Studio - API Keys</a>
        <ul>
          <li>Click on <strong>Create API Key</strong></li>
          <li>Give the key a name like <em>theia</em></li>
          <li>Select the previously created <em>theia-evaluation</em> project</li>
          <li>Click on <strong>Create key</strong></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Switch to the Theia application (e.g. the Theia IDE)
    <ul>
      <li>Open the settings page by pressing <strong>CTRL</strong> + <strong>,</strong>
        <ul>
          <li>Select the <strong>AI Features</strong></li>
          <li>Check <strong>Enable AI</strong></li>
          <li>Configure the LLM you want to use
            <ul>
              <li>Google
                <ul>
                  <li><strong>Api Key</strong>: Copy and paste your Google AI API key (see above)<br />
For this tutorial we simply configure the API key via preferences as it is easier than setting up the environment.
In a productive environment you should use the environment variable <code class="language-plaintext highlighter-rouge">GOOGLE_API_KEY</code> to set the key securely.</li>
                  <li><strong>Models</strong>: Ensure to have models in the list that are currently available according to <a href="https://ai.google.dev/gemini-api/docs/models">Gemini Models</a></li>
                </ul>
              </li>
            </ul>
          </li>
          <li>Configure the <em>Model Aliases</em>
            <ul>
              <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
              <li>Switch to the <em>Model Aliases</em> tab</li>
              <li>For every model alias, select the model that you configured, e.g. <code class="language-plaintext highlighter-rouge">google/gemini-3.1-flash-lite-preview</code></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>Since version 1.68.0 Theia also supports a <a href="https://github.com/eclipse-theia/theia/pull/16841">GitHub Copilot language model integration</a>.
To try it out, you need to authenticate with GitHub</p>

<ul>
  <li>Click on <em>Sign in to GitHub Copilot</em> in the footer of the Theia application
    <figure class="">
<img src="/blog/assets/images/theia_copilot_footer.png" alt="" /></figure>
  </li>
  <li>This will open the following window
    <figure class="">
<img src="/blog/assets/images/theia_copilot_signin.png" alt="" /></figure>
  </li>
  <li><strong>Copy</strong> the key</li>
  <li>Click on <em>Open GitHub</em></li>
  <li>Follow the instructions on the GitHub website to grant the device permission</li>
  <li>After the device activation is successful, switch back to the Theia browser window and click on <em>I have authorized</em> to finish the authentication process in Theia</li>
  <li>After successful authentication, you can select a Copilot model as <em>Model Alias</em>, e.g. <code class="language-plaintext highlighter-rouge">copilot/gpt-4o</code></li>
</ul>

<p><em><strong>Note:</strong></em><br />
At the time of writing this article, the only Copilot models that are working in Theia are <code class="language-plaintext highlighter-rouge">gpt-4o</code> and <code class="language-plaintext highlighter-rouge">gpt-4o-mini</code>. Using other models results in the following issue: <a href="https://github.com/eclipse-theia/theia-ide/issues/675">AI Chat with GitHub Copilot fails with: 400 The requested model is not supported</a>.</p>

<h3 id="mcp-server-configuration">MCP Server Configuration</h3>

<p>To set up the example process like in Visual Studio Code to retrieve a list of publications from a GitHub Gist and then fetch the data for further processing, we need to configure the necessary MCP server. This is again the <em>GitHub MCP Server</em> with the <em>gists</em> toolset enabled, and the <em>fetch MCP Server</em> as Theia does not provide a built-in fetch tool.</p>

<ul>
  <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em>
    <ul>
      <li>Switch to the <em>MCP Servers</em> tab</li>
      <li>Add the <a href="https://github.com/modelcontextprotocol/servers/tree/main/src/fetch">Fetch MCP Server</a> as a remote MCP Server
        <ul>
          <li>Click on <em>Add MCP Server</em></li>
          <li>Set the following values in the dialog
            <ul>
              <li><strong>Server Name:</strong> <em>fetch</em></li>
              <li><strong>Server Type:</strong> <em>Remote (URL)</em></li>
              <li><strong>Server URL:</strong> <em>https://remote.mcpservers.org/fetch/mcp</em></li>
              <li>Keep the <strong>Autostart</strong> flag checked</li>
            </ul>
          </li>
          <li>Click <em>Add Server</em></li>
        </ul>

        <figure class="">
<img src="/blog/assets/images/theia_mcp_add_fetch.png" alt="" /></figure>
      </li>
      <li>Add the <a href="https://github.com/github/github-mcp-server/blob/main/docs/remote-server.md">Remote GitHub MCP Server</a> with the <em>gists</em> toolset
        <ul>
          <li>Click on <em>Add MCP Server</em></li>
          <li>Set the following values in the dialog
            <ul>
              <li><strong>Server Name:</strong> <em>github</em></li>
              <li><strong>Server Type:</strong> <em>Remote (URL)</em></li>
              <li><strong>Server URL:</strong> <em>https://api.githubcopilot.com/mcp/x/gists</em></li>
              <li><strong>Auth Token:</strong> Your Personal Access Token (PAT) with <strong>repo</strong> scope. See <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic">Creating a personal access token (classic)</a> if you do not already have a PAT.</li>
              <li>Keep the <strong>Autostart</strong> flag checked</li>
            </ul>
          </li>
          <li>Click <em>Add Server</em></li>
        </ul>

        <figure class="">
<img src="/blog/assets/images/theia_mcp_add_github.png" alt="" /></figure>
      </li>
    </ul>
  </li>
  <li>Instead of configuring everything via user interface, you can also directly paste one of the following configurations directly in the settings JSON
    <ul>
      <li>Switch to the JSON view of the settings by clicking the curly braces on the upper right corner of the editor (<em>Open Settings (JSON)</em>)</li>
      <li>Alternatively use the <em>Command Palette</em> (F1) and search for <em>Preferences: Open Settings (JSON)</em></li>
      <li>Copy the following snippet and paste it in the editor
        <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"window.titleBarStyle"</span><span class="p">:</span><span class="w"> </span><span class="s2">"custom"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.AiEnable.enableAI"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.google.apiKey"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;your-api-key&gt;"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.google.models"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"gemini-3.1-flash-lite-preview"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-3-flash-preview"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-2.5-flash"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-2.5-flash-lite"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"gemini-2.5-pro"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"ai-features.languageModelAliases"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"default/code"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-3.1-flash-lite-preview"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/universal"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-3.1-flash-lite-preview"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/code-completion"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-3.1-flash-lite-preview"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"default/summarize"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"selectedModel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google/gemini-3.1-flash-lite-preview"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"ai-features.chat.defaultChatAgent"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Universal"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ai-features.mcp.mcpServers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"fetch"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://remote.mcpservers.org/fetch/mcp"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"autostart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"github"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"serverUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.githubcopilot.com/mcp/x/gists"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"autostart"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"serverAuthToken"</span><span class="p">:</span><span class="w"> </span><span class="s2">"&lt;your-github-pat&gt;"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div>        </div>
      </li>
      <li>Replace <code class="language-plaintext highlighter-rouge">&lt;your-api-key&gt;</code> with your Google AI Studio AI key and <code class="language-plaintext highlighter-rouge">&lt;your-github-pat&gt;</code> with your PAT.</li>
    </ul>
  </li>
</ul>

<p>After performing the above steps, you should see the two MCP servers in the overview and they should directly be <em>Connected</em> as the servers are configured to autostart.</p>

<h3 id="single-agent-1">Single Agent</h3>

<p>We again first create a single <em>Custom Agent</em> that performs all steps itself. This agent will then be split to explain the orchestration patterns.</p>

<ul>
  <li>Create a new <em>Custom Agent</em> that executes the previously described process to provide the user with a collection of links for a specific topic.
    <ul>
      <li>Open the <em>AI Configuration</em> via <em>Menu -&gt; View -&gt; AI Configuration</em></li>
      <li>Switch to the <em>Agents</em> tab</li>
      <li>Click on <strong>Add Custom Agent</strong>
        <figure class="">
<img src="/blog/assets/images/theia_add_custom_agent.png" alt="" /></figure>
      </li>
      <li>Select the <em>.prompts</em> folder of the current workspace</li>
      <li>Verify that a <em>.prompts</em> folder is generated in your workspace that contains a <em>customAgents.yml</em> file</li>
      <li>Define a custom agent with the name <em>Research</em> by defining the following information
        <ul>
          <li><em>id</em>: A unique identifier for the agent.</li>
          <li><em>name</em>: The display name of the agent.</li>
          <li><em>description</em>: A brief explanation of what the agent does.</li>
          <li><em>prompt</em>: The default prompt that the agent will use for processing requests.</li>
          <li><em>defaultLLM</em>: The language model used by default.</li>
          <li><em>showInChat</em>: Whether the agent should be shown in the chat UI. This one is optional and defaults to <code class="language-plaintext highlighter-rouge">true</code>.</li>
        </ul>
      </li>
      <li>Replace the content of the <em>customAgents.yml</em> with the following snippet</li>
    </ul>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Research</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Research</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a collection of links for a specific topic.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by extracting and providing links mentioned in blog posts.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use ~{mcp_github_list_gists} to find the correct gist.</span>
    <span class="s">2. Use ~{mcp_fetch_fetch} to fetch the content of the gist with a max-length parameter of 15000.</span>
    <span class="s">3. Filter the fetched content for links about the requested information.</span>
    <span class="s">4. For every found blog post, use ~{mcp_fetch_fetch} to fetch the content of the given blog post with a max-length parameter of 15000.</span>
    <span class="s">5. Collect all links that are mentioned in the blog post and relevant for the topic.</span>
    <span class="s">6. Filter out duplicate links and links that are not relevant for the topic. Relevance can be determined by the presence of keywords related to the topic in the context of the link.</span>
    <span class="s">7. Provide a collection of the extracted filtered links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>

    <p>Compared to a custom agent in Visual Studio Code, we do not need to configure which tools we want to use in the prompt. They can simply be referenced in the prompt via <code class="language-plaintext highlighter-rouge">~&lt;tool-name&gt;</code>.</p>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">Research</code> by selecting it in the chat prompt via <code class="language-plaintext highlighter-rouge">@</code> syntax and add the prompt to execute, for example <code class="language-plaintext highlighter-rouge">@Research show links about theia</code>.
    <figure class="">
<img src="/blog/assets/images/theia_select_custom_agent_research.png" alt="" /></figure>
  </li>
</ul>

<p>Theia does not yet support context window monitoring like Visual Studio Code, so we cannot inspect detailed context usage. This feature has been requested via <a href="https://github.com/eclipse-theia/theia/issues/16779">Context window inspection / analysis command</a>. For several LLMs, token usage can still be inspected via <em>AI Configuration</em> by switching to the <em>Token Usage</em> tab. I tested the example using <em>GPT-5.3-Codex</em> hosted on Azure, <em>GPT-4o</em> via Copilot, and <em>Gemini 3.1 Flash Lite Preview</em> via Google AI Studio in the Free Tier.</p>

<figure class="">
  <img src="/blog/assets/images/theia_token_single.png" alt="" /></figure>

<p><em><strong>Note:</strong></em><br />
The token counts reported for Gemini models are incorrect in Theia 1.69.0. I created the ticket <a href="https://github.com/eclipse-theia/theia/issues/17165">Token usage shows incorrect values for Gemini models</a> and submitted a pull request that fixes this issue. The screenshot above shows token usage with the fix applied for a fair comparison.</p>

<p>Token usage is not persisted and is reset when restarting the application. To get a better comparison, I restart after each example.</p>

<h3 id="delegate-pattern-1">Delegate Pattern</h3>

<p>Theia does not provide a feature like the <a href="https://code.visualstudio.com/docs/copilot/customization/custom-agents#_handoffs">Handoffs</a> in Visual Studio Code. Instead Theia provides the built-in <em>Tool Function</em> <code class="language-plaintext highlighter-rouge">delegateToAgent</code> to support <a href="https://theia-ide.org/docs/user_ai/#agent-to-agent-delegation">Agent-to-Agent Delegation</a>. To show this scenario, we split the previous <em>Research</em> agent into two <em>Custom Agents</em>, one per task:</p>

<ul>
  <li>One agent to get the information from a gist to find the blog posts</li>
  <li>One agent to extract the links from the found blog posts</li>
</ul>

<p>In Eclipse Theia, multiple <a href="https://theia-ide.org/docs/user_ai/#custom-agents">Custom Agents</a> are configured in a single custom agent configuration file. We therefore add the new agents to the previously created <em>customAgents.yml</em>.</p>

<ul>
  <li>Open the <em>.prompts/customAgents.yml</em> file</li>
  <li>Add a new <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent
    <ul>
      <li>Use the <em>MCP Tool</em> <code class="language-plaintext highlighter-rouge">mcp_fetch_fetch</code> to fetch the content of the blog posts</li>
      <li>Add a prompt that defines the steps to process</li>
      <li>The following snippet shows how such an agent could look like</li>
    </ul>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Link_Extractor</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Link_Extractor</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of links extracted from blog posts.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by extracting links mentioned in blog posts and providing them in a structured format.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Iterate over the list of provided blog posts</span>
    <span class="s">2. For every blog post use ~{mcp_fetch_fetch} to fetch the content of the blog post with a max-length parameter of 15000.</span>
    <span class="s">3. Collect all links that are mentioned in the blog post and relevant for the topic.</span>
    <span class="s">4. Filter out duplicate links and links that are not relevant for the topic. Relevance can be determined by the presence of keywords related to the topic in the context of the link.</span>
    <span class="s">5. Provide a collection of the extracted filtered links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>
  </li>
  <li>Add a new <code class="language-plaintext highlighter-rouge">Gists</code> agent
    <ul>
      <li>Use the <em>MCP Tool</em> <code class="language-plaintext highlighter-rouge">mcp_github_list_gists</code> to list the gists</li>
      <li>Use the <em>MCP Tool</em> <code class="language-plaintext highlighter-rouge">mcp_fetch_fetch</code> to fetch the content</li>
      <li>Use the Theia built-in <em>Tool Function</em> <code class="language-plaintext highlighter-rouge">delegateToAgent</code> to delegate processing to the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent</li>
      <li>The following snippet shows how such an agent could look like</li>
    </ul>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of links to blog posts from a GitHub Gist.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by providing links to blog posts.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use ~{mcp_github_list_gists} to find the correct gist.</span>
    <span class="s">2. Use ~{mcp_fetch_fetch} to fetch the content of the gist with a max-length parameter of 15000.</span>
    <span class="s">3. Filter the fetched content for links about the requested information.</span>
    <span class="s">4. Provide a list of links to the relevant blog posts.</span>
    <span class="s">5. Pass the provided list of links to the Link_Extractor agent via ~{delegateToAgent} to extract links from the given list of blog posts</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">Gists</code> by selecting it in the chat prompt via <code class="language-plaintext highlighter-rouge">@</code> syntax and add the prompt to execute, for example <code class="language-plaintext highlighter-rouge">@Gists show links about theia</code>.
    <figure class="">
<img src="/blog/assets/images/theia_select_custom_agent_gists.png" alt="" /></figure>
  </li>
</ul>

<p>You can see in the chat response that the <code class="language-plaintext highlighter-rouge">Gists</code> agent stays the active agent, and <code class="language-plaintext highlighter-rouge">Link_Extractor</code> is called as part of it. So it is not actually a <em>Handoff</em> like in Visual Studio Code, where the active agent really switches.</p>

<figure class="">
  <img src="/blog/assets/images/theia_delegate_response.png" alt="" /></figure>

<p>Theia does not yet support context window monitoring like Visual Studio Code, so we cannot inspect detailed context usage. This feature has been requested via <a href="https://github.com/eclipse-theia/theia/issues/16779">Context window inspection / analysis command</a>. For several LLMs, token usage can still be inspected via <em>AI Configuration</em> by switching to the <em>Token Usage</em> tab. I tested the example using <em>GPT-5.3-Codex</em> hosted on Azure, <em>GPT-4o</em> via Copilot, and <em>Gemini 3.1 Flash Lite Preview</em> via Google AI Studio in the Free Tier.</p>

<figure class="">
  <img src="/blog/assets/images/theia_token_delegate.png" alt="" /></figure>

<p><em><strong>Note:</strong></em><br />
The token counts reported for Gemini models are incorrect in Theia 1.69.0. I created the ticket <a href="https://github.com/eclipse-theia/theia/issues/17165">Token usage shows incorrect values for Gemini models</a> and submitted a pull request that fixes this issue. The screenshot above shows token usage with the fix applied for a fair comparison.</p>

<p>Interestingly, token usage for the <em>Delegate Pattern</em> in Theia is slightly higher compared to the single-agent solution.
It is also interesting that the <em>Delegate Pattern</em> is not exactly the same as in Visual Studio Code via <em>Handoffs</em>. It seems that <em>Agent-to-Agent Delegation</em> is similar to <em>Subagents</em> in Visual Studio Code, at least based on the chat output.</p>

<p>At the time of writing this blog post, Theia does not support an automatic <em>Handoff</em> similar to Visual Studio Code. But you can try to simulate this manually.</p>

<ul>
  <li>Update the <code class="language-plaintext highlighter-rouge">Gists</code> agent
    <ul>
      <li>Remove the last step that passes the processing to the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent</li>
      <li>The following snippet shows how such an agent could look like</li>
    </ul>

    <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of links to blog posts from a GitHub Gist.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by providing links to blog posts.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use ~{mcp_github_list_gists} to find the correct gist.</span>
    <span class="s">2. Use ~{mcp_fetch_fetch} to fetch the content of the gist with a max-length parameter of 15000.</span>
    <span class="s">3. Filter the fetched content for links about the requested information.</span>
    <span class="s">4. Provide a list of links to the relevant blog posts.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>    </div>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">Gists</code> by selecting it in the chat prompt via <code class="language-plaintext highlighter-rouge">@</code> syntax and add the prompt to execute, for example <code class="language-plaintext highlighter-rouge">@Gists show links about theia</code>.
    <figure class="">
<img src="/blog/assets/images/theia_select_custom_agent_gists.png" alt="" /></figure>
  </li>
  <li>Once the <code class="language-plaintext highlighter-rouge">Gists</code> agent is done, use the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent by selecting it in the chat prompt via <code class="language-plaintext highlighter-rouge">@</code> syntax and add the prompt to execute, for example <code class="language-plaintext highlighter-rouge">@Link_Extractor fetch the previously found blog posts and extract further links</code>.
    <figure class="">
<img src="/blog/assets/images/theia_select_custom_agent_link_extractor.png" alt="" /></figure>
  </li>
</ul>

<p>As a result of manually changing the active agent, <code class="language-plaintext highlighter-rouge">Link_Extractor</code> is now the active agent.</p>

<figure class="">
  <img src="/blog/assets/images/theia_token_delegate_manually.png" alt="" /></figure>

<p>The token usage increased as the whole conversation is passed to the next agent as context, and the token usage is accumulated over the whole session. Therefore the <em>Input Tokens</em> and the <em>Output Tokens</em> are higher than compared to using the <em>Agent-to-Agent Delegation</em> with an isolated context or the <em>Single Agent</em>, where only a single request is processed.</p>

<h3 id="coordinator-and-worker-pattern-1">Coordinator and Worker Pattern</h3>

<p>In Eclipse Theia, you implement the <em>Coordinator and Worker Pattern</em> by using <em>Subagents</em> via the built-in <em>Tool Function</em> <code class="language-plaintext highlighter-rouge">delegateToAgent</code>. Using a subagent means spawning a child agent within a session to handle a subtask in its own isolated context window. This can be seen in the <a href="https://github.com/eclipse-theia/theia/blob/master/packages/ai-chat/src/browser/agent-delegation-tool.ts"><code class="language-plaintext highlighter-rouge">delegateToAgent</code> implementation</a>. From a pattern perspective, this means there is a main coordinator agent that manages the overall task and delegates subtasks to specialized subagents. Each subagent call is sequential (the coordinator waits for that call to return), but the coordinator can spawn multiple subagent calls in parallel.</p>

<p>In this section, the previously created agents are converted into coordinator and worker agents.</p>

<ul>
  <li>Open the <em>.prompts/customAgents.yml</em> file</li>
  <li>Change the prompt of the <code class="language-plaintext highlighter-rouge">Research</code> agent
    <ul>
      <li>Use <code class="language-plaintext highlighter-rouge">~{delegateToAgent}</code> to delegate tasks to the <code class="language-plaintext highlighter-rouge">Gists</code> and the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent</li>
      <li>
        <p>The following snippet shows how such an agent could look like</p>

        <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Research</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Research</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a collection of links for a specific topic.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by providing links to blog posts about a specific topic.</span>
    <span class="s">To provide the necessary links use subagents to execute the following steps:</span>

    <span class="s">1. Use the Gists subagent via ~{delegateToAgent} to fetch a collection of blog posts about the specific topic.</span>
    <span class="s">2. For each of the found blog post link use the Link_Extractor subagent via ~{delegateToAgent} to fetch the content of the blog post and extract all links that are mentioned in the blog post.</span>
    <span class="s">3. Provide a collection of the extracted links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Change the prompt of the <code class="language-plaintext highlighter-rouge">Gists</code> agent
    <ul>
      <li>Remove the last step that delegates to the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent</li>
      <li>Ensure that the agent returns something at the end</li>
      <li>
        <p>The following snippet shows how such an agent could look like</p>

        <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Gists</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of links to blog posts from a GitHub Gist.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by providing links to blog posts.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Fetch the publications of Dirk Fauth in the gists of the user fipro78. Use ~{mcp_github_list_gists} to find the correct gist.</span>
    <span class="s">2. Use ~{mcp_fetch_fetch} to fetch the content of the gist with a max-length parameter of 15000.</span>
    <span class="s">3. Filter the fetched content for links about the requested information.</span>
    <span class="s">4. Provide a list of links to the relevant blog posts.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Change the prompt of the <code class="language-plaintext highlighter-rouge">Link_Extractor</code> agent
    <ul>
      <li>Remove the iteration, as now the agent is called once per blog post</li>
      <li>Ensure that the agent returns something at the end</li>
      <li>
        <p>The following snippet shows how such an agent could look like</p>

        <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">Link_Extractor</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">Link_Extractor</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s">This agent provides a list of links extracted from blog posts.</span>
  <span class="na">prompt</span><span class="pi">:</span> <span class="pi">&gt;-</span>
    <span class="s">You are an agent that helps the developer by extracting links mentioned in a blog post and providing them in a structured format.</span>

    <span class="s">To provide the necessary links execute the following steps:</span>

    <span class="s">1. Use ~{mcp_fetch_fetch} to fetch the content of the blog post with a max-length parameter of 15000.</span>
    <span class="s">2. Collect all links that are mentioned in the blog post and relevant for the topic.</span>
    <span class="s">3. Filter out duplicate links and links that are not relevant for the topic. Relevance can be determined by the presence of keywords related to the topic in the context of the link.</span>
    <span class="s">4. Provide a collection of the extracted filtered links ordered by the blog post they are mentioned in. Use the anchor text as the name of the link if available. If the anchor text is not available, use the URL as the name of the link. Order them alphabetically by the name of the link.</span>

  <span class="na">defaultLLM</span><span class="pi">:</span> <span class="s">default/universal</span>
  <span class="na">showInChat</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>Use the <em>Custom Agent</em> <code class="language-plaintext highlighter-rouge">Research</code> by selecting it in the chat prompt via <code class="language-plaintext highlighter-rouge">@</code> syntax and add the prompt to execute, for example <code class="language-plaintext highlighter-rouge">@Research show links about theia</code>.
    <figure class="">
<img src="/blog/assets/images/theia_select_custom_agent_research.png" alt="" /></figure>
  </li>
</ul>

<p>I noticed that it depends on the model used whether subagent calls are executed sequentially or in parallel. For example, with <em>gemini-3.1-flash-lite-preview</em> in the free tier, the <code class="language-plaintext highlighter-rouge">delegateToAgent</code> tool calls were executed sequentially. When executing the same workflow with <em>gpt-5.4</em>, the calls were executed in parallel, as you can see in the following screenshot:</p>

<figure class="">
  <img src="/blog/assets/images/theia_coordinate_response.png" alt="" /></figure>

<p>Theia does not yet support context window monitoring like Visual Studio Code, so we cannot inspect detailed context usage. This feature has been requested via <a href="https://github.com/eclipse-theia/theia/issues/16779">Context window inspection / analysis command</a>. For several LLMs, token usage can still be inspected via <em>AI Configuration</em> by switching to the <em>Token Usage</em> tab. I tested the example using <em>GPT-5.3-Codex</em> hosted on Azure, <em>GPT-4o</em> via Copilot, and <em>Gemini 3.1 Flash Lite Preview</em> via Google AI Studio in the Free Tier.</p>

<figure class="">
  <img src="/blog/assets/images/theia_token_coordinate.png" alt="" /></figure>

<p><em><strong>Note:</strong></em><br />
The token counts reported for Gemini models are incorrect in Theia 1.69.0. I created the ticket <a href="https://github.com/eclipse-theia/theia/issues/17165">Token usage shows incorrect values for Gemini models</a> and submitted a pull request that fixes this issue. The screenshot above shows token usage with the fix applied for a fair comparison.</p>

<p>Interestingly, token usage for the <em>Coordinator and Worker Pattern</em> in Theia uses fewer tokens than the <em>Delegate Pattern</em> but still slightly more than the single-agent solution.</p>

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

<p>There is no single “best” orchestration pattern for every scenario. The right choice depends on whether your priority is simplicity, reuse, user guidance, or token efficiency.</p>

<p>For quick implementations and straightforward tasks, a single agent is often the easiest and most reliable option. If you want to split responsibilities into reusable building blocks and keep a guided user flow, delegation is a good fit. If you need better context isolation and potentially lower token usage for larger workflows, a coordinator with specialized worker subagents is usually the strongest approach.</p>

<p>The key takeaway is to treat orchestration as an architectural decision, not just a prompt-writing detail. In Visual Studio Code, the selected orchestration pattern can have a clear impact on token usage, while in Eclipse Theia the impact is almost negligible in this scenario. Start with the simplest setup that works, measure behavior and token usage with your target model, and then evolve toward delegation or coordinator-worker designs when your workflow grows in complexity.</p>

<p>As agent tooling in Visual Studio Code and Eclipse Theia continues to evolve, these patterns will likely become even more powerful. Revisit your agent design regularly to benefit from new capabilities and improved model behavior.</p>]]></content><author><name>Dirk Fauth</name></author><category term="fipro" /><category term="vscode" /><category term="typescript" /><category term="theia" /><category term="ai" /><category term="agents" /><summary type="html"><![CDATA[When talking about AI agents, we now often refer to Agentic AI workflows, where multiple agents collaborate to achieve complex goals. When creating Custom Agents, you need to consider collaboration options, especially if you design your agents for dedicated tasks rather than having one agent do everything. This is important because you should keep the context of your agent or prompt as minimal as possible while still providing enough information to achieve the desired results. Creating agents for dedicated tasks with a limited scope/context is similar to the encapsulation principle in object-oriented programming, and similarly, you need to consider processing or orchestration patterns.]]></summary></entry><entry><title type="html">Eclipse UI Best Practices and Icon Testing</title><link href="https://vogella.com/blog/eclipse-ui-best-practices-and-icon-replacement/" rel="alternate" type="text/html" title="Eclipse UI Best Practices and Icon Testing" /><published>2026-03-18T00:00:00+00:00</published><updated>2026-03-18T00:00:00+00:00</updated><id>https://vogella.com/blog/eclipse-ui-best-practices-and-icon-replacement</id><content type="html" xml:base="https://vogella.com/blog/eclipse-ui-best-practices-and-icon-replacement/"><![CDATA[<p>The <a href="https://github.com/eclipse-platform/ui-best-practices/">Eclipse UI Best Practices</a> project works on icon packs with modernized icons for the Eclipse IDE.</p>

<p><img src="/blog/assets/images/eclipse-dual-tone-icons.png" alt="Eclipse IDE with dual-tone icons" /></p>

<h2 id="testing-new-icons-in-your-ide">Testing New Icons in Your IDE</h2>

<p>To try them out in your existing Eclipse installation, you can use the provided <code class="language-plaintext highlighter-rouge">replace_icons.sh</code> script.</p>

<h3 id="prerequisites">Prerequisites</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">jq</code> (for JSON parsing)</li>
  <li>A JDK with the <code class="language-plaintext highlighter-rouge">jar</code> command</li>
</ul>

<h3 id="steps">Steps</h3>

<ol>
  <li>Clone the repository:</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/eclipse-platform/ui-best-practices.git
<span class="nb">cd </span>ui-best-practices
</code></pre></div></div>

<ol>
  <li>Run the replacement script, pointing it to your Eclipse <code class="language-plaintext highlighter-rouge">plugins</code> directory and the desired icon mapping file:</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./scripts/replace_icons.sh /path/to/eclipse/plugins iconpacks/eclipse-dual-tone/icon-mapping.json
</code></pre></div></div>

<p>The script reads the JSON mapping file, locates each icon in the icon pack, and replaces the corresponding icons inside the Eclipse plugins — whether they are folder-based or packaged as JARs.</p>

<ol>
  <li>Restart Eclipse with the <code class="language-plaintext highlighter-rouge">-clean -clearPersistedState</code> flags to clear the icon cache:</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./eclipse <span class="nt">-clean</span> <span class="nt">-clearPersistedState</span>
</code></pre></div></div>

<p>You should now see the updated icons in your IDE.</p>

<p><strong>Note:</strong> To see the new icons in EGit, you need a nightly build of EGit, as it only recently moved to SVG images.</p>

<h2 id="kudos-to-the-contributors">Kudos to the Contributors</h2>

<p>This project would not exist without the dedicated work of its contributors. Special thanks to <strong>Jasmin Hundt</strong>, <strong>Matthias Becker</strong>, <strong>Daniela Stegaru</strong>, and <strong>Michael Bangas</strong>. Their efforts are making the Eclipse IDE more visually modern and consistent.</p>]]></content><author><name>Lars Vogel</name></author><category term="eclipse" /><category term="rcp" /><summary type="html"><![CDATA[The Eclipse UI Best Practices project works on icon packs with modernized icons for the Eclipse IDE.]]></summary></entry></feed>