Course page: Lecture 4 of the Generative Software Engineering course at Nanjing University

When one agent can finish a code change in tens of seconds, and several agents advance different tasks at the same time, the pace of change in a software project no longer matches the rhythm of traditional version management. Version control still has to save snapshots, support rollback, and integrate parallel edits, but the assumptions behind commit history, conflict handling, and teamwork are all worth revisiting.
The open-ended "personal assistant that grows" assignment grounds the problem in project practice: the requirements can point at study materials, personal information, email, or a background agent, and the technology choice is almost unconstrained. That freedom leaves room to explore, and it also makes it easier for a system generated in one pass to drift away from the problem actually worth solving. Keeping a project controllable takes a history of the changes and a shrinking of every step to a scope that can be verified.
1. Version control records a way of collaborating
The snapshots, branches, commits, merges, and rebases in Git are a set of commands, and they also match a working rhythm suited to human collaboration. Developers discuss the task and divide its scope, then each modify the project; after a while the changes are gathered back together through commits and merges. Small commits keep the project in a state it can roll back to, and tie each change to the goal it had at the time.
For a person, commit messages, git blame, and the history help reconstruct why a decision appeared: what problem was being solved then, how the change evolved, and at which step an error came in. History is both a backup and a carrier of experience. Even when a route finally fails, keeping snapshots lets the project retreat to an earlier state and try again from a different direction.
Branching, merging, and rebasing in Git are the way separate edits get reconnected to one project history, and reading them as that mechanism works better than memorizing them as ritual. Student assignments rarely need a complex multi-person rebase, but an open-source project's CONTRIBUTING.md usually sets explicit rules for branches, commits, and merges; understanding those rules is what makes it possible to join collaboration on an existing project. Using agents lowers the barrier to trying a new workflow, provided you first understand what problem those rules solve.
Agents change this rhythm. Code can be generated in seconds, and tasks can be handed to many execution units at once. If those agents edit overlapping areas, merging and review may not keep up with the speed of generation. At the same time, when an agent can repeat an execution and fix faults quickly, the value of recording who wrote something wrong may decline; what may matter more is keeping which attempts worked, how a problem was corrected, and where the project should go next. Greater speed changes what history is asked to answer, while history keeps its place in the project.
2. From Git to Jujutsu: seeing the change itself more clearly
Different version management tools can be read as responses to different frictions in collaboration. Git organizes history with snapshots and commits; the explicit staging area lets a developer choose which differences enter a commit, and it also adds switching between the working tree, the staged state, and the commit.
The class traced this line of evolution from copying project directories by hand: V1, V2, and the final version scattered across different archives, hard to compare, merge, and trace reliably; tools such as SVN began to manage version changes centrally, and Git recorded the complete project state as a snapshot, which made branch exploration and local commits more natural. Each generation of tools eased the friction of the older model and left a new boundary of abstraction behind.
Jujutsu (jj) offers another approach: it manages the current working state directly as a change, and tracks the modifications through a separate change concept. That way, when a rebase rewrites commit history, the logical change stays identifiable; conflicts can also be recorded and handled together later. The working tree no longer requires developers to put content into a staging area before it counts as a manageable state.
The difference comes from the underlying abstraction. A Git commit ID identifies one snapshot and its parent commit; when a rebase attaches a change to a new base, it usually writes out new commits. Representing a commit and a logical change separately keeps the original change's identity traceable even after history is reordered, and a conflict does not have to block what follows right away. jj turns this way of working into a tool capability, reducing the burden of switching back and forth between the staging area and uncommitted work.
Neither tool is being claimed as a fit for every project. Understanding how the abstractions inside a tool affect people's work comes first: Git centers on the snapshot of the file tree, and Jujutsu puts further emphasis on the change in progress. Understand what problem these designs address, then compare their trade-offs in conflicts, commit granularity, and multi-person collaboration before deciding which model suits the task at hand.
3. Parallel agents need explicit boundaries
Human collaboration often uses optimistic concurrency: each person pushes forward with work they believe will not conflict, and the few conflicts are handled when the changes are gathered. Part of what makes this work is that people communicate in advance, claim tasks, and commit at a limited rate. Several agents, though, may modify the same module within a very short window, especially when the task description is broad and shared state and interfaces are not clearly defined, so conflicts and rework grow more frequent.
One workable practice today is to let different agents work in isolated worktrees or containers, and let whoever owns integration inspect, test, and merge. Isolation keeps working trees from overwriting one another, and it does not automatically remove semantic conflicts; the changes still have to be coordinated back inside the same project.
The Git worktree is one lightweight form of isolation: several working directories can check out different branches while sharing the same local repository. An agent writes files in its own worktree without contending for one working copy; when it finishes, the interfaces, tests, and goals still have to be confirmed as compatible before the edits are integrated. Putting agents into separate containers or remote repositories isolates them more strongly, and the cost of exchanging commits and resolving conflicts rises with it.
Another possible direction is to declare the task's scope before execution begins, and lock the files, modules, or resources that could affect one another. Overlapping scopes wait their turn; disjoint work still runs in parallel. That lowers the conflict rate, though the coarser the lock, the less room remains for parallelism, and the finer the lock's granularity, the more complex coordination and lock maintenance become. Locking therefore starts with the project expressing task boundaries clearly; saying "add more concurrency" does not produce a lock by itself. This is a design idea aimed at agent workflows, and it should be kept distinct from the isolated workspaces existing tools provide.
This resembles optimistic concurrency in databases and locks in operating systems. The optimistic approach lets operations run first and checks for conflicts at commit time; a lock requires declaring the resources to be used before execution. Human teams can often divide tasks in advance through meetings, and their commit rate is lower, so "do it first and resolve conflicts later" usually works. When agents execute faster and task splitting is denser, coordinating resources in advance may pay off. Locks still reduce the parallelism of overlapping work, and whether they are worth it depends on the cost of conflicts, the granularity of the lock, and the project's overall throughput.
4. Project traceability covers more than code
A software project usually keeps requirements, design documents, tests, and code in different files. Many implicit relationships run between them: which implementations a requirement corresponds to, which test verifies which constraint, and which documents a design change requires updating. A file system makes this content easy to store, and it does not maintain the connections between them automatically; when the code has changed and a document still carries the old assumption, the project accumulates technical debt.
For example, optimizing performance by replacing a linked-list implementation with a tree structure: the code may already have changed, while the design document still says "the data is maintained by a linked list", and the related tests may not cover the new constraint. The relationships among code, documents, and tests have not disappeared; they are scattered across repository files, commit records, and chat history that has been lost, and a later maintainer or agent may be unable to tell which one is the standard.
A future-facing way of representing a project could record these relationships more explicitly, treating requirements, implementations, tests, and explanations as objects related to one another. When a change only needs to look at the relevant parts, an agent can generate a smaller working view from the task: list the design, code, and tests affected, and after the change is made check whether they are still consistent. This resembles a database view that organizes the same data around a question, and it can be presented as a board containing only the information relevant to the current change, reducing the burden of re-understanding the whole repository on every edit.
A structured repository like this is still a direction under exploration, and not every project needs to migrate to a database today. It points at a long-term problem: the more complex a project becomes, the more it needs to know how different work products correspond; agents can help maintain these relationships, provided the task and the acceptance criteria are clear enough.
5. First make clear what the personal assistant has to solve
A "personal assistant that grows" can extend into many features: organizing course materials, searching research literature, filling in forms, handling email, collecting information from external websites, or running continuously in the background. Each direction can be argued for convincingly, and that does not mean they all belong in the first version.
Hand a broad description straight to an agent, and it may quickly fill in a database, an interface layer, object storage, a task queue, and complex business processes. A plan like that matches the shape of a common information system and sounds convincing when explained; but if the core requirement is for students to study more efficiently, the genuinely useful first step may be a research assistant in the browser: one that collects external material, organizes knowledge against the student's own progress, and offers a suitable entry point for review. Treating every optional example as a hard specification keeps the agent busy building infrastructure without verifying that core capability.
Whether the specification expresses priorities matters more than whether the description is detailed or short. Vague intent needs further clarification, and too many requirements with no layering scatter the model's attention onto secondary implementation details. The problems that must be solved, the acceptance results, and the scope to defer can be marked out first, then the agent can be asked to discuss possible plans; when a plan drifts away from the goal, returning to the intent and the success conditions works better than appending more implementation instructions.
Closing the distance between intention and implementation starts with judging the pain point that actually needs solving, and then writing the success conditions as results that can be checked. Which concrete task should the personal assistant help the user with first? What output is enough to prove it is useful? Which capabilities can wait until after validation? Answering these questions constrains the direction of implementation better than piling up more feature descriptions.
6. Validate value with a minimal version, then leave room for change
The purpose of a minimum viable product (MVP) is to verify early whether anyone needs this result, not to shrink the system into a half-finished product that still cannot be used. The education product example in the class shows the difference: a product built around Tower of Hanoi practice can first let students write code, run the program, and observe the visual result; once that learning path is confirmed to hold, an AI teaching assistant invoked on demand and a more complete service can be added.
The Tower of Hanoi variant requires disks to move in one direction only, and students need to understand recursion, observe the run results, and find the errors in the program. The product capabilities worth verifying first are whether the code runs, whether errors can be seen, and whether the visualization helps understanding. The AI teaching assistant can be a next-layer service the user requests on their own initiative, giving a targeted hint based on this attempt; if the basic exercise has not been shown to be useful and a complete paid system gets built first, the risk is only hidden behind a fancier interface.
If the product's core value lies in saving every attempt a student makes, letting them look back at how the code changed, and having AI give feedback on a state, the early design should center on those needs. Each run or milestone edit can form a Git record that can be traced back; the assistant's feedback can be tied to the commit at the time and kept as a branch or an annotation where necessary. Students can then compare different attempts, and the product can explain which version a piece of advice refers to.
Compared with bringing in a complete database, object storage, and an asynchronous task system at the start, organizing data first with familiar files and version history may make it easier to adjust directories and record formats quickly. Folders and files are a model most people already know; a database schema, migrations, and task state demand more design up front. When early data structures change often, the latter can put a migration burden on every experiment.
A file system does not always beat a database. Once data volume, query needs, concurrency scale, or persistence requirements change, migrating to a database can become worthwhile; the real access patterns in the codebase can first establish which queries and consistency requirements have already appeared, and then a storage structure gets chosen for them. The aim is to avoid paying architecture costs in advance for a scale that has not been verified. A project can start from the representation that is easiest to understand and adjust, and once the data relationships and usage patterns stabilize, choose a more suitable storage and service structure.
7. Project progress comes from task design and continuous integration
Version control provides the mechanisms for snapshots, isolation, and merging; the hard part remains deciding what to do next, who does it, how completion is proved, and which changes can be integrated safely. Adding more agents does not automatically produce an efficient team; if the architecture, shared interfaces, and task scopes are not clear, more execution units can mean conflicts and rework accumulate faster.
The safer order is for a person and a stronger agent to clarify the goal and discuss the architecture and acceptance conditions together, then hand well-bounded work to several execution units. Whoever is responsible has to settle which interfaces are shared, which files belong to which task, how completion conditions get checked, and when changes are gathered. Suppose one agent changes the data format while another writes pages against the old format at the same time; without an agreed interface and integration point, each task may look correct on its own and fail to run together.
Every change should be small enough to inspect, test, and revert on its own, and integration confirms that it is compatible with the other work. Version history still matters, but whether a team moves forward depends more on task breakdown and feedback cycles. The cost of human communication rises with the number of participants, and agents, faster as they execute, still need explicit boundaries, shared interfaces, and an integration strategy. That is how the speed of agents becomes progress on the project, instead of a faster batch of changes that are difficult to explain to one another.
Version control is a tool, and it does not replace engineering judgment. The more agents can execute in parallel, the more a person has to decide which things should run in parallel, which state must be shared, and what product value is most worth verifying at this stage. Reliable software does not take shape on its own after one generation; it grows gradually out of clear intent, traceable changes, and continuous verification.
