Skip to content

[Feature] Assign an Automation to a Project (and file the sessions it spawns) #5363

Description

@btli

Problem or use case

Automations (scheduled tasks) are getting genuinely useful — recurring RRULE schedules, per-task
agent, model override, reasoning effort, cost cap, pinned host and workspace. But they live
completely outside the Projects organizing model, and that gap gets worse the more automations a
user accumulates.

Two concrete pain points:

1. An Automation cannot be filed into a Project. Today the only thing that can belong to a
Project is a session: membership is a project_id pointer on the conversation metadata row
(omnigent/db/db_models.py:648-651), set through PATCH /v1/sessions/{id}
(omnigent/server/routes/sessions/routes_core.py:1990-2025). The scheduled_tasks table
(omnigent/db/db_models.py:1430) has agent_id, model_override, reasoning_effort,
max_cost_usd, workspace, host_id, timezone, state — and no project column. The
Automations page (web/src/pages/TasksPage.tsx) is a single flat list filterable only by name and
active/paused state. A user with a dozen automations across three projects has no way to see
"the automations for this project", and the sidebar's project folders can't show them at all.

2. The sessions an Automation spawns land unfiled. When an automation fires, the conversation
it creates is given agent_id, title, resolved host_id and workspace, then model_override
and reasoning_effort (omnigent/server/scheduled/fire.py:589). project_id is never
passed, so it defaults to NULL
(omnigent/stores/conversation_store/sqlalchemy_store.py:860,983). Every scheduled run therefore
drops an orphan session into the unfiled bucket, and the user has to hand-file each one after the
fact. This is the sharper of the two problems: it generates recurring manual cleanup forever,
proportional to schedule frequency.

There is also a latent duplication angle: ProjectConfig already carries host_id, workspace,
agent_id, use_worktree and base_branch (web/src/lib/projectsApi.ts:21-44) — exactly the
fields a user re-enters by hand on every automation belonging to that project.

Proposed solution

Let an Automation be assigned to a Project, and have that assignment flow through to the sessions
it spawns.

Sketch (the specification should settle the details and the open questions below):

  • Data model — add a nullable project_id to scheduled_tasks, mirroring the conversation
    metadata pointer: Uuid16(), nullable (NULL = unfiled), no DB foreign key per the schema's
    no-FK rule, with an index supporting per-owner per-project listing.
  • API — accept and return project_id on POST /v1/scheduled-tasks and
    PATCH /v1/scheduled-tasks/{id}; support GET /v1/scheduled-tasks?project=… filtering.
    Ownership must be verified against the target project the same way session filing does
    (a project owned by someone else must 404, not leak existence).
  • Runtime — when an automation fires, the spawned conversation inherits the automation's
    project_id so scheduled runs appear inside their project folder automatically instead of
    accumulating unfiled.
  • UI — a project picker on the Automation create/edit form, project grouping or a project
    filter on the Automations page, and (optionally) automations surfaced within their project's
    sidebar folder.

Alternatives considered

  • Reuse the legacy omni_project name-valued label instead of a first-class column. Rejected:
    the label path is the older half of an existing dual-read that the codebase is already
    migrating away from, it keys on mutable project name rather than id, and it would add a second
    legacy consumer to something meant to shrink.
  • Leave automations flat and only file the spawned sessions. This fixes the orphan-session
    cleanup but not discoverability — the user still cannot answer "which automations belong to this
    project", and the per-automation project would have to be inferred from run history.
  • Derive the project from the automation's pinned workspace. Rejected: workspace is a
    filesystem path, projects are a user-facing organizing concept; several projects can share a
    workspace and an automation may have no pinned workspace at all (it resolves at fire time).
  • Do nothing / let users file each run by hand. This is the status quo, and its cost grows
    linearly with schedule frequency.

Open questions for the specification

  1. Inheritance vs. explicit assignment at fire time — does the spawned session take the
    automation's project unconditionally, and what happens if the project was deleted between
    arming and firing? (Note DELETE /v1/projects/{id} deletes only the container row and leaves
    member project_id values dangling — omnigent/server/routes/projects.py:145-147 — so a fired
    automation can legitimately reference a project that no longer exists.)
  2. Project deletion semantics for automations — unfile them (null out), leave them dangling
    like sessions do today, or block deletion while automations reference the project? The web UI
    currently compensates for sessions by unfiling members before deleting
    (web/src/hooks/useConversations.ts:1752-1794); automations need a deliberate answer rather
    than an accidental one.
  3. Filter by id or by name? The Projects surfaces are inconsistent today: CRUD is by id while
    GET /v1/sessions?project= filters by name with a dual-read union
    (omnigent/stores/conversation_store/sqlalchemy_store.py:2494-2549). A new endpoint should
    probably filter by id, but the spec must say so explicitly and justify diverging from the
    existing session-side shape.
  4. Should null be accepted to unfile? Session filing rejects explicit null with a 400 and
    uses "" to unfile (routes_core.py:1995-2007). Consistency versus a cleaner API is a real
    trade-off worth stating.
  5. Does assigning a project pre-fill the automation's agent/workspace/host from ProjectConfig?
    Tempting, but it risks silently changing where an existing automation runs. Probably
    out of scope for a first cut — the spec should say yes or no rather than leave it ambiguous.
  6. Backfill — should existing automations, or the already-unfiled sessions their past runs
    created, be retroactively filed? (Likely no, but state it.)

Type of change

Enhancement spanning DB migration, server API, scheduler runtime, and web UI.

Component

comp:server (API + scheduler runtime), comp:web-ui (Automations page + project picker)

Additional context

Grounding for the above (branch staging):

  • Automations domain entity is deliberately named "scheduled task"; only UI copy says
    "Automations" (omnigent/entities/scheduled_task.py:10).
  • Fire path and the exact set of fields copied onto the spawned conversation:
    omnigent/server/scheduled/fire.py:589.
  • Automations API surface: omnigent/server/routes/scheduled_tasks.py (create :289,
    list :331, detail :365, runs :384, run-now :422, patch :465, delete :514).
    Note responses are hand-built dicts rather than response models, so adding a field touches the
    serializers directly (:113, :138).
  • Project membership pointer and dual-read filter:
    omnigent/db/db_models.py:648-651,
    omnigent/stores/conversation_store/sqlalchemy_store.py:1362-1388,2494-2549.
  • Alembic conventions: single lineage under omnigent/db/migrations/versions/, additive,
    no foreign keys, dialect-split for SQLite (recreate="always") vs PG/MySQL.

Metadata

Metadata

Assignees

Labels

FeatureNew feature or requestP2-mediumPriority: bug with workaround, important feature requestcomp:dbDatabase, persistence, and migrationscomp:serverComponent: server, API, session managementcomp:web-uiComponent: web frontend (ap-web)triagedIssue has been triaged by the bot

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions