<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>enilu&apos;s Weblog</title>
    <description>English articles by enilu</description>
    <language>en</language>
    <link>https://blog.enilu.cn/en/</link>
    <atom:link href="https://blog.enilu.cn/en/rss.xml" rel="self" type="application/rss+xml"/>
    
    
    <item>
      <title>Managing Multi-Project AI Development with an Aggregate Workspace</title>
      <link>https://blog.enilu.cn/en/2026/10/21/ai-multi-project-workspace/</link>
      <guid>https://blog.enilu.cn/en/2026/10/21/ai-multi-project-workspace/</guid>
      <description>&lt;p&gt;Recently, I brought several projects I maintain into an aggregate workspace. The aim was to give AI a single entry point for changes spanning repositories, while letting me work on several tasks at once. Putting the directories together turned out to be only the beginning. I also needed to decide how to find the right project, where to change code, how to resume a task, and what counts as finished. Here is the approach I use today.
&lt;!--break--&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-create-an-aggregate-workspace&quot;&gt;Why create an aggregate workspace?&lt;/h2&gt;

&lt;p&gt;The systems I maintain consist of independent frontend, backend and data-service repositories, along with batch-processing and automated-testing projects. A single request often touches several of them.&lt;/p&gt;

&lt;p&gt;Adding a search filter, for example, may require a frontend input, an API parameter and a condition in the data service. I usually know that call chain. An AI session needs to discover it: where the frontend lives, which API it calls, and which service is actually in use.&lt;/p&gt;

&lt;p&gt;Explaining all this again in each new conversation gets tedious. With old and new services running alongside each other, guessing from repository names is unreliable, too.&lt;/p&gt;

&lt;p&gt;I also want to develop one feature while investigating another issue, with a third task waiting for feedback from testing. If all those sessions edit the same working directory, one can switch branches underneath another. Uncommitted changes from different tasks can get mixed together. Fast code generation does not make that mess any easier to untangle.&lt;/p&gt;

&lt;p&gt;My setup therefore addresses two practical needs: helping AI find the correct projects, and giving different tasks separate places to work on code.&lt;/p&gt;

&lt;h2 id=&quot;centralize-project-entry-points-and-rules&quot;&gt;Centralize project entry points and rules&lt;/h2&gt;

&lt;p&gt;The aggregate workspace is its own Git repository. It stores project navigation, collaboration rules and helper scripts. Business projects retain their own repositories and commit histories.&lt;/p&gt;

&lt;p&gt;The project names below are generic, and the layout is simplified:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;system-workspace/
├── README.md                   # Workspace overview and reading order
├── AGENTS.md                   # AI entry point and rule routing
├── config/
│   └── projects.json           # Project registry
├── projects/                   # Project browsing entry points
│   ├── web-app/                # Junction to the original repository
│   ├── api-service/
│   └── data-service/
├── doc/ai/
│   ├── standards/current/      # Rules organized by activity
│   ├── skills/                 # Reusable workflows
│   └── prompts/                # Short prompt templates
├── scripts/                    # Project, task and workspace helpers
├── tasks/                      # Task records
└── runtime/                    # Requirements, logs and temporary output
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On Windows, I use junctions to link repositories from their original locations into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;projects/&lt;/code&gt;. This gives me one place to browse without moving the source directories.&lt;/p&gt;

&lt;p&gt;One detail matters here: &lt;strong&gt;a junction points to the original directory. Editing a file through it changes the source project directly.&lt;/strong&gt; It provides no task isolation.&lt;/p&gt;

&lt;p&gt;In this workspace, the external project entries mainly serve navigation and reference. Formal business development happens in the fixed workspace pool described below. A few tools maintained directly in the aggregate repository follow their own rules.&lt;/p&gt;

&lt;h2 id=&quot;a-project-registry-answers-which-repository-should-change&quot;&gt;A project registry answers “which repository should change?”&lt;/h2&gt;

&lt;p&gt;A collection of directory links still needs a project map that both humans and AI can read.&lt;/p&gt;

&lt;p&gt;I keep responsibilities, aliases, entry points, source repositories, branch roles and call chains in a project registry. Scripts query it or generate readable documentation. For example:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Page search feature: web-app -&amp;gt; api-service -&amp;gt; data-service
Data production job: batch-job -&amp;gt; data-service
Functional regression: test-cases
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For a request, I start with the UI entry point, follow its API calls, then find the data source. This avoids searching every repository immediately and reduces the chance of changing an obsolete interface.&lt;/p&gt;

&lt;p&gt;Branches work the same way. The registry maps roles such as development baseline, testing and production to actual branch names. Scripts resolve those roles; a branch named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; is not automatically assumed to be production.&lt;/p&gt;

&lt;p&gt;Readable project documentation is generated from that registry and checked for consistency. Maintaining one source of truth is easier than keeping separate project tables in README files, Skills and conversations.&lt;/p&gt;

&lt;p&gt;Whether registry data belongs in Git depends on who can access the repository. An internal team can share necessary project metadata, while passwords, private keys and tokens stay in private local configuration. Public examples also need internal addresses, real paths and business details removed.&lt;/p&gt;

&lt;h2 id=&quot;give-ai-a-reading-order&quot;&gt;Give AI a reading order&lt;/h2&gt;

&lt;p&gt;My entry file contains a few core constraints and routes to more specific rules, rather than every rule in full.&lt;/p&gt;

&lt;p&gt;The general reading order is:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Read the aggregate workspace README to understand the directories and project relationships.&lt;/li&gt;
  &lt;li&gt;Read the rules for the current activity: development, investigation, data processing or delivery.&lt;/li&gt;
  &lt;li&gt;Read the relevant Skill when a specific business workflow applies.&lt;/li&gt;
  &lt;li&gt;Read the target project’s README and development rules before working inside it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The aggregate layer explains where to go and which workflow to follow. The project layer explains how to write, build and verify its code.&lt;/p&gt;

&lt;p&gt;Repeatable procedures belong in Skills: how to trace a particular kind of cross-project request, which evidence to inspect first, or what to check before delivery. Short instructions with clear inputs and outputs remain prompt templates.&lt;/p&gt;

&lt;p&gt;When I change conversations or AI tools, this gives the next session context it can read again.&lt;/p&gt;

&lt;h2 id=&quot;isolate-concurrent-work-with-a-fixed-workspace-pool&quot;&gt;Isolate concurrent work with a fixed workspace pool&lt;/h2&gt;

&lt;p&gt;Separate conversations alone do not prevent code changes from interfering with each other. I use a fixed pool and assign each task an exclusive slot:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;workspace-pool/
├── ws-01/                      # Task A: add a search filter
│   ├── web-app/
│   ├── api-service/
│   └── data-service/
├── ws-02/                      # Task B: investigate an API issue
│   ├── web-app/
│   └── api-service/
└── ws-03/                      # Available for the next task
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These directories are created with Git worktrees. Each has a separate working directory while sharing Git objects with its source repository. A pool can contain every project or only a subset frequently used together.&lt;/p&gt;

&lt;p&gt;A task spanning several repositories works in the corresponding directories within one slot. Another task uses a different slot. Concurrent tasks in the same repository need distinct task branches; the same branch cannot simply be checked out in multiple worktrees at once.&lt;/p&gt;

&lt;p&gt;Why reuse fixed slots? Mainly to preserve local build preparation.&lt;/p&gt;

&lt;p&gt;Frontend &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_modules&lt;/code&gt;, Java &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;target&lt;/code&gt; directories, Gradle caches and IDE indexes all take time to recreate. Releasing a slot preserves those caches, while code and branches are prepared for the next task.&lt;/p&gt;

&lt;p&gt;That also means a reused directory does not automatically contain valid dependencies or build output. Dependency changes still require installation, and a clean rebuild is sometimes necessary.&lt;/p&gt;

&lt;p&gt;The pool manifest records slot ownership. Management scripts use a file lock and atomic writes to coordinate allocation. An occupied requested slot produces a conflict; an exhausted pool requires releasing completed work or expanding capacity. The workflow does not quietly create another directory to bypass allocation checks.&lt;/p&gt;

&lt;h2 id=&quot;use-different-modes-for-development-and-investigation&quot;&gt;Use different modes for development and investigation&lt;/h2&gt;

&lt;p&gt;I distinguish two ways of using a slot:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Mode&lt;/th&gt;
      &lt;th&gt;Behavior&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Development&lt;/td&gt;
      &lt;td&gt;Create or resume a task branch from a confirmed baseline, then develop and verify changes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Investigation&lt;/td&gt;
      &lt;td&gt;Fetch the target remote branch, freeze a specific commit, and inspect it with detached HEAD&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Freezing the commit makes investigation evidence traceable to a particular version. If synchronization fails, old local code must not be treated as the current environment.&lt;/p&gt;

&lt;p&gt;Reading remote logs, databases or existing documents does not always require a code slot. Reading, compiling or comparing local business code does.&lt;/p&gt;

&lt;p&gt;Once an investigation identifies a problem, formal changes start by releasing that investigation slot and preparing a development branch. Finding the cause and authorizing a fix are separate steps.&lt;/p&gt;

&lt;p&gt;These examples use &lt;strong&gt;my own management scripts&lt;/strong&gt; and generic project names. They are not commands supplied by an off-the-shelf tool:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Inspect pool allocation&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;python&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scripts/task.py&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Allocate a workspace for a registered investigation task&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;python&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scripts/task.py&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;acquire&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;investigate-query&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--project&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;api-service&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--mode&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Investigation&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Allocate a workspace after development authorization and stage checks&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;python&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;scripts/task.py&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;workspace&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;acquire&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add-filter&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--project&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;web-app&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--project&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;api-service&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--mode&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Development&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;--fetch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The commands also validate task state, project scope and branch configuration. Copying the final command does not bypass those prerequisites.&lt;/p&gt;

&lt;h2 id=&quot;keep-progress-in-task-records&quot;&gt;Keep progress in task records&lt;/h2&gt;

&lt;p&gt;After separating the working directories, I still need to know where each task stands.&lt;/p&gt;

&lt;p&gt;If that information lives only in conversations, it is easy to forget which task was pushed, which is waiting for testing, and which branch needs further work.&lt;/p&gt;

&lt;p&gt;Business requests that span conversations therefore get independent Task records. At minimum, they contain an ID, the affected projects, current status and phase, next step, artifact locations, and workspace, branch and commit information.&lt;/p&gt;

&lt;p&gt;My current workflow has these phases:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Intake -&amp;gt; Analysis -&amp;gt; Solution -&amp;gt; Authorization -&amp;gt; Development
       -&amp;gt; Local verification -&amp;gt; Review -&amp;gt; Delivery -&amp;gt; Environment testing -&amp;gt; Completed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The default is to analyze the request, collect its materials and produce a proposal. Development starts after explicit authorization is recorded. When waiting for a person or an external system, the task keeps its phase and records that waiting status.&lt;/p&gt;

&lt;p&gt;Advancing a phase requires more than changing a field. Local verification needs commands and results. Delivery needs concrete commits and remote branches. Completion requires test acceptance, handover materials and a workspace release record.&lt;/p&gt;

&lt;p&gt;Some checkpoints also need human approval, including the proposal, code review and test acceptance. Approval is tied to a fingerprint of the artifact reviewed. If the artifact changes, approval must be renewed, so an old proposal cannot silently authorize delivery of different content.&lt;/p&gt;

&lt;p&gt;When testing finds a problem, the task’s saved branch and baseline restore the working context. If its original slot has been released, the workflow can allocate another slot and resume the same feature branch instead of starting an unrelated line of changes.&lt;/p&gt;

&lt;p&gt;Before releasing a workspace, scripts check for uncommitted or unpushed changes. Investigation mode also checks the frozen baseline. Releasing a slot preserves caches; completing a task is a separate action. An available directory does not mean the business change passed acceptance.&lt;/p&gt;

&lt;h2 id=&quot;separate-code-directories-still-share-runtime-resources&quot;&gt;Separate code directories still share runtime resources&lt;/h2&gt;

&lt;p&gt;An easy point to miss: &lt;strong&gt;two Git worktrees do not automatically create two runtime environments.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two frontend tasks can still compete for the same port. Two tasks using the same test database can still affect each other’s data. Shared testing branches, deployment and database changes also need coordination.&lt;/p&gt;

&lt;p&gt;The pool isolates the code working context. Running that code still requires choosing environments, allocating ports and coordinating shared resources.&lt;/p&gt;

&lt;p&gt;Slot ownership only constrains operations that use the management scripts. People or tools that edit directories directly can still disturb the working context. Everyone needs to follow the workflow, and scripts need to recheck state before important operations.&lt;/p&gt;

&lt;h2 id=&quot;make-temporary-materials-easy-to-find&quot;&gt;Make temporary materials easy to find&lt;/h2&gt;

&lt;p&gt;Concurrent tasks quickly scatter requirements, SQL, logs and test results. I keep each request’s materials together:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;runtime/
└── 20261021/
    └── add-filter/
        ├── source.md           # Requirement source
        ├── attachments/        # Original attachments
        ├── solution/           # Technical proposal
        └── run-001/            # Commands, logs and results for one run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Long-running commands also record a run ID, status and exit code. If no result appears, I first check whether the previous process is still alive before rerunning it.&lt;/p&gt;

&lt;p&gt;Requirement materials and runtime output stay out of Git by default. Knowledge worth reusing is later distilled into rules, Skills or project documentation, keeping lasting guidance separate from one task’s working files.&lt;/p&gt;

&lt;h2 id=&quot;how-i-would-build-this-incrementally&quot;&gt;How I would build this incrementally&lt;/h2&gt;

&lt;p&gt;These rules fit the multi-repository, concurrent-task workflow I currently have. Someone maintaining one or two small projects does not need every phase and approval gate from the start.&lt;/p&gt;

&lt;p&gt;I would begin with these steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create an aggregate directory and document project responsibilities, entry points and common commands.&lt;/li&gt;
  &lt;li&gt;Add a project registry and call chains to reduce repeated explanations and navigation mistakes.&lt;/li&gt;
  &lt;li&gt;Introduce separate working directories and allocation records when concurrent editing becomes necessary.&lt;/li&gt;
  &lt;li&gt;Add task records and recovery workflows when work spans conversations and testing rounds.&lt;/li&gt;
  &lt;li&gt;Move repeatedly missed checks from reminders into scripts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last step matters to me. A rule saying “do not switch the wrong branch” helps; checking the branch and uncommitted changes before allocation is more dependable. Likewise, requiring test-acceptance evidence is more useful than simply writing “test before completing.”&lt;/p&gt;

&lt;p&gt;This setup has maintenance costs. Registries need updating, rules must stay consistent, the pool consumes disk space, and someone still needs to examine verification results. It cannot decide whether a requirement is right or whether code actually meets the business need.&lt;/p&gt;

&lt;p&gt;But with project relationships, task state and code working contexts made explicit, AI can work continuously within a more predictable scope. Spending less time repeating context and untangling mixed changes is already worthwhile for me.&lt;/p&gt;
</description>
      <pubDate>Wed, 21 Oct 2026 00:00:00 +0000</pubDate>
    </item>
    
  </channel>
</rss>
