135 lines
4.5 KiB
Markdown
135 lines
4.5 KiB
Markdown
# The Agentic Workflow Loop
|
|
|
|
Gadget Code is a software engineering Integrated Development Environment (IDE) built around an Agentic Workflow Loop (AWL). The User can edit project files by hand, and also ask Gadget to do work for them in the project.
|
|
|
|
A technical chat interface is offered as the Chat Session view where the User can send a prompt to Gadget for processing. The prompt is packaged as a Work Order, and sent to an available Gadget Drone for processing. The Agentic Workflow Loop resides and executes within the Gadget Drone.
|
|
|
|
The browser is used as a remote control of sorts for Agentic Workflow Loops running in the User's drones on the user's behalf. As the agent is performing work, it emits messages back to the browser (using Socket.IO by way of the web server) to update the User's display in the Chat Session view.
|
|
|
|
The loop starts when the User submits a prompt for processing, and ends when an AI API call responds with zero tool calls. If a response contains tool calls, the loop calls the tools, inserts "tool" role response messages in the context, and remains in the loop to submit the context back to the model for additional thinking, response, and tool call responses.
|
|
|
|
## The Work Order
|
|
|
|
Each Gadget Drone registered by the User implements a named Bull job queue. Gadget Code will create a Work Order job for the Drone to process.
|
|
|
|
The Work Order is a JSON object that contains information about the project, AI service provider and model, and also the User's prompt.
|
|
|
|
````typescript
|
|
/*
|
|
* Chat session context is made of chat messages with a timestamp and role. The
|
|
* role is "system", "user", "assistant", or "tool". The assistant can have
|
|
* reasoning or "thinking" content. When thinking content is being included, we
|
|
* merge the thinking + response by enclosing the thinking content in a
|
|
* <thinking> element in the content.
|
|
*
|
|
* ```
|
|
* <thinking>I need to research the project...</thinking>
|
|
* I found the bug!
|
|
* ```
|
|
*/
|
|
interface IChatMessage {
|
|
createdAt: Date;
|
|
role: string;
|
|
content: string;
|
|
}
|
|
|
|
interface IWorkOrder {
|
|
project: {
|
|
_id: string;
|
|
name: string;
|
|
slug: string;
|
|
gitUrl: string;
|
|
};
|
|
session: {
|
|
name: string;
|
|
mode: string;
|
|
};
|
|
provider: {
|
|
baseUrl: string;
|
|
apiKey: string;
|
|
modelId: string;
|
|
params: {
|
|
temperature: number;
|
|
topP: number;
|
|
topK: number;
|
|
};
|
|
};
|
|
context: IChatMessage[];
|
|
prompt: string;
|
|
}
|
|
````
|
|
|
|
## The Workflow Loop
|
|
|
|
When a Work Order job is received a Gadget Drone, it performs a series of steps in preparation to work on the prompt contained in the work order.
|
|
|
|
1. Check if the workspace directory currently has the project cloned. If not, clone the project from git into the workspace directory.
|
|
1. Instantiate the correct AI API client object configured with the credentials provided in the work order.
|
|
|
|
```typescript
|
|
/*
|
|
* This is just pseudocode that shows the intent. It does not account for every
|
|
* aspect of the loop.
|
|
*/
|
|
async function workflowLoop(
|
|
session: IChatSession,
|
|
provider: IAiProvider,
|
|
llm: string,
|
|
userPrompt: string,
|
|
): Promise<void> {
|
|
const NOW = new Date();
|
|
|
|
/*
|
|
* Create the ChatTurn in the database at the *start* of the turn, and update
|
|
* it with information as the turn executes. This helps ensure we don't lose
|
|
* work conducted in a turn if an error happens. A turn should be able to be
|
|
* resumed whenever possible.
|
|
*/
|
|
|
|
const turn = new ChatTurn();
|
|
turn.createdAt = NOW;
|
|
turn.status = ChatTurnStatus.Processing;
|
|
|
|
turn.user = session.user;
|
|
turn.provider = provider;
|
|
turn.llm = llm;
|
|
turn.mode = session.mode;
|
|
turn.prompt = userPrompt;
|
|
|
|
turn.stats = {
|
|
toolCallCount: 0,
|
|
inputTokens: 0,
|
|
thinkintTokenCount: 0,
|
|
responseTokens: 0,
|
|
durationMs: 0,
|
|
durationLabel: "pending",
|
|
};
|
|
|
|
await turn.save();
|
|
|
|
/* emit Socket.IO message with turn
|
|
|
|
// this turn's context (system, history, prompt, work)
|
|
const messages = [];
|
|
|
|
const systemPrompt = buildSystemPrompt(session);
|
|
messages.push({ role: "system", content: systemPrompt });
|
|
|
|
// recall full session history into messages array
|
|
buildSessionContext(session, messages);
|
|
|
|
// push the User's latest prompt to the context
|
|
messages.push({ role: "user", content: userPrompt });
|
|
|
|
let keepProcessing = true;
|
|
do {
|
|
const response = await aiApiCall(messages);
|
|
keeProcessing = response.tool_calls.length > 0;
|
|
for (const tool_call of response.tool_calls) {
|
|
const response = await callTool(tool_call.name, tool_call.args);
|
|
messages.push({ role: "tool", content: response });
|
|
}
|
|
} while (keepProcessing);
|
|
}
|
|
```
|