Introduction To Parallel Workflows

In our previous lessons, we explored how to use specialized AI agents to handle complex tasks with high precision. We learned that by delegating work to subagents, we avoid context decay and keep code quality high. Now that you understand how to manage a single stream of work, it's time to look at how we can scale this process.

In a production environment, we often have multiple features waiting to be built. If Feature A and Feature B do not rely on each other, we do not have to wait for Feature A to finish before starting Feature B. This is called Parallel Development. By running these workflows at the same time, we significantly reduce calendar time — the actual days or hours it takes to deliver the project — even if the total amount of work remains the same.

In this lesson, you will learn how to identify when features can be built in parallel and how to coordinate them so they do not clash when they are merged back together.

Determining Feature Independence

Not every task can be done in parallel. If two features require changing the same line of code in the same file, they will cause a "conflict." To work in parallel, features must be independent.

We use a simple checklist to verify independence:

  1. No shared files: Aside from basic configuration or test setup, the features should live in different files.
  2. No integration dependencies: Feature A should not need code from Feature B to function.
  3. Different database tables: They should not modify the same data structures.
  4. Different API endpoints: They should provide different routes for the user.

Let's look at our target features: Task Tags and Task Reminders.

FeatureTablesFilesEndpoints
Task Tagstags, task_tagstag.py, tag_repository.py/tags
Task Remindersremindersreminder.py, reminder_repository.py/reminders

Since these use different tables and files, they are perfect candidates for parallel development. We can document this in a file called parallel-features-analysis.md to ensure our AI agents understand the boundaries.

Phase 1: Setting The Shared Foundation

Even though the features are independent, they usually share a common starting point, such as the database. If two agents try to create a database migration at the same time, they might generate conflicting version numbers. To prevent this, we use Phase 1: Foundation.

In this phase, we perform a single session to set up the infrastructure that both features will use.

First, we create a database migration that defines the tables for both features. This ensures the "ground" is ready for both tracks.

# Generate the migration
alembic revision --autogenerate -m "add tags and reminders tables"

This creates a file in alembic/versions/ with a name like abc123def456_add_tags_and_reminders.py. The migration will define the new tables:

"""add tags and reminders tables

Revision ID: abc123def456
Revises: previous_revision
Create Date: 2024-01-15 10:00:00.000000

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import UUID

revision = 'abc123def456'
down_revision = 'previous_revision'
branch_labels = None
depends_on = None


def upgrade():
    # Tables for Feature A: Tags
    op.create_table('tags',
        sa.Column('id', UUID(as_uuid=True), primary_key=True),
        sa.Column('name', sa.String(30), nullable=False),
        sa.Column('user_id', UUID(as_uuid=True), sa.ForeignKey('users.id'), nullable=False),
        sa.Column('created_at', sa.DateTime(timezone=True), nullable=False)
    )
    
    op.create_table('task_tags',
        sa.Column('task_id', UUID(as_uuid=True), sa.ForeignKey('tasks.id'), nullable=False),
        sa.Column('tag_id', UUID(as_uuid=True), sa.ForeignKey('tags.id'), nullable=False),
        sa.PrimaryKeyConstraint('task_id', 'tag_id')
    )

    # Tables for Feature B: Reminders
    op.create_table('reminders',
        sa.Column('id', UUID(as_uuid=True), primary_key=True),
        sa.Column('task_id', UUID(as_uuid=True), sa.ForeignKey('tasks.id'), nullable=False),
        sa.Column('user_id', UUID(as_uuid=True), sa.ForeignKey('users.id'), nullable=False),
        sa.Column('due_date', sa.DateTime(timezone=True), nullable=False),
        sa.Column('description', sa.String(500)),
        sa.Column('is_sent', sa.Boolean, default=False, nullable=False),
        sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
        sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False)
    )


def downgrade():
    op.drop_table('reminders')
    op.drop_table('task_tags')
    op.drop_table('tags')

Next, we verify that the foundation is solid by running the migration and checking if the models can be loaded. In your CodeSignal environment, these tools are already set up for you.

# Apply the migration
alembic upgrade head

# Verify that our Python models can see the new tables
python -c "from src.models.tag import Tag; from src.models.reminder import Reminder; print('Foundation OK')"

Output:

Foundation OK

By completing this small shared step first, we create a "safe zone" where the two parallel tracks can now run without stepping on each other's toes.

Phase 2: Executing Parallel AI Sessions

Now that the foundation is ready, we can start two separate AI sessions. The key here is context separation. We want the Tags Agent to focus only on tags, and the Reminders Agent to focus only on reminders.

If we give one agent too much information about the other feature, it creates "noise" that can lead to mistakes. We provide each agent with its own specific task list.

Session A (Task Tags) Prompt:

Implement Task Tags.
Context: @specs/task-tags/tasks.md
Foundation: Tag and TaskTag models exist in src/models/tag.py
Tasks: T001 (Models), T002 (Service), T003 (Schemas), T004 (API Routes in src/api/tags.py).

Session B (Task Reminders) Prompt:

Implement Task Reminders.
Context: @specs/task-reminders/tasks.md
Foundation: Reminder model exists in src/models/reminder.py
Tasks: T001 (Model), T002 (Service), T003 (Schemas), T004 (API Routes in src/api/reminders.py).

While these sessions run, we can track the time. Because they are independent, the total calendar time is only as long as the slowest session. If both take 12 minutes, the features are finished in 12 minutes total, rather than 24.

Phase 3: Integration And Final Validation

Once both sessions are complete, we enter the Integration Phase. This is a single session where we verify that both features work together on the same data.

To test this, we can perform a Union Test. We will create a single task and attempt to add both a tag and a reminder to it.

First, let's create a task and capture its ID.

# Create a new task
curl -X POST http://localhost:8000/api/tasks \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"title": "Parallel Test Task"}'

Output:

{"id": "550e8400-e29b-41d4-a716-446655440000", "title": "Parallel Test Task", "status": "pending", ...}

Now, we use that ID to add a tag and a reminder using the new endpoints created in the parallel sessions.

# Add a tag
curl -X POST http://localhost:8000/api/tasks/550e8400-e29b-41d4-a716-446655440000/tags \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "urgent"}'

# Add a reminder
curl -X POST http://localhost:8000/api/tasks/550e8400-e29b-41d4-a716-446655440000/reminders \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"due_date": "2024-12-31T10:00:00Z", "description": "Finish Lesson"}'

Finally, we fetch the task to ensure both pieces of data exist in the same object.

curl http://localhost:8000/api/tasks/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer <token>"

Output:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "title": "Parallel Test Task",
  "status": "pending",
  "owner_id": "...",
  "created_at": "2024-01-15T10:00:00Z",
  "updated_at": "2024-01-15T10:00:00Z",
  "tags": [{"id": "...", "name": "urgent"}],
  "reminders": [{
    "id": "...",
    "description": "Finish Lesson",
    "due_date": "2024-12-31T10:00:00Z",
    "is_sent": false
  }]
}

If the output shows both the tags and the reminders, we have successfully integrated two independent workflows!

Summary And Practice Overview

In this lesson, we covered the strategy for Two-Track Parallel Development. We learned that:

  • Independence is key: Features must use different files, tables, and endpoints to be developed simultaneously.
  • The 3-Phase Strategy keeps work organized:
    1. Foundation: Set up shared tables and models.
    2. Parallel: Run separate AI sessions with focused context.
    3. Integration: Verify that both features work together in a single environment.
  • Context Separation prevents AI confusion and reduces errors.

In the upcoming practice exercises, you will apply this knowledge in the CodeSignal IDE. You will analyze two features for independence, set up their shared foundation, and simulate the execution of parallel tracks to build a robust, multi-featured API. You're doing great — let's get to the practice!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal