Skip to content

fix(tasks): omit params from the list-tasks response#377

Open
gabrycina wants to merge 1 commit into
mainfrom
fix/list-tasks-omit-params
Open

fix(tasks): omit params from the list-tasks response#377
gabrycina wants to merge 1 commit into
mainfrom
fix/list-tasks-omit-params

Conversation

@gabrycina

@gabrycina gabrycina commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

GET /tasks (list) returned the full task object, the same TaskResponse the single-task GET returns, so every item included params: the arbitrary, caller-supplied create-time payload. Callers routinely stash sensitive material there (credentials, tokens, PII). Because the list is broadly scoped, that turned a single list call into a bulk read of every task's params.

This makes the list a lean summary (TaskSummary, no params) and keeps the full record on the single-task fetch. A list response should carry only what a collection view needs; the full record belongs on the item fetch.

Scope: the list response only. Behavior of GET /tasks/{id}, GET /tasks/name/{name}, POST/PATCH, and the domain/repository layers is unchanged.

Endpoint contract (after)

flowchart TD
    Client(["Client"])
    Client -->|"GET /tasks (list)"| Summary["list of TaskSummary<br/>id, name, status, status_reason,<br/>created_at, updated_at, cleaned_at,<br/>task_metadata, agents"]
    Client -->|"GET /tasks/{id} (detail)"| Full["TaskResponse<br/>(all summary fields) + params"]
    Summary -. "need params for one task?" .-> Client
    Client ==>|"fetch that id"| Full

    classDef lean fill:#e8f5e9,stroke:#43a047,color:#1b5e20;
    classDef full fill:#fdecea,stroke:#e53935,color:#b71c1c;
    class Summary lean;
    class Full full;
Loading
  • List returns the lean summary for every task, cheap, and safe to return in bulk.
  • Detail still returns everything, including params, and is fetched one id at a time (and is subject to the same per-request authorization as before).

Schema

TaskSummary is a deliberate allowlist. It is a standalone model (not a subclass of Task) so params cannot leak in via inheritance, and because BaseModel ignores extra fields, validating a TaskEntity (which still carries params) into a TaskSummary drops params at serialization.

classDiagram
    class TaskSummary {
      +id
      +name
      +status
      +status_reason
      +created_at
      +updated_at
      +cleaned_at
      +task_metadata
      +agents
    }
    class TaskResponse {
      +id
      +name
      +status
      +status_reason
      +created_at
      +updated_at
      +cleaned_at
      +task_metadata
      +agents
      +params
    }
    note for TaskSummary "list response, omits params"
    note for TaskResponse "single-task detail, includes params (may carry credentials or PII)"
Loading

The two share the same safe fields; only TaskResponse exposes params.

Before / after

// BEFORE - one item in the list response
{
  "id": "0c1f8e2a-…",
  "name": "",
  "status": "RUNNING",
  "created_at": "", "updated_at": "", "cleaned_at": null,
  "task_metadata": { "display_name": "" },
  "params": {
    "auth_token": "",        // whatever the caller stored at create time,
    "user_email": "",        // e.g. credentials and PII, returned in bulk
    "…": ""
  }
}

// AFTER - same item, lean summary (no params)
{
  "id": "0c1f8e2a-…",
  "name": "",
  "status": "RUNNING",
  "created_at": "", "updated_at": "", "cleaned_at": null,
  "task_metadata": { "display_name": "" },
  "agents": null
}

What changes across a whole list call:

flowchart LR
    subgraph Before["Before: GET /tasks"]
        direction TB
        b["N tasks × full object<br/>N × params (creds/PII)"]
    end
    subgraph After["After: GET /tasks"]
        direction TB
        a["N tasks × lean summary<br/>0 × params"]
    end
    Before --> After
    classDef bad fill:#fdecea,stroke:#e53935,color:#b71c1c;
    classDef good fill:#e8f5e9,stroke:#43a047,color:#1b5e20;
    class Before bad;
    class After good;
Loading

Field reference

Field In list (TaskSummary) In detail (TaskResponse) Notes
id yes yes System-assigned task id
name yes yes Optional, caller-supplied
status / status_reason yes yes Lifecycle state
created_at / updated_at / cleaned_at yes yes Timestamps
task_metadata yes yes Display / routing / filter metadata (the list can filter on it)
agents yes yes Populated only with ?relationships=agents
params no yes Arbitrary create-time payload; may carry credentials/PII

Breaking change and migration

Removing params from the list is an intentional, security-motivated breaking change for typed consumers that read params off a list item.

flowchart TD
    Q{"Reading params off a list item?"}
    Q -->|No| OK["No change needed"]
    Q -->|Yes| Fix["Fetch the task by id, then read params"]
    classDef ok fill:#e8f5e9,stroke:#43a047,color:#1b5e20;
    class OK ok;
Loading

The SDKs are generated from agentex/openapi.yaml (Stainless), so the list-item type regenerates automatically on merge; consumers pick up the change when they bump the SDK. This PR regenerates and commits openapi.yaml (the list_tasks 200 now references TaskSummary), which the openapi-spec CI check enforces.

Consumers touched in this PR

  • Developer UI (agentex-ui): createTaskName fell back to params.description for the task label; switched to the task name (task_metadata.display_name → name → "Unnamed task"). No other code reads params off a listed task.

Implementation

  • src/api/schemas/tasks.py: add TaskSummary (allowlist, omits params).
  • src/api/routes/tasks.py: list_tasks response_model=list[TaskSummary], returns TaskSummary.model_validate(...) per entity. Single-task routes unchanged.
  • agentex-ui/lib/task-utils.ts + test: name-derivation fallback.
  • agentex/openapi.yaml: regenerated.

Follow-ups / known limitations

  • task_metadata is still free-form (dict[str, Any]) and partly caller-influenced. By convention it holds non-secret display/routing/filter metadata (and the list can filter on it), so it stays in the summary. If we want the list to be strict rather than convention-safe, a follow-up could give task_metadata a typed shape so callers cannot stash sensitive data that the list would surface.
  • Concurrency is out of scope: unrelated to this change; not touched here.

Test plan

flowchart LR
    subgraph list["GET /tasks"]
        L1["task present in list"]
        L2["params NOT in item"]
    end
    subgraph get["GET /tasks/{id}"]
        G1["params present + correct"]
    end
    list --> get
Loading
  • Integration (tests/integration/api/tasks/test_tasks_api.py): list omits params for both populated- and null-params tasks; the schema test asserts params absent on list items; single-task GET (by id and by name) still returns params.
  • UI unit (agentex-ui/lib/task-utils.test.ts): task-name derivation from display_name / name.
  • openapi.yaml regenerated and committed.
  • Verified directly that TaskSummary.model_validate({... "params": {...}}) drops params while TaskResponse keeps it; ruff clean.

The list endpoint returned the full task object, including `params`: the
arbitrary, caller-supplied create-time payload. On a broadly-scoped list that
exposes whatever callers store there (per-caller bearer tokens, PII, ...) in
bulk. Return a lean `TaskSummary` from `GET /tasks` instead, and keep the full
record (including `params`) on the single-task `GET /tasks/{id}`.

- Add `TaskSummary` (list shape) that omits `params`; point `list_tasks` at it.
- Update the dev UI task-name fallback to use the task `name` instead of
  `params.description`, and regenerate `openapi.yaml`.
- Tests: list omits `params`; single-task GET still includes it.

Breaking change: typed SDK consumers that read `params` off a list item must
fetch the task by id instead.
@gabrycina
gabrycina requested a review from danielmillerp July 24, 2026 16:26
@gabrycina
gabrycina requested a review from a team as a code owner July 24, 2026 16:26
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

✱ Stainless preview builds

This PR will update the agentex-sdk SDKs with the following commit messages.

openapi

feat(api): add TaskSummary response to list tasks

python

fix(api): remove params field from task list response

typescript

fix(api): remove params field from tasks list response

Edit this comment to update them. They will appear in their respective SDK's changelogs.

agentex-sdk-python studio · code · diff

Your SDK build had at least one "warning" diagnostic, but this did not represent a regression.
generate ⚠️build ✅lint ❗test ✅

pip install https://pkg.stainless.com/s/agentex-sdk-python/8821b99b7c80f42a31497a95990038461b33dcc6/agentex_client-0.20.0-py3-none-any.whl
agentex-sdk-openapi studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅

agentex-sdk-typescript studio · code · diff

Your SDK build had at least one "warning" diagnostic, but this did not represent a regression.
generate ⚠️build ✅lint ✅test ✅

npm install https://pkg.stainless.com/s/agentex-sdk-typescript/9fe4a4c8cb1d405fc907ad71c95d1b2fd803d781/dist.tar.gz

This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-07-24 16:29:43 UTC

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