DEV Community

Filipp Mishchenko
Filipp Mishchenko

Posted on

Part 3: From an Agent-Ready Queue to a Scheduled AI Worker

The Original Idea

The first version of Personal Task Assistant was built around one product idea:

Stop manually figuring out what to delegate to AI. Let the task system surface agent-ready work.

That idea is still the center of the project.

But the implementation has moved from "a task board with an AI queue" to
something more operational:

  • an agent can atomically claim work;
  • source ingestion can be retried without creating duplicate tasks;
  • MCP clients can operate the board without custom API glue;
  • the MCP tool surface has a repeatable evaluation process;
  • future Codex and Claude sessions are routed by AGENTS.md and project skills;
  • a daily ritual can write a deterministic digest and, when explicitly enabled, let Claude work the Codex queue through only the task tools.

The question changed from:

Which tasks can AI take?

to:

How can an agent show up every day, move safe work forward, and still leave the human in control?

Why a Queue Was Not Enough

The first public drafts focused on routing:

  • human-owned work;
  • Codex-owned work;
  • review-ready output;
  • blocked work;
  • unassigned work.

That split is useful, but it is not the full operating model.

Once the queue exists, the next problems appear quickly:

  1. The agent needs a precise contract, not a UI to inspect.
  2. Multiple runs should not accidentally take the same task.
  3. Source adapters will retry messages, emails, or webhooks.
  4. Agent tool descriptions need to be tested, not just written.
  5. A scheduled worker needs hard limits in code, not only instructions in a prompt.

That is where the recent versions went.

Atomic Claim

Version 0.4.0 added:

POST /api/agent/claim
Enter fullscreen mode Exit fullscreen mode

The agent no longer reads a list and hopes the first task is still available.
Instead, the server picks the top-ranked codex/backlog task, conditionally
moves it to in_progress, and returns it.

In SQLite this is a conditional update. In Firestore this is a transaction.

If another worker already claimed the task, the current worker tries the next
candidate. If nothing is claimable, the JSON API returns 404; the MCP tool
wraps that into a readable "No claimable task" message.

This matters even for a personal tool. As soon as you have scheduled runs,
manual starts, or multiple agent windows, "two agents took the same card" becomes
a real failure mode.

Idempotent Ingest

The same release added external_id.

Adapters can pass stable source ids such as:

telegram:<chat_id>:<message_id>:<line>
gmail:<thread_id>:<message_id>:<n>
jira:PROJ-42
Enter fullscreen mode Exit fullscreen mode

Context ingest now reports repeated items as duplicates instead of creating new
tasks. Direct task creation with an existing external_id returns a conflict.

This is not a glamorous feature, but it is essential for real inbox and chat
automation. Polling jobs retry. Webhooks replay. Offsets get lost. The task
system has to survive that.

MCP Became the Native Agent Interface

Version 0.5.0 added an MCP server:

adapters/task_assistant_mcp.py
Enter fullscreen mode Exit fullscreen mode

It wraps the JSON API as nine tools:

  • task_assistant_get_queue;
  • task_assistant_queue_summary;
  • task_assistant_claim_task;
  • task_assistant_finish_task;
  • task_assistant_create_task;
  • task_assistant_update_task;
  • task_assistant_list_tasks;
  • task_assistant_ingest_context;
  • task_assistant_due_reminders.

The intended workflow is:

read the queue
-> claim a task
-> do the work
-> finish the task
-> human reviews it in waiting_review
Enter fullscreen mode Exit fullscreen mode

That last step is the product principle.

For AI-agent workflows, done is often the wrong state immediately after agent
execution. If an agent drafts an email, prepares a fix, writes notes, or finds a
blocker, the human usually still needs to accept the outcome.

So the normal terminal state for agent work is:

waiting_review
Enter fullscreen mode Exit fullscreen mode

not:

done
Enter fullscreen mode Exit fullscreen mode

Evaluating the Tool Surface

Once the MCP server existed, I needed a way to test whether an agent could
actually use it.

Version 0.5.1 added evals/:

  • a deterministic 16-task board;
  • 10 read-only questions with single verifiable answers;
  • a free verify_answers.py ground-truth check that uses the real MCP server;
  • an optional LLM evaluation harness that gives Claude only the MCP tools;
  • a write-loop check for claim -> in_progress -> finish -> waiting_review.

The goal is not to prove that one model is smart.

The goal is to test whether the tool contract is clear enough.

If an eval fails, the useful question is often:

  • Is the tool name misleading?
  • Is the output too verbose or too sparse?
  • Does the error message tell the agent what to do next?
  • Does the queue summary expose the right counters?
  • Is the workflow encoded in the tools or only implied by docs?

That is a much better feedback loop than manually watching an agent struggle.

Agent Playbooks

Version 0.5.2 added AGENTS.md and project skills under .claude/skills/.

This sounds like documentation, but it is more specific than that. It is an
operating contract for future agents working in the repository.

Examples:

  • If an agent is about to work tasks from the board, read task-board-worker.
  • If it is touching the daily automation, read daily-task-ritual.
  • If it is changing MCP tools, read mcp-tool-change and run the eval gate.
  • If it is touching schema, use Alembic only.
  • If it is shipping a change, bump the version and update the changelog.

For a project that is developed with both Codex and Claude, this matters. The
repo needs to carry not just code, but repeatable agent behavior.

The Daily Ritual

Version 0.6.0 added the daily automation layer.

The schedule is not auto-installed on upgrade. The user deliberately installs
it with automation/install.sh, because it schedules an agent that acts on
their behalf.

It has two deliberately separate parts.

The first part is deterministic:

automation/daily_ritual.py
Enter fullscreen mode Exit fullscreen mode

It reads the board and writes a dated digest:

  • overdue;
  • waiting for review;
  • blocked;
  • unassigned;
  • due within 24 hours;
  • agent-ready queue.

It never mutates the board.

That means you always get a board report even if the agent layer is off or
fails.

The second part is opt-in:

automation/work_loop.py
Enter fullscreen mode Exit fullscreen mode

When DAILY_RITUAL_WORK=1 and ANTHROPIC_API_KEY are set, Claude gets only the
task_assistant_* MCP tools and the worker instructions.

The safety rails are enforced in code:

  • no shell;
  • no repository access;
  • no web access;
  • only the Personal Task Assistant MCP tools;
  • attempts to set status=done are rejected;
  • --max-tasks caps claims per run.

So the daily worker can claim work, add findings, update the task, and send it
to waiting_review. It cannot silently close the task.

The Current Boundary

The architecture now looks like this:

web UI = human operating surface
JSON API = integration contract
MCP server = native agent contract
evals = tool-surface quality gate
AGENTS.md + skills = project memory for future agents
daily ritual = scheduled report and optional bounded worker
human review = final acceptance
Enter fullscreen mode Exit fullscreen mode

This is less flashy than a fully autonomous agent with every possible tool.

But it is a safer boundary for a personal operations system.

The agent can move work forward. It can ask for input. It can create reviewable
output. It cannot pretend that a human accepted the result.

What Changed Technically

The recent releases also hardened the project:

  • Alembic migrations replaced ad-hoc schema creation;
  • the pytest suite covers task CRUD, auth, queue behavior, ingest deduplication, migrations, MCP tools, and the daily ritual;
  • Ruff and GitHub Actions run lint and tests;
  • Local Mode auth is loopback-only;
  • Firestore reads avoid pulling completed tasks during agent polling;
  • Google Sheets history writes moved off the request path;
  • the Docker image runs as a non-root user.

None of that is the headline feature. All of it matters if the app is supposed
to run every day.

Try It Locally

The local setup path is still intentionally boring:

git clone https://github.com/J3d1-fm/Personal-Task-Assistant
cd Personal-Task-Assistant
python3 scripts/setup_wizard.py
Enter fullscreen mode Exit fullscreen mode

That starts Local Mode on:

http://127.0.0.1:8000
Enter fullscreen mode Exit fullscreen mode

For an MCP client, run the app and register:

claude mcp add task-assistant \
  -e TASK_TRACKER_URL=http://127.0.0.1:8000 \
  -e TASK_TRACKER_API_KEY=<key> \
  -- /path/to/repo/.venv/bin/python /path/to/repo/adapters/task_assistant_mcp.py
Enter fullscreen mode Exit fullscreen mode

The useful first test is simple:

Read my task queue, claim the next Codex task, and finish it to review.
Enter fullscreen mode Exit fullscreen mode

What I Learned

The most useful product primitive was not "AI chat".

It was:

task status + next-action owner + review handoff
Enter fullscreen mode Exit fullscreen mode

The most useful engineering primitive was not "give the model more tools".

It was:

give the model fewer tools, make them explicit, and evaluate the contract
Enter fullscreen mode Exit fullscreen mode

That is where Personal Task Assistant is now heading.

Development continues beyond 0.6.0. I will share the next changes separately instead of mixing several releases into one article.

Repository:
https://github.com/J3d1-fm/Personal-Task-Assistant

If you are using Codex, Claude Code, or another agent as part of real work, I
would like feedback on the boundary: what should an agent be allowed to do
unattended, and what should always come back to human review?

Top comments (0)