Skip to content

Enable Task status updates via POST requests#174

Open
declan-scale wants to merge 6 commits intomainfrom
declan-scale/mark-task-completed
Open

Enable Task status updates via POST requests#174
declan-scale wants to merge 6 commits intomainfrom
declan-scale/mark-task-completed

Conversation

@declan-scale
Copy link
Collaborator

@declan-scale declan-scale commented Mar 24, 2026

Updates

  • Added POST requests to update tasks to a completed, terminated, timed_out, failed, or canceled state
  • Added tests to support changes
  • Only can interact with running tasks via the UI
  • UI message rendering is not dependent on a user message

resolves AGX1-153

Greptile Summary

This PR adds five new POST endpoints (/complete, /fail, /cancel, /terminate, /timeout) to transition running tasks to terminal states, along with UI changes that disable the prompt input and display the terminal status when a task is no longer running.

Key changes:

  • A new atomic transition_status repository method uses UPDATE WHERE status = expected_status to safely handle concurrent transition requests — directly addressing the race condition flagged in the previous review round.
  • _transition_to_terminal in TasksUseCase provides a unified pre-flight check (DELETED → 404, non-RUNNING → 400) and surfaces concurrent-modification conflicts as a retryable 400 rather than silently clobbering state.
  • task-messages.tsx is refactored to support agent-only message pairs (no preceding user message), enabling tasks that start with agent-initiated output.
  • Test coverage is thorough: seven new integration tests and a comprehensive new unit test file covering all five transitions, blocked-transition guards, name-based lookup, and metadata update edge cases.

Minor items to address:

  • task_repository.py: session.get() result used without a null check before model_validate — defensive guard recommended.
  • prompt-input.tsx: 'RUNNING' is a hardcoded magic string; an enum reference from the SDK (if available) is preferred per the project style guide.

Confidence Score: 4/5

  • Safe to merge after addressing the two minor P2 items; the core atomic transition logic is solid.
  • The previously flagged race condition is properly resolved with an atomic WHERE-guarded UPDATE. Business logic, authorization, error handling, and event publishing are all correctly layered. Two small polish items remain: a defensive null check on the post-UPDATE session.get() result, and replacing the hardcoded 'RUNNING' string with an enum reference. Neither blocks correctness in practice.
  • agentex/src/domain/repositories/task_repository.py (missing null guard) and agentex-ui/components/primary-content/prompt-input.tsx (hardcoded status string).

Important Files Changed

Filename Overview
agentex/src/domain/repositories/task_repository.py Adds transition_status method using an atomic UPDATE WHERE status = expected_status pattern to handle concurrent modifications correctly; minor: missing null check on session.get() result.
agentex/src/domain/services/task_service.py Adds transition_task_status service method that delegates to the repository and publishes a task_updated stream event on success; error is swallowed (logged only) to avoid masking the status change, consistent with the existing update_task pattern.
agentex/src/domain/use_cases/tasks_use_case.py Adds _transition_to_terminal helper and five public methods (complete/fail/cancel/terminate/timeout). The previous race condition concern is addressed via the atomic UPDATE; concurrent modification is surfaced as a 400 with a retry hint.
agentex/src/api/routes/tasks.py Adds five POST endpoints (/complete, /fail, /cancel, /terminate, /timeout) with optional TaskStatusReasonRequest body; authorization and routing look correct.
agentex/src/api/schemas/tasks.py Adds TaskStatusReasonRequest schema with a single optional reason field; straightforward and correct.
agentex-ui/components/primary-content/prompt-input.tsx Adds isTaskTerminal computed flag to disable the prompt input and show task status in the placeholder when a task reaches a terminal state; uses a hardcoded 'RUNNING' string instead of an enum reference.
agentex-ui/components/task-messages/task-messages.tsx Refactors message-pair building to allow agent-only pairs (no preceding user message) by tracking a pairStarted flag and making userMessage nullable; the "thinking" shimmer now only shows when a user message is present.
agentex/tests/integration/api/tasks/test_tasks_api.py Adds seven integration tests covering happy-path transitions, default-reason fallback, and the double-transition guard; well-structured and thorough.
agentex/tests/unit/use_cases/test_tasks_use_case.py New unit test file with comprehensive parametrized coverage of all five terminal transitions, blocked-transition guards for each terminal state, name-based lookup, and metadata update edge cases.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Route as POST /tasks/{id}/complete
    participant UseCase as TasksUseCase
    participant Service as AgentTaskService
    participant Repo as TaskRepository
    participant DB as PostgreSQL
    participant Stream as Redis Stream

    Client->>Route: POST /tasks/{task_id}/complete {reason}
    Route->>UseCase: complete_task(id, reason)
    UseCase->>Service: get_task(id)
    Service->>DB: SELECT task WHERE id=task_id
    DB-->>Service: TaskEntity
    Service-->>UseCase: TaskEntity

    alt task.status == DELETED
        UseCase-->>Route: raise ItemDoesNotExist (404)
    else task.status != RUNNING
        UseCase-->>Route: raise ClientError (400)
    end

    UseCase->>Service: transition_task_status(task_id, RUNNING→COMPLETED, reason)
    Service->>Repo: transition_status(task_id, expected=RUNNING, new=COMPLETED)
    Repo->>DB: UPDATE tasks SET status=COMPLETED WHERE id=task_id AND status=RUNNING
    DB-->>Repo: rowcount (0 or 1)

    alt rowcount == 0 (concurrent modification)
        Repo-->>Service: None
        Service-->>UseCase: None
        UseCase-->>Route: raise ClientError "concurrently modified, retry" (400)
    end

    Repo->>DB: SELECT task WHERE id=task_id
    DB-->>Repo: Updated TaskORM
    Repo-->>Service: TaskEntity (COMPLETED)
    Service->>Stream: publish task_updated event
    Service-->>UseCase: TaskEntity (COMPLETED)
    UseCase-->>Route: TaskEntity
    Route-->>Client: 200 Task{status: COMPLETED}
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: agentex/src/domain/repositories/task_repository.py
Line: 171-172

Comment:
**Missing null guard on `session.get()` result**

`session.get()` returns `TaskORM | None`. While `refreshed` should never be `None` here (the `rowcount > 0` guard confirms the row was just updated), a defensive check prevents a confusing Pydantic `ValidationError` in any edge case (e.g. a concurrent hard DELETE at the DB level). Consider:

```suggestion
            refreshed = await session.get(TaskORM, task_id)
            if refreshed is None:
                return None
            return TaskEntity.model_validate(refreshed)
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: agentex-ui/components/primary-content/prompt-input.tsx
Line: 63

Comment:
**Hardcoded status string instead of enum**

`'RUNNING'` is a magic string here. Per the project style guide, enum values should be used instead of hardcoded strings when available. If `TaskStatus` (or equivalent) is exported from the `agentex` SDK package, reference it directly to avoid a silent mismatch if the status value ever changes:

```suggestion
    return task.status != null && task.status !== TaskStatus.RUNNING;
```

If `TaskStatus` isn't exported from the `agentex` package, consider extracting this as a local constant (`const RUNNING_STATUS = 'RUNNING' as const`) so it's defined in one place.

**Rule Used:** Use enum values instead of hardcoded strings when ... ([source](https://app.greptile.com/review/custom-context?memory=c0c58ddb-09dc-4e8a-9837-ec1bb02f9579))

**Learnt From**
[scaleapi/scaleapi#126557](https://github.com/scaleapi/scaleapi/pull/126557)

How can I resolve this? If you propose a fix, please make it concise.

Reviews (4): Last reviewed commit: "add better tests for tasks" | Re-trigger Greptile

Context used:

  • Rule used - Use enum values instead of hardcoded strings when ... (source)

Learnt From
scaleapi/scaleapi#126557

@declan-scale declan-scale requested a review from a team as a code owner March 24, 2026 21:35
@declan-scale declan-scale force-pushed the declan-scale/mark-task-completed branch 2 times, most recently from 6624cb8 to ebd3487 Compare March 24, 2026 23:56
@declan-scale declan-scale force-pushed the declan-scale/mark-task-completed branch from ebd3487 to 58d2723 Compare March 24, 2026 23:57
@declan-scale declan-scale changed the title Enable Task status updates via PUT /tasks/{id_or_name} Enable Task status updates PUT requests Mar 24, 2026
@declan-scale declan-scale changed the title Enable Task status updates PUT requests Enable Task status updates via POST requests Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant