Skip to content

Agent controller roadmap

Post-decoupling features to build on the typed API + event model that Chunk 3 of agent-dispatch-unification delivers. Each is “new fields + a new producer/consumer,” never new IRC-command scraping — that leverage is the whole payoff of the decoupling.

Sequencing: build AFTER the Chunk 3 cutover (a clean, ergo-free, API-driven controller), except where noted. #4 (governance / judge / auto-approve scoping) is deliberately deferred — not in this roadmap.

Reference: turnstone (k0s/turnstone) already implements all of these — config.yaml, scheduled-tasks.toml, personas.toml, skills/. Borrow the shapes; the scheduled-tasks.toml header is essentially a spec sheet for 1–3.


1. Scheduled tasks — cron-created tasks ✅ BUILT 2026-08-25

What. The controller creates tasks on a schedule (nightly repo digest, recurring checks), not only on-demand. Replaces ad-hoc crons with first-class, git-tracked recurring work. Shape. A scheduled_tasks table {name, schedule_type (cron|at), cron_expr/at_time, repo, model, persona, skill, initial_message (the task prompt), notify_targets, enabled} + a scheduler goroutine that fires due entries by calling the SAME domain op the API does (createTask / POST /v1/tasks). New endpoints POST/GET/DELETE /v1/schedules; reconciled from a git-tracked toml exactly like turnstone’s scheduled-tasks-import. Depends. Nothing IRC — cleanly separable, could even be an early standalone win. Highest value. Built (7d2d07a1 engine, 0c75beb3 import+deploy). internal/schedule — a dependency-free 5-field cron evaluator (Vixie dom/dow rule, Sunday 0|7), the Sched type, Validate, and the Scheduler runner (re-reads each tick; fires at-most-once per due schedule, marking the fire clock BEFORE firing so a failure skips rather than double-spawns). store migration #3 = scheduled_tasks; RunScheduled (create+ready) is the fire hook; /v1/schedules CRUD behind a separate ScheduleStore dep. Declarative import = a git-tracked YAML (scheduled-tasks.yaml, AGENT_SCHEDULES_PATH) reconciled (upsert-only) at startup, delivered as the name-hashed agent-controller-schedules ConfigMap. Shipped INERT (empty list). We used YAML not TOML — no local Go to hand-manage a TOML dep’s go.sum, and yaml.v3 was already in the module graph. Deferred within #1: persona/skill/notify are persisted but not yet consumed (they are features #2/#5/#3).

2. Personas — reusable behavior configs ✅ BUILT 2026-08-25

What. A named persona (system prompt / voice / defaults) a task references, so setup is DRY and consistent — turnstone’s glados-slack. Shape. A personas store/config {name, system_prompt, default_model?, …} + a persona field on task-create, injected into the worker env (system prompt). Endpoints /v1/personas (CRUD or git-tracked import). The worker applies the persona as its system prompt. Depends. Additive: a field on task-create + worker env. The IRC adapter exposes it (!persona) only if wanted. Built (23620a85 core, 09aacef4 import+deploy). internal/persona leaf package (type + Validate + YAML loader). store migration #4 = tasks.persona column (mirrored like the other task fields, reconstructs on restart), #5 = personas table + CRUD + ResolvePersona. The persona name lives on the task; its system prompt resolves FRESH at each spawn → SpawnSpec.SystemPromptLLM_APPENDED_SYSTEM_PROMPT (append, not replace — the popquiz/rrroster pattern, keeps the base safety prompt). A persona’s default MODEL applies at create via the model path (right provider) only if the task didn’t pin one. /v1/personas CRUD + declarative personas.yaml, folded with the schedules file into one agent-controller-config ConfigMap. The bot stays decoupled (primitive PersonaResolver). Shipped INERT. Not done: the !persona IRC command in the adapter (deferred — set via api/schedule for now).

3. Notify targets — deliver results anywhere ✅ BUILT 2026-08-26

What. On completion, deliver a task’s final result to configured destinations (a Slack channel, a webhook, a DM) — not just DM the IRC requester. Turnstone’s notify_targets. Shape. notify_targets on the task [{channel_type, channel_id | webhook_url}]

  • a delivery consumer that subscribes to task_completed on /v1/events and posts to each target. SHAPES 3c NOW. Delivery is a client/adapter concern (the controller emits; a deliverer sends) — the same seam as the IRC render. So design the extracted adapter to own delivery from the start, rather than baking requester-DMs into the controller. Build the general notify-target delivery after the cutover, but let this principle steer the 3c adapter design. Depends. The event stream (done) + the adapter (3c). Built (c9f17c00). internal/notify leaf (Target type — schedule.NotifyTarget now aliases it — + RenderCompletion); store migration #7 = tasks.notify_targets; task_completed carries title+status+notify_targets (so a deliverer needs only the event); RunScheduled threads a schedule’s targets. The deliverer is a CLIENT (internal/delivery, the agent-controller notify subcommand): tails /v1/events, posts task_completed to Slack (chat.postMessage) / webhook. clients-notify Deployment (replicas 0, staged like clients-irc), SLACK_BOT_TOKEN from the slack-bot-pi-mom Secret, :9101 opened to it. MVP scope: delivers a COMPLETION NOTICE (title/status), not the worker’s full output — capturing a task’s final result text is a follow-up (needs a worker-reported result field). Undeployed.

5. Skills — materialized capability scripts

What. Named skills (reusable scripts/tools) a task applies — turnstone’s kuvasz, prometheus — materialized onto the worker’s PATH so a task can call them. Shape. A skills registry {name, scripts, activation} + a skill field on task-create; the worker materializes the skill’s scripts at startup. Distribution is the hard part (a skills ConfigMap, or an OCI/Garage bundle the worker pulls). Depends. Worker-side changes; biggest lift, lowest priority — build last.


Order

Chunk 3 cutover → (1) scheduled tasks ✅ → (2) personas ✅ → (3) notify targets ✅ → (5) skills (last, biggest lift). Governance/judge (#4) deferred. Also open: Phase 4 (pi-mom → translator, calls /v1/ask) — blocked on block_actions in pi-mom; and result-text capture (a worker-reported final result, so #3 delivers output not just a completion notice).

Progress 2026-08-25: #1 + #2 built (7d2d07a1, 0c75beb3, 23620a85, 09aacef4) — the “features first” pair, both ahead of the Chunk 3 cutover and safe because neither touches the IRC seam. Next: the Slack side (Phase 3 of agent-dispatch-unification — registry split + /v1/ask + pi-mom as translator), with #3 notify-targets built as part of it. #5 skills last.