Skip to content

Build an Agentic Todo Machine ​

In this tutorial you will build a fully autonomous system made of three parts:

  1. A todos blueprint β€” the data model that stores tasks and their status.
  2. An AI agent β€” given a goal, it generates a list of actionable todos and saves them to the blueprint.
  3. A poller app β€” a Node.js script that periodically fetches pending todos, executes each task (by asking the agent what to do), and marks them complete.

By the end you will have an end-to-end agentic loop running entirely inside Qelos.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       Agentic Loop                           β”‚
β”‚                                                              β”‚
β”‚   User goal  β†’  AI Agent  β†’  todos blueprint                 β”‚
β”‚                                  ↑                           β”‚
β”‚                           Poller (Node.js)                   β”‚
β”‚                         reads & executes tasks               β”‚
β”‚                           marks todos done                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Estimated time: 45–60 minutes


Prerequisites ​


Part 1 β€” Create the Todos Blueprint ​

A blueprint is a data model. You will create one called todos with the fields needed to track a task.

1a. Create the Blueprint in the Admin UI ​

  1. Navigate to Admin β†’ Blueprints β†’ + New Blueprint.

  2. Set the key to todos and the name to Todos.

  3. Add the following fields:

    KeyTypeRequiredNotes
    titleStringYesShort description of the task
    descriptionTextNoDetailed instructions for executing the task
    statusStringYesOne of: pending, in_progress, done, failed
    resultTextNoOutput produced when the task was executed
    goalStringNoThe high-level goal this todo belongs to
  4. Click Save.

1b. (Alternative) Create via the SDK ​

If you prefer code, use the Admin SDK:

typescript
import QelosAdminSDK from '@qelos/sdk/administrator';

const sdkAdmin = new QelosAdminSDK({
  appUrl: 'https://your-qelos-instance.com',
  fetch: globalThis.fetch,
});

await sdkAdmin.authentication.oAuthSignin({
  username: 'admin@example.com',
  password: 'yourpassword',
});

await sdkAdmin.manageBlueprints.create({
  key: 'todos',
  name: 'Todos',
  description: 'Agentic task queue',
  fields: [
    { key: 'title',       type: 'string', required: true  },
    { key: 'description', type: 'text',   required: false },
    { key: 'status',      type: 'string', required: true  },
    { key: 'result',      type: 'text',   required: false },
    { key: 'goal',        type: 'string', required: false },
  ],
});

console.log('Blueprint created.');

Part 2 β€” Create the Todo-Generator Agent ​

This agent receives a high-level goal and responds with a structured list of todos in JSON format.

2a. Create the Integration ​

In Settings β†’ AI Integrations β†’ + New Integration:

FieldValue
Nametodo-generator
ProviderOpenAI
Modelgpt-4o
System Prompt(see below)

System Prompt:

You are a task-planning assistant. When given a goal, you break it down into a list of concrete, actionable tasks.

Always respond with valid JSON in this exact format:
{
  "goal": "<the original goal>",
  "todos": [
    { "title": "<short title>", "description": "<step-by-step instructions for executing this task>" },
    ...
  ]
}

Do not include any text outside the JSON object. Keep titles under 80 characters. Include 3–7 tasks per goal.

Click Save.

2b. Use the Agent to Generate Todos and Save Them ​

Create a script called generate-todos.ts (or .js):

typescript
import QelosSDK from '@qelos/sdk';

const APP_URL          = 'https://your-qelos-instance.com';
const INTEGRATION_ID   = 'your-todo-generator-integration-id'; // from the UI
const USERNAME         = 'your@email.com';
const PASSWORD         = 'yourpassword';

async function main(goal: string) {
  const sdk = new QelosSDK({ appUrl: APP_URL, fetch: globalThis.fetch, forceRefresh: true });
  await sdk.authentication.oAuthSignin({ username: USERNAME, password: PASSWORD });

  // Ask the agent to plan the goal
  const response = await sdk.ai.chat.chat(INTEGRATION_ID, {
    messages: [{ role: 'user', content: `Goal: ${goal}` }],
    temperature: 0.3,
  });

  const raw = response.choices[0].message.content;

  let plan: { goal: string; todos: { title: string; description: string }[] };
  try {
    plan = JSON.parse(raw);
  } catch {
    throw new Error(`Agent returned invalid JSON:\n${raw}`);
  }

  console.log(`Creating ${plan.todos.length} todos for goal: "${plan.goal}"`);

  const todoEntities = sdk.blueprints.entitiesOf('todos');

  for (const todo of plan.todos) {
    await todoEntities.create({
      title: todo.title,
      description: todo.description,
      status: 'pending',
      goal: plan.goal,
    });
    console.log(`  βœ“ Created: ${todo.title}`);
  }

  console.log('Done.');
}

// Run: npx ts-node generate-todos.ts "Set up a CI/CD pipeline for a Node.js project"
main(process.argv[2] ?? 'Build a production-ready Node.js REST API');

Run the script:

bash
npx ts-node generate-todos.ts "Set up a CI/CD pipeline for a Node.js project"

Check the Todos section in the Qelos admin to see the generated tasks.


Part 3 β€” Build the Poller App ​

The poller app runs on a schedule, fetches pending todos one at a time, asks the agent what to do with each one, and then marks the todo as done (or failed).

3a. Create the Task-Executor Agent ​

Create a second integration called task-executor with this system prompt:

You are an AI assistant that executes tasks from a todo list.

When given a task title and description, you perform the task β€” which may involve:
- Writing code or documentation
- Drafting a plan or checklist
- Answering a technical question
- Generating configuration files

Return only the result of executing the task, ready to be stored.

3b. Write the Poller Script ​

Create poller.ts:

typescript
import QelosSDK from '@qelos/sdk';

const APP_URL        = 'https://your-qelos-instance.com';
const EXECUTOR_ID    = 'your-task-executor-integration-id';
const USERNAME       = 'your@email.com';
const PASSWORD       = 'yourpassword';
const POLL_INTERVAL  = 10_000; // milliseconds between polls

interface Todo {
  _id: string;
  title: string;
  description?: string;
  status: string;
  goal?: string;
}

async function executeTodo(sdk: QelosSDK, todo: Todo): Promise<void> {
  const todos = sdk.blueprints.entitiesOf<Todo>('todos');

  // Mark as in-progress so other pollers skip it
  await todos.update(todo._id, { status: 'in_progress' });
  console.log(`[β†’] Executing: ${todo.title}`);

  try {
    const userMessage = [
      `Task: ${todo.title}`,
      todo.description ? `Instructions: ${todo.description}` : '',
      todo.goal ? `Part of goal: "${todo.goal}"` : '',
    ]
      .filter(Boolean)
      .join('\n');

    const response = await sdk.ai.chat.chat(EXECUTOR_ID, {
      messages: [{ role: 'user', content: userMessage }],
      temperature: 0.4,
    });

    const result = response.choices[0].message.content;

    await todos.update(todo._id, { status: 'done', result });
    console.log(`[βœ“] Done:     ${todo.title}`);
  } catch (err) {
    await todos.update(todo._id, {
      status: 'failed',
      result: String(err),
    });
    console.error(`[βœ—] Failed:  ${todo.title}`, err);
  }
}

async function poll(sdk: QelosSDK): Promise<void> {
  const todos = sdk.blueprints.entitiesOf<Todo>('todos');

  const pending = await todos.getList({
    status: 'pending',
    $limit: 1,
    $sort: 'created', // oldest first
  });

  if (!pending || pending.length === 0) {
    console.log('[Β·] No pending todos. Waiting...');
    return;
  }

  await executeTodo(sdk, pending[0]);
}

async function main() {
  const sdk = new QelosSDK({ appUrl: APP_URL, fetch: globalThis.fetch, forceRefresh: true });
  await sdk.authentication.oAuthSignin({ username: USERNAME, password: PASSWORD });

  console.log(`Poller started. Checking every ${POLL_INTERVAL / 1000}s...`);

  // Run immediately, then on interval
  await poll(sdk);
  setInterval(() => poll(sdk), POLL_INTERVAL);
}

main().catch(console.error);

3c. Run the Poller ​

bash
npx ts-node poller.ts

The output will look like:

Poller started. Checking every 10s...
[β†’] Executing: Set up GitHub Actions workflow
[βœ“] Done:     Set up GitHub Actions workflow
[β†’] Executing: Configure Docker multi-stage build
[βœ“] Done:     Configure Docker multi-stage build
[Β·] No pending todos. Waiting...

Part 4 β€” Using the CLI Agent to Inspect and Interact ​

You can inspect the system at any point using the CLI.

Check how many todos are still pending ​

bash
# Use the agent to summarize the current state
qelos agent todo-generator \
  --message "How many tasks are typically still pending after a CI/CD goal?"

Manually trigger generation from the terminal ​

bash
qelos agent todo-generator \
  --message "Goal: Migrate a PostgreSQL database to a managed cloud service" \
  --export ./plan.json

Then feed the output JSON into your generate-todos.ts script.

Stream the executor's response to a file ​

bash
qelos agent task-executor \
  --stream \
  --message "Task: Write a Dockerfile for a Node.js 20 application" \
  --export ./dockerfile.md

Architecture Summary ​

generate-todos.ts
  └─ calls AI (todo-generator)
  └─ saves todos β†’ todos blueprint (status: pending)

poller.ts (running continuously)
  └─ reads todos where status = pending
  └─ marks todo β†’ in_progress
  └─ calls AI (task-executor) with title + description
  └─ stores result β†’ marks todo β†’ done (or failed)

Qelos Admin UI
  └─ lets you browse todos, check results, and requeue failed tasks

Extending the System ​

Here are some ideas for taking this further:

  • Requeue failed tasks β€” add a cron job that sets failed todos back to pending after a cooldown.
  • Priority field β€” add a priority (number) field to the blueprint and sort the poller query by -priority.
  • Webhook trigger β€” instead of polling, use a Qelos lambda (serverless function) that fires when a new todo is created.
  • Human-in-the-loop β€” add an approved boolean field; the poller only executes todos where approved = true.
  • Notifications β€” call a Slack or email webhook from the poller when all todos in a goal are complete.

Build SaaS Products Without Limits.