Skip to content

[chore] Clean up and unify (most) git-based routes#4470

Draft
jp-agenta wants to merge 10 commits into
feat/unified-eval-loopsfrom
refactor/unify-revision-request-fields
Draft

[chore] Clean up and unify (most) git-based routes#4470
jp-agenta wants to merge 10 commits into
feat/unified-eval-loopsfrom
refactor/unify-revision-request-fields

Conversation

@jp-agenta
Copy link
Copy Markdown
Member

@jp-agenta jp-agenta commented May 28, 2026

No description provided.

Rename the wrapper field on revision commit/log request bodies so each
matches the snake_case form of its enclosing request class.

Outliers fixed:
- /workflows/revisions/commit: workflow_revision   -> workflow_revision_commit
- /workflows/revisions/log:    workflow            -> workflow_revisions_log
- /applications/revisions/log: application         -> application_revisions_log
- /evaluators/revisions/log:   evaluator           -> evaluator_revisions_log
- /environments/revisions/log: environment         -> environment_revisions_log
- /queries/revisions/log:      query_revisions     -> query_revisions_log
- /testsets/revisions/log:     testset_revision    -> testset_revisions_log

Updates the FastAPI request models, the routers that read those fields,
and the handwritten callers in:
- web/packages/agenta-entities/src/workflow/api/api.ts (commit bodies only;
  query/response uses of workflow_revision remain unchanged)
- sdks/python/agenta/sdk/managers/shared.py (history/ahistory log bodies)
- 11 workflow acceptance tests + 1 SDK workflow acceptance test (commit
  + log bodies)

Fern-generated clients regenerated separately.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jun 4, 2026 3:43pm

Request Review

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. refactoring A code change that neither fixes a bug nor adds a feature labels May 28, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added variant fork operations for environments, queries, and testsets.
    • Added inline revision resolution support for multiple entity types.
  • Improvements

    • Restructured revision commit and log request payloads for consistency across all entity types.
    • Enhanced revision querying with improved filtering capabilities.
    • Unified fork request shapes across all entity types.
  • Documentation

    • Added comprehensive design documentation for request payload structures across git-backed entities.

Walkthrough

This PR unifies request body field naming and fork request shapes across six git-backed entity types (workflows, applications, evaluators, queries, testsets, environments). It renames revision commit/log request wrapper fields, adds inline-resolution support to revision resolve endpoints, implements fork service methods for environments and testsets, and updates tests and SDKs to use the new request shapes.

Changes

Git-backed entity fork and request field standardization

Layer / File(s) Summary
Fork request models and variant fork request unification
api/oss/src/apis/fastapi/applications/models.py, api/oss/src/apis/fastapi/environments/models.py, api/oss/src/apis/fastapi/evaluators/models.py, api/oss/src/apis/fastapi/queries/models.py, api/oss/src/apis/fastapi/testsets/models.py, api/oss/src/apis/fastapi/workflows/models.py
All six entities gain *VariantForkRequest Pydantic models with consistent shape: *_variant (fork payload), *_variant_ref (required reference), and optional *_revision_ref. New artifact-level *ForkRequest models added for environments and testsets. DTO imports updated to include variant/artifact fork types.
Fork endpoint routing and request handling
api/oss/src/apis/fastapi/applications/router.py, api/oss/src/apis/fastapi/environments/router.py, api/oss/src/apis/fastapi/evaluators/router.py, api/oss/src/apis/fastapi/queries/router.py, api/oss/src/apis/fastapi/testsets/router.py, api/oss/src/apis/fastapi/workflows/router.py
All fork endpoints updated to accept unified *VariantForkRequest type, derive variant refs from request payloads, and refactor service calls to pass split parameters (variant, variant_ref, revision_ref) instead of monolithic fork objects. New POST /variants/fork endpoints added for queries and testsets.
Revision commit/log request field renaming
api/oss/src/apis/fastapi/*/models.py, api/oss/src/apis/fastapi/*/router.py
Commit request nested field renamed *_revision_commit*_revision (e.g., application_revision_commitapplication_revision) across all entities. Log request field renamed **_revisions (e.g., applicationapplication_revisions). Router wiring updated to source commit/log payloads from renamed fields when invoking service methods.
Inline-resolution support for revision resolve
api/oss/src/apis/fastapi/*/models.py, api/oss/src/apis/fastapi/*/router.py
*RevisionResolveRequest models extended with optional *_revision fields to enable inline resolution without database lookup. Removed resolve: bool from query endpoints. Routers updated to pass resolution payloads to service methods; services implement early-return inline resolution path when revision object is provided.
Fork service implementations and DTO updates
api/oss/src/core/applications/service.py, api/oss/src/core/environments/dtos.py, api/oss/src/core/environments/service.py, api/oss/src/core/evaluators/service.py, api/oss/src/core/queries/service.py, api/oss/src/core/testsets/dtos.py, api/oss/src/core/testsets/service.py, api/oss/src/core/workflows/service.py
Fork service method signatures refactored to accept *VariantFork + explicit *_variant_ref and optional *_revision_ref instead of unified fork objects. New fork DTOs added for environments/testsets with VariantFork/RevisionFork subclasses and alias wrappers. New fork_environment_variant and fork_testset_variant service methods introduced.
Inline-resolution service paths for all entities
api/oss/src/core/applications/service.py, api/oss/src/core/environments/service.py, api/oss/src/core/evaluators/service.py
resolve_*_revision methods across applications, evaluators, and environments extended with optional *_revision parameter to trigger inline resolution. When supplied, method validates EmbedsService, resolves configuration inline, replaces revision data, and returns early without database fetch. Existing fetch-and-resolve path preserved for reference-based resolution.
Environment query parameter refactoring
api/oss/src/apis/fastapi/environments/models.py, api/oss/src/apis/fastapi/environments/router.py, api/oss/src/core/environments/service.py, api/oss/src/dbs/postgres/git/dao.py, web/packages/agenta-entities/src/environment/api/api.ts
application_refs renamed to generic references in EnvironmentRevisionQueryRequest to enable cross-entity revision filtering. Service/DAO signatures and internal adapter logic updated to use references parameter throughout environment revision query path.
Test fixture and assertion updates for new request shapes
api/oss/tests/pytest/acceptance/*/, api/oss/tests/pytest/unit/*/
All acceptance and unit test fixtures updated to use new revision commit/log wrapper field names and new references parameter where applicable. Changes span fixture setup (creating revisions), assertion payloads, and test data creation across all six entity types.
SDK and web client request payload updates
sdks/python/agenta/sdk/managers/shared.py, web/packages/agenta-entities/src/environment/api/api.ts, web/oss/src/components/DeploymentsDashboard/store/deploymentStore.ts
Python SDK manager methods updated to use new revision log wrapper (application_revisions_log instead of application). Web environment API client updated to use references instead of application_refs. TypeScript comment corrected to reflect backend field naming change.
Design documentation for request body standardization
docs/designs/git-entities/request-body-fields.md, docs/designs/git-entities/tasks.md
Comprehensive design document added inventorying request body field conventions for all git-backed entities, formalizing naming rules (fork/create/edit/query/commit/log/retrieve/resolve/deploy), and documenting inline-resolution modes. Implementation task tracking updated to record fork unification completion and remaining work items.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Agenta-AI/agenta#4469: Adds retrieval_info to application/evaluator revision resolve responses, building on the inline-resolution request support introduced in this PR.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/unify-revision-request-fields

@junaway junaway changed the title refactor(api): unify revision request body field names [chore] Clean up and unify (some of) the revision request field names May 28, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

Railway Preview Environment

Status Destroyed (PR converted to draft)

Updated at 2026-06-04T08:13:48.900Z

@junaway junaway marked this pull request as draft May 28, 2026 07:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR standardizes the top-level wrapper field names in several revision commit/log request bodies so they match the snake_case form of their enclosing FastAPI request model names, and updates a subset of in-repo callers/tests accordingly.

Changes:

  • Renamed request wrapper fields for /workflows/revisions/commit and multiple */revisions/log endpoints (workflows, applications, evaluators, environments, queries, testsets).
  • Updated FastAPI request models + routers to read the renamed wrapper fields.
  • Updated handwritten callers (TS frontend workflow API, Python SDK history/ahistory) and acceptance tests to send the new wrapper keys.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
web/packages/agenta-entities/src/workflow/api/api.ts Updates workflow revision commit request bodies to use workflow_revision_commit.
sdks/python/oss/tests/pytest/acceptance/workflows/test_embeds_integration.py Updates workflow commit acceptance-test payloads to use workflow_revision_commit.
sdks/python/agenta/sdk/managers/shared.py Updates application revisions log payload wrapper to application_revisions_log (sync + async).
api/oss/tests/pytest/acceptance/workflows/test_workflow_revisions_queries.py Updates workflow commit payload wrappers in revision query acceptance tests.
api/oss/tests/pytest/acceptance/workflows/test_workflow_revisions_basics.py Updates workflow commit payload wrapper in basic revisions test.
api/oss/tests/pytest/acceptance/workflows/test_workflow_retrieval_info.py Updates workflow commit payload wrappers in retrieval-info acceptance tests.
api/oss/tests/pytest/acceptance/workflows/test_workflow_lineage.py Updates workflow revisions log payload wrapper to workflow_revisions_log.
api/oss/tests/pytest/acceptance/workflows/test_workflow_embeds.py Updates workflow commit payload wrappers used by embed resolution tests.
api/oss/tests/pytest/acceptance/workflows/test_workflow_embeds_string.py Updates workflow commit payload wrappers used by string-embed tests.
api/oss/tests/pytest/acceptance/workflows/test_workflow_embeds_security.py Updates workflow commit payload wrappers used by embed security tests.
api/oss/tests/pytest/acceptance/workflows/test_workflow_embeds_retrieve_resolve.py Updates workflow commit payload wrappers used by retrieve/resolve embed tests.
api/oss/tests/pytest/acceptance/workflows/test_workflow_embeds_legacy.py Updates workflow commit payload wrappers used by legacy embed tests.
api/oss/tests/pytest/acceptance/workflows/test_workflow_embeds_errors.py Updates workflow commit payload wrappers used by embed error-handling tests.
api/oss/tests/pytest/acceptance/workflows/test_workflow_embeds_cross_entity.py Updates workflow commit payload wrappers used by cross-entity embed tests.
api/oss/src/apis/fastapi/workflows/router.py Reads workflow_revision_commit and workflow_revisions_log from request bodies.
api/oss/src/apis/fastapi/workflows/models.py Renames request model fields to workflow_revision_commit and workflow_revisions_log.
api/oss/src/apis/fastapi/testsets/router.py Reads testset_revisions_log from request body.
api/oss/src/apis/fastapi/testsets/models.py Renames log request wrapper field to testset_revisions_log.
api/oss/src/apis/fastapi/queries/router.py Reads query_revisions_log from request body.
api/oss/src/apis/fastapi/queries/models.py Renames log request wrapper field to query_revisions_log.
api/oss/src/apis/fastapi/evaluators/router.py Reads evaluator_revisions_log from request body.
api/oss/src/apis/fastapi/evaluators/models.py Renames log request wrapper field to evaluator_revisions_log.
api/oss/src/apis/fastapi/environments/router.py Reads environment_revisions_log from request body (also minor formatting change).
api/oss/src/apis/fastapi/environments/models.py Renames log request wrapper field to environment_revisions_log.
api/oss/src/apis/fastapi/applications/router.py Reads application_revisions_log from request body (also minor formatting change).
api/oss/src/apis/fastapi/applications/models.py Renames log request wrapper field to application_revisions_log.
Comments suppressed due to low confidence (8)

api/oss/src/apis/fastapi/workflows/models.py:251

  • This rename makes POST /workflows/revisions/commit reject the legacy wrapper key workflow_revision, which is still used by existing clients in this repo (e.g. generated TS client). To avoid an immediate breaking change, consider accepting the old key as a validation alias while keeping the new field name in the OpenAPI schema.
class WorkflowRevisionCommitRequest(BaseModel):
    workflow_revision_commit: WorkflowRevisionCommit = Field(
        description=(
            "Revision to append to a variant's history. Requires `workflow_variant_id` "
            "and optional `message`; `data` carries the new configuration."
        ),
    )

api/oss/src/apis/fastapi/workflows/models.py:369

  • This rename makes POST /workflows/revisions/log reject the legacy wrapper key workflow, which is still used by existing clients. If you want a non-breaking transition, accept the old key via a Pydantic validation_alias while documenting/serializing the new name.
class WorkflowRevisionsLogRequest(BaseModel):
    workflow_revisions_log: WorkflowRevisionsLog = Field(
        description=(
            "Log query. Supply `workflow_id`, `workflow_variant_id`, or "
            "`workflow_revision_id` to scope the log, and an optional `depth`."
        ),
    )

api/oss/src/apis/fastapi/applications/models.py:174

  • POST /applications/revisions/log will now reject the legacy wrapper key application. Since existing clients still send application, consider accepting it as a validation_alias to prevent a hard break while transitioning to application_revisions_log.
class ApplicationRevisionsLogRequest(BaseModel):
    """Request body for `POST /applications/revisions/log`.

    Returns the ordered list of revisions committed to a variant, newest first.
    Each entry carries commit metadata and the full revision record.
    """

    application_revisions_log: ApplicationRevisionsLog = Field(
        description=(
            "Filter for the log. Typically set `application_variant_id` to list "
            "the revision history of a single variant; optionally set "
            "`application_revision_id` + `depth` to walk back a bounded number "
            "of commits from a specific revision."
        ),
    )

api/oss/src/apis/fastapi/testsets/models.py:280

  • POST /testsets/revisions/log will now reject the legacy wrapper key testset_revision. If backwards compatibility is desired, accept the old key as a validation_alias while keeping the new field name for documentation.
class TestsetRevisionsLogRequest(BaseModel):
    testset_revisions_log: TestsetRevisionsLog = Field(
        description="Scope for the log: one of `testset_id`, `testset_variant_id`, or `testset_revision_id`. Optional `depth` limits how far back to walk.",
    )
    include_testcases: Optional[bool] = Field(
        default=None,
        description="Include full testcase objects for each returned revision.",
    )

api/oss/src/apis/fastapi/workflows/models.py:251

  • This rename makes POST /workflows/revisions/commit reject the legacy wrapper key workflow_revision, which is still used by existing clients in this repo (e.g. generated TS client). To avoid an immediate breaking change, consider accepting the old key as a validation alias while keeping the new field name in the OpenAPI schema.
class WorkflowRevisionCommitRequest(BaseModel):
    workflow_revision_commit: WorkflowRevisionCommit = Field(
        description=(
            "Revision to append to a variant's history. Requires `workflow_variant_id` "
            "and optional `message`; `data` carries the new configuration."
        ),
    )

api/oss/src/apis/fastapi/workflows/models.py:369

  • This rename makes POST /workflows/revisions/log reject the legacy wrapper key workflow, which is still used by existing clients. If you want a non-breaking transition, accept the old key via a Pydantic validation_alias while documenting/serializing the new name.
class WorkflowRevisionsLogRequest(BaseModel):
    workflow_revisions_log: WorkflowRevisionsLog = Field(
        description=(
            "Log query. Supply `workflow_id`, `workflow_variant_id`, or "
            "`workflow_revision_id` to scope the log, and an optional `depth`."
        ),
    )

api/oss/src/apis/fastapi/applications/models.py:174

  • POST /applications/revisions/log will now reject the legacy wrapper key application. Since existing clients still send application, consider accepting it as a validation_alias to prevent a hard break while transitioning to application_revisions_log.
class ApplicationRevisionsLogRequest(BaseModel):
    """Request body for `POST /applications/revisions/log`.

    Returns the ordered list of revisions committed to a variant, newest first.
    Each entry carries commit metadata and the full revision record.
    """

    application_revisions_log: ApplicationRevisionsLog = Field(
        description=(
            "Filter for the log. Typically set `application_variant_id` to list "
            "the revision history of a single variant; optionally set "
            "`application_revision_id` + `depth` to walk back a bounded number "
            "of commits from a specific revision."
        ),
    )

api/oss/src/apis/fastapi/testsets/models.py:280

  • POST /testsets/revisions/log will now reject the legacy wrapper key testset_revision. If backwards compatibility is desired, accept the old key as a validation_alias while keeping the new field name for documentation.
class TestsetRevisionsLogRequest(BaseModel):
    testset_revisions_log: TestsetRevisionsLog = Field(
        description="Scope for the log: one of `testset_id`, `testset_variant_id`, or `testset_revision_id`. Optional `depth` limits how far back to walk.",
    )
    include_testcases: Optional[bool] = Field(
        default=None,
        description="Include full testcase objects for each returned revision.",
    )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/oss/src/apis/fastapi/evaluators/models.py
Comment thread api/oss/src/apis/fastapi/environments/models.py
Comment thread api/oss/src/apis/fastapi/queries/models.py
Comment thread api/oss/src/apis/fastapi/evaluators/models.py
Comment thread api/oss/src/apis/fastapi/environments/models.py
Comment thread api/oss/src/apis/fastapi/queries/models.py
@junaway junaway changed the base branch from feat/add-retrieval-info-to-api-and-sdk to release/v0.100.8 May 31, 2026 11:48
@junaway junaway marked this pull request as ready for review May 31, 2026 11:48
Copilot AI review requested due to automatic review settings May 31, 2026 11:48
@dosubot dosubot Bot added the Backend label May 31, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.

Comment thread api/oss/src/apis/fastapi/applications/router.py
Comment thread api/oss/src/apis/fastapi/environments/router.py
Comment thread api/oss/src/apis/fastapi/evaluators/router.py
Comment thread api/oss/src/apis/fastapi/queries/router.py
Comment thread api/oss/src/apis/fastapi/testsets/router.py
@junaway junaway changed the title [chore] Clean up and unify (some of) the revision request field names [chore] Clean up and unify (most) git-based routes May 31, 2026
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels May 31, 2026
@junaway junaway requested a review from Copilot May 31, 2026 14:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 44 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (2)

sdks/python/agenta/sdk/managers/shared.py:946

  • The request body wrapper key for POST /applications/revisions/log should match the FastAPI model (ApplicationRevisionsLogRequest.application_revisions). Using application_revisions_log will cause a 422 validation error.
    sdks/python/agenta/sdk/managers/shared.py:990
  • Same issue as the sync history() method: the request wrapper key should be application_revisions (per ApplicationRevisionsLogRequest) rather than application_revisions_log, otherwise the async call will 422.

Comment thread api/oss/src/core/environments/service.py Outdated
Comment thread api/oss/src/apis/fastapi/queries/router.py
Comment thread api/oss/src/apis/fastapi/testsets/router.py
Comment thread api/oss/src/apis/fastapi/environments/router.py
@junaway junaway marked this pull request as draft June 4, 2026 08:13
@junaway junaway changed the base branch from release/v0.100.8 to feat/unified-eval-loops June 4, 2026 08:38
@junaway
Copy link
Copy Markdown
Contributor

junaway commented Jun 4, 2026

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

sdks/python/agenta/sdk/managers/shared.py:946

  • The /applications/revisions/log request body wrapper was changed to application_revisions_log, but the FastAPI model expects application_revisions (see api/oss/src/apis/fastapi/applications/models.py), so the SDK call will send an unexpected field and likely return 422.
    sdks/python/agenta/sdk/managers/shared.py:990
  • Same issue as the sync history() method: the request body wrapper key should be application_revisions, not application_revisions_log, to match the API model for POST /applications/revisions/log.

Comment on lines +42 to +46
| `POST /<entities>/variants/` | `<entity>_variant: <Entity>VariantCreate` |
| `PUT /<entities>/variants/{id}` | `<entity>_variant: <Entity>VariantEdit` |
| `POST /<entities>/variants/query` | `<entity>_variant: <Entity>VariantQuery` + `<entity>_refs: List[Reference]` + `<entity>_variant_refs: List[Reference]` |
| `POST /<entities>/variants/fork` *(all six entities)* | `<entity>_variant: <Entity>VariantFork` + `<entity>_variant_ref: Reference` + `<entity>_revision_ref: Reference` |

Comment on lines +33 to +37
| `POST /<entities>/` | `<entity>: <Entity>Create` |
| `PUT /<entities>/{id}` | `<entity>: <Entity>Edit` |
| `POST /<entities>/query` | `<entity>: <Entity>Query` + `<entity>_refs: List[Reference]` |
| `POST /<entities>/{id}/fork` *(all six entities)* | `<entity>: <Entity>Fork` |

Comment on lines 1116 to 1126
environment_revisions = await self.environments_service.query_environment_revisions(
project_id=UUID(request.state.project_id),
#
environment_revision_query=environment_revision_query_request.environment_revision,
#
environment_refs=environment_revision_query_request.environment_refs,
environment_variant_refs=environment_revision_query_request.environment_variant_refs,
environment_revision_refs=environment_revision_query_request.environment_revision_refs,
#
application_refs=environment_revision_query_request.application_refs,
references=environment_revision_query_request.references,
#
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
api/oss/src/apis/fastapi/applications/router.py (1)

1750-1779: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Inline resolve should not populate retrieval_info.

When application_revision_resolve_request.application_revision is present, the service resolves only data and ignores the revision identifiers. This handler still calls build_retrieval_info(...), so the response can claim the API retrieved refs that actually just came from the request body.

Suggested fix
         application_revision, resolution_info = result
+        retrieval_info = None
+        if application_revision_resolve_request.application_revision is None:
+            retrieval_info = build_retrieval_info(
+                revision=application_revision,
+                entity_type="application",
+            )
 
         return ApplicationRevisionResolveResponse(
             count=1,
             application_revision=application_revision,
             resolution_info=resolution_info,
-            retrieval_info=build_retrieval_info(
-                revision=application_revision,
-                entity_type="application",
-            ),
+            retrieval_info=retrieval_info,
         )
web/packages/agenta-entities/src/environment/api/api.ts (1)

99-107: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Move this request onto the Fern client instead of extending the raw axios path.

This payload rename is being added inside a direct axios.post(...) call with axios-style params. The repository rule for frontend API code is to go through the Fern-generated client and pass query params via queryParams, so this change should land in that wrapper instead of growing the raw HTTP surface.

As per coding guidelines, "All new frontend API code must go through the Fern-generated client, not raw axios" and "Pass query params via {queryParams: {...}}, NOT axios's {params: {...}}."

api/oss/src/apis/fastapi/environments/router.py (1)

865-902: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Skip retrieval_info when resolving an inline environment payload.

When environment_revision_resolve_request.environment_revision is supplied, nothing is fetched from storage, but this response still calls build_retrieval_info(...) on the returned model. That makes retrieval_info reflect caller-supplied ids/slugs rather than an actual retrieval path. Leave it unset for inline mode, or have the service tell the router whether the revision was fetched.

api/oss/src/apis/fastapi/evaluators/router.py (1)

1753-1781: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't synthesize retrieval_info for inline evaluator resolves.

When the request passes evaluator_revision directly, the service resolves that payload without fetching a stored revision. Building retrieval_info from the returned model makes the response report a retrieval path that never happened and can echo arbitrary ids/slugs from the request body. Return retrieval_info=None in inline mode.

api/oss/src/apis/fastapi/testsets/router.py (1)

1405-1413: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the renamed wrapper field when building the internal commit request.

This helper still instantiates TestsetRevisionCommitRequest with testset_revision_commit=..., but Line 1481 now consumes testset_revision_commit_request.testset_revision. That breaks /revisions/{testset_revision_id}/upload before it reaches commit_testset_revision.

Suggested fix
         testset_revision_commit_request = TestsetRevisionCommitRequest(
-            testset_revision_commit=TestsetRevisionCommit(
+            testset_revision=TestsetRevisionCommit(
                 testset_id=base_revision.testset_id,
                 testset_variant_id=base_revision.testset_variant_id,
                 testset_revision_id=testset_revision_id,
                 data=testset_revision_data,
             ),
             include_testcases=include_testcases,
         )
api/oss/src/dbs/postgres/git/dao.py (1)

1469-1538: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

This adapter breaks cursor pagination across pages.

apply_windowing() trims the raw revision stream before this application-diff filter runs, and Line 1496 resets prev_app_revision_ids on every call. That makes the first matching revision on each page look like a new deployment change from None, so paginated history can return false positives at page boundaries and unstable page sizes. Apply the diff filter before windowing, or persist the last-seen application revision ids in the cursor state. As per coding guidelines, "Use cursor pagination via Windowing, not page-number pagination."

🧹 Nitpick comments (1)
api/oss/src/apis/fastapi/evaluators/models.py (1)

496-502: ⚡ Quick win

Use a request-specific inline resolve payload.

This field advertises the full persisted EvaluatorRevision shape even though the endpoint only consumes data. That couples the public request contract to server-managed revision fields that are ignored at runtime and can force callers to fabricate ids/version metadata just to resolve an ad-hoc payload. A small request DTO with a required data field would keep the wire contract aligned with the implementation. Based on learnings: "Define explicit request/response models in models.py."


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0db92667-5c80-4fad-bce0-6d64361b9dca

📥 Commits

Reviewing files that changed from the base of the PR and between 5fbeddc and 44dba70.

📒 Files selected for processing (46)
  • api/oss/src/apis/fastapi/applications/models.py
  • api/oss/src/apis/fastapi/applications/router.py
  • api/oss/src/apis/fastapi/environments/models.py
  • api/oss/src/apis/fastapi/environments/router.py
  • api/oss/src/apis/fastapi/evaluators/models.py
  • api/oss/src/apis/fastapi/evaluators/router.py
  • api/oss/src/apis/fastapi/queries/models.py
  • api/oss/src/apis/fastapi/queries/router.py
  • api/oss/src/apis/fastapi/testsets/models.py
  • api/oss/src/apis/fastapi/testsets/router.py
  • api/oss/src/apis/fastapi/workflows/models.py
  • api/oss/src/apis/fastapi/workflows/router.py
  • api/oss/src/core/applications/service.py
  • api/oss/src/core/environments/dtos.py
  • api/oss/src/core/environments/service.py
  • api/oss/src/core/evaluators/service.py
  • api/oss/src/core/queries/service.py
  • api/oss/src/core/testsets/dtos.py
  • api/oss/src/core/testsets/service.py
  • api/oss/src/core/workflows/service.py
  • api/oss/src/dbs/postgres/git/dao.py
  • api/oss/tests/pytest/acceptance/applications/test_application_retrieval_info.py
  • api/oss/tests/pytest/acceptance/applications/test_application_variants_and_revisions.py
  • api/oss/tests/pytest/acceptance/environments/test_environment_retrieval_info.py
  • api/oss/tests/pytest/acceptance/evaluators/test_evaluator_retrieval_info.py
  • api/oss/tests/pytest/acceptance/legacy_variants/test_legacy_configs_fetch_acceptance.py
  • api/oss/tests/pytest/acceptance/loadables/test_loadable_strategies.py
  • api/oss/tests/pytest/acceptance/queries/test_queries_basics.py
  • api/oss/tests/pytest/acceptance/queries/test_query_retrieval_info.py
  • api/oss/tests/pytest/acceptance/test_revision_commit_extra_forbid.py
  • api/oss/tests/pytest/acceptance/test_revision_logs.py
  • api/oss/tests/pytest/acceptance/test_revision_retrieve_ambiguous_400.py
  • api/oss/tests/pytest/acceptance/test_revision_retrieve_env_path.py
  • api/oss/tests/pytest/acceptance/test_revision_retrieve_inconsistent_400.py
  • api/oss/tests/pytest/acceptance/test_revision_retrieve_positive.py
  • api/oss/tests/pytest/acceptance/testsets/test_testset_retrieval_info.py
  • api/oss/tests/pytest/acceptance/tracing/test_traces_preview.py
  • api/oss/tests/pytest/acceptance/workflows/test_workflow_embeds_cross_entity.py
  • api/oss/tests/pytest/acceptance/workflows/test_workflow_lineage.py
  • api/oss/tests/pytest/acceptance/workflows/test_workflow_retrieval_info.py
  • api/oss/tests/pytest/unit/environments/test_commit_validation.py
  • docs/designs/git-entities/request-body-fields.md
  • docs/designs/git-entities/tasks.md
  • sdks/python/agenta/sdk/managers/shared.py
  • web/oss/src/components/DeploymentsDashboard/store/deploymentStore.ts
  • web/packages/agenta-entities/src/environment/api/api.ts

Comment on lines 1067 to +1074
if application_variant_id:
if (
fork_request.application_variant_id
and fork_request.application_variant_id != application_variant_id
application_variant_ref.id
and application_variant_ref.id != application_variant_id
):
return ApplicationVariantResponse()

if not fork_request.application_variant_id:
fork_request.application_variant_id = application_variant_id
if not application_variant_ref.id:
application_variant_ref = Reference(id=application_variant_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return 400 for conflicting source-variant IDs.

Line 1072 turns contradictory inputs into 200 {count: 0}. That makes a malformed request look like an empty result set instead of a client error.

Suggested fix
         if application_variant_id:
             if (
                 application_variant_ref.id
                 and application_variant_ref.id != application_variant_id
             ):
-                return ApplicationVariantResponse()
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail="application_variant_id does not match application_variant_ref.id.",
+                )
             if not application_variant_ref.id:
                 application_variant_ref = Reference(id=application_variant_id)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if application_variant_id:
if (
fork_request.application_variant_id
and fork_request.application_variant_id != application_variant_id
application_variant_ref.id
and application_variant_ref.id != application_variant_id
):
return ApplicationVariantResponse()
if not fork_request.application_variant_id:
fork_request.application_variant_id = application_variant_id
if not application_variant_ref.id:
application_variant_ref = Reference(id=application_variant_id)
if application_variant_id:
if (
application_variant_ref.id
and application_variant_ref.id != application_variant_id
):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="application_variant_id does not match application_variant_ref.id.",
)
if not application_variant_ref.id:
application_variant_ref = Reference(id=application_variant_id)

Comment on lines 238 to 240
class TestsetRevisionCommitRequest(BaseModel):
testset_revision_commit: TestsetRevisionCommit = Field(
testset_revision: TestsetRevisionCommit = Field(
description="New revision to commit. Pass either `data` (full replacement of the testcase list) or `delta` (add/remove/replace operations against the base revision) — not both.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore the standardized wrapper keys for testset commit/log bodies.

Line 239 and Line 295 rename these payload keys to testset_revision / testset_revisions, but the contract this PR is standardizing is the snake_case form of the enclosing request type. Leaving testsets on the short names will make this surface reject callers that send testset_revision_commit / testset_revisions_log.

Suggested fix
 class TestsetRevisionCommitRequest(BaseModel):
-    testset_revision: TestsetRevisionCommit = Field(
+    testset_revision_commit: TestsetRevisionCommit = Field(
         description="New revision to commit. Pass either `data` (full replacement of the testcase list) or `delta` (add/remove/replace operations against the base revision) — not both.",
     )
@@
 class TestsetRevisionsLogRequest(BaseModel):
-    testset_revisions: TestsetRevisionsLog = Field(
+    testset_revisions_log: TestsetRevisionsLog = Field(
         description="Scope for the log: one of `testset_id`, `testset_variant_id`, or `testset_revision_id`. Optional `depth` limits how far back to walk.",
     )

Also applies to: 294-296

Comment on lines +654 to +677
async def fork_environment_variant(
self,
*,
project_id: UUID,
user_id: UUID,
#
environment_variant_fork: EnvironmentVariantFork,
environment_variant_ref: Reference,
environment_revision_ref: Optional[Reference] = None,
) -> Optional[EnvironmentVariant]:
_artifact_fork = ArtifactFork(
variant_id=environment_variant_ref.id,
revision_id=environment_revision_ref.id
if environment_revision_ref
else None,
variant=environment_variant_fork,
)

variant = await self.environments_dao.fork_variant(
project_id=project_id,
user_id=user_id,
#
artifact_fork=_artifact_fork,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve slug/version-based source refs in the fork path.

Line 664 drops environment_variant_ref and environment_revision_ref down to .id only. That breaks valid requests that identify the source variant or revision by slug/version, because the DAO now receives None for those cases instead of a resolvable source.

Suggested fix
     async def fork_environment_variant(
         self,
         *,
         project_id: UUID,
         user_id: UUID,
         #
         environment_variant_fork: EnvironmentVariantFork,
         environment_variant_ref: Reference,
         environment_revision_ref: Optional[Reference] = None,
     ) -> Optional[EnvironmentVariant]:
+        source_variant = await self.fetch_environment_variant(
+            project_id=project_id,
+            environment_variant_ref=environment_variant_ref,
+        )
+        if not source_variant:
+            return None
+
+        source_revision_id: Optional[UUID] = None
+        if environment_revision_ref is not None:
+            source_revision = await self.fetch_environment_revision(
+                project_id=project_id,
+                environment_variant_ref=Reference(id=source_variant.id),
+                environment_revision_ref=environment_revision_ref,
+            )
+            if not source_revision:
+                return None
+            source_revision_id = source_revision.id
+
         _artifact_fork = ArtifactFork(
-            variant_id=environment_variant_ref.id,
-            revision_id=environment_revision_ref.id
-            if environment_revision_ref
-            else None,
+            variant_id=source_variant.id,
+            revision_id=source_revision_id,
             variant=environment_variant_fork,
         )

Comment on lines +959 to +974
if evaluator_revision is not None:
if not evaluator_revision.data:
return None
(
resolved_data,
resolution_info,
) = await self.embeds_service.resolve_configuration(
project_id=project_id,
configuration=evaluator_revision.data.model_dump(mode="json"),
max_depth=max_depth,
max_embeds=max_embeds,
error_policy=ErrorPolicy(error_policy),
include_archived=include_archived,
)
evaluator_revision.data = EvaluatorRevisionData(**resolved_data)
return (evaluator_revision, resolution_info)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't treat an invalid inline resolve payload as “not found”.

If evaluator_revision is present but data is missing, this branch returns None. The routers then serialize that as an empty 200 response, so malformed inline input becomes indistinguishable from a missing revision. Raise a domain validation exception here instead so the API boundary can return a 400. As per coding guidelines: "Define appropriate service exceptions" and "Include structured context in domain exceptions (not just a message string) so the router can build rich HTTP error responses."

Comment on lines 590 to 594
_artifact_fork = ArtifactFork(
**query_fork.model_dump(mode="json"),
variant_id=query_variant_ref.id,
revision_id=query_revision_ref.id if query_revision_ref else None,
variant=query_variant_fork,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve Reference inputs before extracting fork IDs.

Line 591 and Line 592 only copy .id from the incoming refs. That breaks fork requests that identify the source variant by slug or the source revision by slug/version, even though this method still advertises Reference inputs. Resolve the refs first (or make this API explicitly UUID-only) before building ArtifactFork.

Comment on lines +500 to +503
_artifact_fork = ArtifactFork(
variant_id=testset_variant_ref.id,
revision_id=testset_revision_ref.id if testset_revision_ref else None,
variant=testset_variant_fork,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't collapse source refs to .id in the new fork path.

Line 501 and Line 502 discard every selector except id, so a valid Reference(slug=...) source variant or Reference(version=...) source revision can no longer be forked from this service. Resolve the refs to concrete IDs first, or tighten the contract so this method only accepts UUIDs.

Comment on lines 969 to +972
_artifact_fork = ArtifactFork(
**workflow_fork.model_dump(mode="json"),
variant_id=workflow_variant_ref.id,
revision_id=workflow_revision_ref.id if workflow_revision_ref else None,
variant=workflow_variant_fork,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve full Reference semantics when forking workflows.

Line 970 and Line 971 throw away slug/version selectors and keep only .id. That regresses any caller that forks from a slug-only variant ref or a version-based revision ref, and it also propagates into application/evaluator fork flows that now delegate here.

Comment on lines +56 to +57
| `POST /<entities>/revisions/retrieve` | `<entity>_ref: Reference` + `<entity>_variant_ref: Reference` + `<entity>_revision_ref: Reference` + `environment_ref: Reference` + `environment_variant_ref: Reference` + `environment_revision_ref: Reference` + `key: str` + `resolve: bool` *(queries, testsets, environments drop the `environment_*` triple and `key`)* |
| `POST /<entities>/revisions/resolve` *(workflows, applications, evaluators, environments)* | `<entity>_ref: Reference` + `<entity>_variant_ref: Reference` + `<entity>_revision_ref: Reference` + `<entity>_revision: <Entity>Revision` + `max_depth: int` + `max_embeds: int` + `error_policy: ErrorPolicy` |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify retrieve exceptions to include resolve omission for queries/testsets.

The generic retrieve rule is broader than the concrete class listings: queries/testsets are shown later without resolve. Please update this sentence so the generic contract matches the per-entity definitions.

Suggested doc fix
-| `POST /<entities>/revisions/retrieve` | `<entity>_ref: Reference` + `<entity>_variant_ref: Reference` + `<entity>_revision_ref: Reference` + `environment_ref: Reference` + `environment_variant_ref: Reference` + `environment_revision_ref: Reference` + `key: str` + `resolve: bool` *(queries, testsets, environments drop the `environment_*` triple and `key`)* |
+| `POST /<entities>/revisions/retrieve` | `<entity>_ref: Reference` + `<entity>_variant_ref: Reference` + `<entity>_revision_ref: Reference` + `environment_ref: Reference` + `environment_variant_ref: Reference` + `environment_revision_ref: Reference` + `key: str` + `resolve: bool` *(queries/testsets drop `environment_*`, `key`, and `resolve`; environments drop `key` only)* |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `POST /<entities>/revisions/retrieve` | `<entity>_ref: Reference` + `<entity>_variant_ref: Reference` + `<entity>_revision_ref: Reference` + `environment_ref: Reference` + `environment_variant_ref: Reference` + `environment_revision_ref: Reference` + `key: str` + `resolve: bool` *(queries, testsets, environments drop the `environment_*` triple and `key`)* |
| `POST /<entities>/revisions/resolve` *(workflows, applications, evaluators, environments)* | `<entity>_ref: Reference` + `<entity>_variant_ref: Reference` + `<entity>_revision_ref: Reference` + `<entity>_revision: <Entity>Revision` + `max_depth: int` + `max_embeds: int` + `error_policy: ErrorPolicy` |
| `POST /<entities>/revisions/retrieve` | `<entity>_ref: Reference` + `<entity>_variant_ref: Reference` + `<entity>_revision_ref: Reference` + `environment_ref: Reference` + `environment_variant_ref: Reference` + `environment_revision_ref: Reference` + `key: str` + `resolve: bool` *(queries/testsets drop `environment_*`, `key`, and `resolve`; environments drop `key` only)* |
| `POST /<entities>/revisions/resolve` *(workflows, applications, evaluators, environments)* | `<entity>_ref: Reference` + `<entity>_variant_ref: Reference` + `<entity>_revision_ref: Reference` + `<entity>_revision: <Entity>Revision` + `max_depth: int` + `max_embeds: int` + `error_policy: ErrorPolicy` |

Comment on lines +7 to +10
- [ ] **Fork parity — artifact level**: add `POST /<entities>/{id}/fork` to `queries`, `testsets`, and `environments` so all six git-backed entities have artifact-level fork. Requires `QueryFork` / `TestsetFork` / `EnvironmentFork` DTOs + `QueryForkRequest` / `TestsetForkRequest` / `EnvironmentForkRequest` models + router handlers + service methods.

- [ ] **Fork parity — variant level**: add `POST /<entities>/variants/fork` to all six git-backed entities. Currently only evaluators and queries have it; workflows, applications, testsets, and environments are missing it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Task status is internally inconsistent between “Open” and “Done”.

These sections currently claim the same fork-parity work is both pending and completed. Please reconcile them so one source of truth remains for rollout tracking.

Also applies to: 47-50


## Done

- [x] **Fork parity — artifact level**: `QueryForkRequest`, `TestsetForkRequest`, `EnvironmentForkRequest` + DTOs (`TestsetFork`, `EnvironmentFork`) + service methods + router endpoints added. All six entities now have `POST /variants/fork`.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Artifact-level endpoint path is incorrect in the completion note.

For artifact-level parity, the route should be POST /<entities>/{id}/fork, not POST /variants/fork (variant-level path).

Suggested doc fix
-- [x] **Fork parity — artifact level**: `QueryForkRequest`, `TestsetForkRequest`, `EnvironmentForkRequest` + DTOs (`TestsetFork`, `EnvironmentFork`) + service methods + router endpoints added. All six entities now have `POST /variants/fork`.
+- [x] **Fork parity — artifact level**: `QueryForkRequest`, `TestsetForkRequest`, `EnvironmentForkRequest` + DTOs (`TestsetFork`, `EnvironmentFork`) + service methods + router endpoints added. All six entities now have `POST /<entities>/{id}/fork`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- [x] **Fork parity — artifact level**: `QueryForkRequest`, `TestsetForkRequest`, `EnvironmentForkRequest` + DTOs (`TestsetFork`, `EnvironmentFork`) + service methods + router endpoints added. All six entities now have `POST /variants/fork`.
- [x] **Fork parity — artifact level**: `QueryForkRequest`, `TestsetForkRequest`, `EnvironmentForkRequest` + DTOs (`TestsetFork`, `EnvironmentFork`) + service methods + router endpoints added. All six entities now have `POST /<entities>/{id}/fork`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend refactoring A code change that neither fixes a bug nor adds a feature size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants