Welcome to the world of rowboat!
If you want to build an AI teammate—someone who can handle emails, analyze data, or manage schedules—you can't just start typing code into a void. You need a structure to hold everything together.
In rowboat, that structure is the Project, and the instructions for how your AI behaves are called the Workflow.
This chapter covers the absolute foundation. Before we give our AI a brain or hands, we need to build its "house" and write its "job description."
Throughout this tutorial, imagine we are building a Customer Support Helper. We want this AI to eventually read support tickets and draft replies.
In this chapter, we will create the container (The Project) for this helper and understand how to manage its configuration (The Workflow).
This system is built on two core ideas. Let's break them down using a construction analogy.
Think of a Project as the physical office building.
Without a Project, your AI has nowhere to live.
Think of the Workflow as the Employee Handbook inside that building.
One of the coolest features of rowboat is that every project has two copies of the handbook:
This allows you to safely make changes to your AI without breaking the version that is currently helping customers.
Let's look at how we define a Project in code. We use a library called Zod to define the "shape" of our data (think of it as a bouncer checking IDs).
Here is a simplified view of the Project model. Notice how it holds two workflows side-by-side.
// src/entities/models/project.ts
import { z } from "zod";
export const Project = z.object({
id: z.string().uuid(), // The unique address
name: z.string(), // "Customer Support Helper"
secret: z.string(), // API Key for external access
// The two handbooks:
draftWorkflow: Workflow, // Where we make changes
liveWorkflow: Workflow, // What runs in production
});
Explanation: When we load a project, we aren't just loading one set of instructions. We load the state of the entire building, including the safe-to-edit draft and the production-ready live version.
When you click "Create Project" in rowboat, what actually happens?
We need to ensure that when a project is born, the "Draft" and "Live" versions start exactly the same.
Let's look at the repository layer (the part of the code that talks to the database). This acts as the "Engine" ensuring our data is saved correctly.
// src/infrastructure/repositories/mongodb.projects.repository.ts
async create(data: z.infer<typeof CreateSchema>) {
const now = new Date();
const id = crypto.randomUUID(); // Create a unique ID
// We clone the workflow provided during creation
const wflow = { ...data.workflow };
// We assign the SAME workflow to both slots initially
const doc = {
...data,
_id: id,
liveWorkflow: wflow, // Production ready
draftWorkflow: wflow, // Ready for editing
};
await this.collection.insertOne(doc); // Save to MongoDB
return { ...doc, id };
}
Explanation:
UUID (a long, random string) so the project is unique.wflow).liveWorkflow and draftWorkflow. This ensures the project starts in a synchronized state.Now that our project exists, we want to edit the "Draft" version.
In the user interface, we need to decide which version to show the user. Are they in "Edit Mode" (Draft) or "View Mode" (Live)?
This logic happens in the frontend application. It determines which blueprint creates the interface.
// apps/rowboat/app/projects/[projectId]/workflow/app.tsx
// ... inside the component ...
let workflow;
if (autoPublishEnabled) {
// If auto-publish is on, we are always looking at the draft
workflow = project?.draftWorkflow;
} else {
// If manual mode, check if we want to see Live or Draft
workflow = mode === 'live' ? project?.liveWorkflow : project?.draftWorkflow;
}
Explanation:
autoPublishEnabled: If this is true, every change you make is instantly live. We just show the draft because it's effectively the live version.mode === 'live': If you turned off auto-publish, you can toggle a switch to peek at what is currently running in production vs. what you are working on.
When you save changes to your agents or tools, you are only updating the draftWorkflow.
// src/infrastructure/repositories/mongodb.projects.repository.ts
async updateDraftWorkflow(projectId: string, workflow: Workflow) {
// We only touch the 'draftWorkflow' field in the database
const result = await this.collection.findOneAndUpdate(
{ _id: projectId },
{
$set: {
draftWorkflow: workflow, // Update ONLY the draft
lastUpdatedAt: new Date().toISOString(),
}
}
);
return result;
}
Explanation:
This is the safety mechanism in action. Even if you accidentally delete all your agents in the draft, the liveWorkflow field in the database remains untouched, and your actual assistant keeps working.
We've talked about the Workflow as a "Handbook," but we haven't opened it yet. While we will dive deep into specific parts in later chapters, it's important to know that the Workflow connects everything together.
The Workflow configuration contains:
The Project is simply the box that holds this configuration secure and accessible.
You have successfully laid the foundation!
But an empty office building isn't very useful. Our assistant needs to remember things—like files, past conversations, and facts—to be effective.
In the next chapter, we will give our project a brain.
Next: Knowledge Graph & File System (The Memory)
Generated by Code IQ