Skip to content

feat(agent-service): add stateless /recommend endpoint (V1 hardcoded)#6436

Open
gupta-sahil01 wants to merge 1 commit into
apache:mainfrom
gupta-sahil01:feat/operator-recommender-backend
Open

feat(agent-service): add stateless /recommend endpoint (V1 hardcoded)#6436
gupta-sahil01 wants to merge 1 commit into
apache:mainfrom
gupta-sahil01:feat/operator-recommender-backend

Conversation

@gupta-sahil01

Copy link
Copy Markdown

Ambient operator recommender, Version 1 (#5240). Adds a stateless POST /api/recommend that, given the operator a user just added, returns a short ranked list of likely next operators for the canvas to render as ghost suggestions.

V1 ranks from a hardcoded rule table (no LLM call, no persistent state), so it ships and validates the full pipeline at zero API cost, per the discussion's V1/V2 split. Suggestions are validated against the live operator catalog (WorkflowSystemMetadata) when available, so a stale rule degrades to "not suggested" rather than a broken ghost. The response carries a strategy discriminator ("hardcoded") so V2 can swap in a small LLM behind the same request/response shape without a breaking change.

What changes were proposed in this PR?

Backend half (PR 1 of 2) of the ambient operator recommender, Version 1 — see #6293 (design discussion #5240).

Adds a stateless POST /api/recommend endpoint to agent-service: given the operator a user just added to the canvas, it returns a short ranked list of likely next operators for the frontend to render as ghost suggestions.

  • Stateless, zero API cost: V1 ranks from a small hardcoded rule table — no LLM call, no persistent state, no user token required.
  • Catalog-validated: suggestions are filtered against the live operator catalog (WorkflowSystemMetadata) when available, so a stale rule degrades to "not suggested" rather than a broken suggestion.
  • Forward-compatible: the response carries a strategy discriminator ("hardcoded" / "llm") so V2 can swap in a small-LLM ranker behind the same request/response shape without a breaking change.

The frontend that consumes this endpoint (ghost rendering, opt-in flag, click-to-materialize) follows in PR 2.

Any related issues, documentation, discussions?

How was this PR tested?

bun test in agent-service/ — 131 passed, including new src/recommender/recommender.spec.ts and src/server.recommend.spec.ts covering ranking order, catalog validation, terminal sinks (no suggestions), limit clamping, and 400s on malformed/empty input. Also verified live: POST /api/recommend with {"operatorType":"CSVFileScan"} returns the expected ranked JSON.

Was this PR authored or co-authored using generative AI tooling?

Generated-by: Anthropic Claude (Claude Code)

Ambient operator recommender, Version 1 (apache#5240). Adds a
stateless POST /api/recommend that, given the operator a user just added,
returns a short ranked list of likely next operators for the canvas to
render as ghost suggestions.

V1 ranks from a hardcoded rule table (no LLM call, no persistent state),
so it ships and validates the full pipeline at zero API cost, per the
discussion's V1/V2 split. Suggestions are validated against the live
operator catalog (WorkflowSystemMetadata) when available, so a stale rule
degrades to "not suggested" rather than a broken ghost. The response
carries a `strategy` discriminator ("hardcoded") so V2 can swap in a small
LLM behind the same request/response shape without a breaking change.
@github-actions

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @bobbai00
    You can notify them by mentioning @bobbai00 in a comment.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Thanks for your first contribution to Texera, @gupta-sahil01!

If you're looking for a good place to start, browse issues labeled starter-task; they're scoped to be approachable for newcomers.

You can drive common housekeeping yourself by commenting one of these commands on its own line:

  • Issues. Comment /take to assign an open issue to yourself, or /untake to release it. You can find unclaimed work with the search filter is:issue is:open no:assignee.
  • Sub-issues. To link issues into a parent/child hierarchy, comment /sub-issue #5166 #5222 on the parent to attach those children (or /unsub-issue #5166 #5222 to detach them). From a child issue, comment /parent-issue #5166 to set its parent, or /unparent-issue to clear it (the current parent is detected automatically). References may be written as #5166 or as a bare 5166; cross-repository references are not supported.
  • Pull requests (author only). Comment /request-review @user to request a review from someone, or /unrequest-review @user to withdraw that request.

Each command must match exactly: /take this will not work, only /take does. For the full contribution flow, see CONTRIBUTING.md.

@kz930

kz930 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Really clean V1 — the hardcoded/strategy-discriminator split and the catalog validation are a nice way to ship the whole pipeline at zero API cost. Two forward-looking notes, both about what the endpoint
knows before V2 lands:

  1. Validation checks existence, but not connectability.
    operatorTypeExists guarantees the ghost is a real operator, which is great for not suggesting broken types. It doesn't yet check that the suggested operator can actually be wired to the just-added operator's
    output. Two levels here:
  • Arity is checkable in V1 today: catalog metadata knows a target's input-port count, so an operator that needs two inputs (e.g. a join) can't be single-wired as a "next" step. Right now this is avoided by
    hand-curating the successor lists (no joins/unions appear as successors), but nothing enforces it — a future rule edit could reintroduce an un-connectable suggestion.
  • Schema/type compatibility can't be done in V1, since the request only carries the operator type, not the output schema. Worth flagging as a V2 need (and it ties into the next point).
  1. The reserved context field may be thinner than its stated purpose.
    Nice call reserving existingOperatorTypes so V2 can add graph context without a request-shape change. One thing to reconsider before V2 builds on it: a flat list of operator types loses the topology (what's
    actually upstream of the added operator) and the output-port schema — which is most of what an LLM would need to "bias with graph context." When V2 lands, capturing the upstream path / operator id + the
    output schema (rather than just the set of types present) would likely serve the ranker better — and it's the same information point 1's schema check would want, so the two converge.

Neither blocks V1 — just wanted to surface them while the request shape is still easy to evolve.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 6 out of 6 changed files in this pull request and generated 4 comments.

Comment on lines +412 to +418
.post("/", ({ body }) => recommendOperators(body as RecommendationRequest, WorkflowSystemMetadata.getInstance()), {
body: t.Object({
operatorType: t.String({ minLength: 1 }),
existingOperatorTypes: t.Optional(t.Array(t.String())),
limit: t.Optional(t.Number()),
}),
});
Comment on lines +65 to +69
test("rejects an empty operatorType as 400", async () => {
const res = await postRecommend({ operatorType: "" });
expect(res.status).toBe(400);
});

Comment on lines +409 to +410
set.status = 500;
return { error: errorMessage || "Internal server error" };
Comment on lines +67 to +68
/** Recommended operator type; always a real, catalog-known type. */
operatorType: string;
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.26590% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.28%. Comparing base (181e38b) to head (bda9360).
⚠️ Report is 11 commits behind head on main.

Files with missing lines Patch % Lines
agent-service/src/server.ts 85.00% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6436      +/-   ##
============================================
+ Coverage     70.17%   70.28%   +0.10%     
  Complexity     3387     3387              
============================================
  Files          1142     1145       +3     
  Lines         44844    45017     +173     
  Branches       4949     4949              
============================================
+ Hits          31468    31638     +170     
- Misses        11739    11742       +3     
  Partials       1637     1637              
Flag Coverage Δ *Carryforward flag
access-control-service 70.00% <ø> (ø) Carriedforward from 181e38b
agent-service 77.91% <98.26%> (+1.15%) ⬆️
amber 66.66% <ø> (ø) Carriedforward from 181e38b
computing-unit-managing-service 9.97% <ø> (ø) Carriedforward from 181e38b
config-service 52.30% <ø> (ø) Carriedforward from 181e38b
file-service 65.63% <ø> (ø) Carriedforward from 181e38b
frontend 68.91% <ø> (ø) Carriedforward from 181e38b
notebook-migration-service 78.94% <ø> (ø) Carriedforward from 181e38b
pyamber 91.19% <ø> (ø) Carriedforward from 181e38b
workflow-compiling-service 55.14% <ø> (ø) Carriedforward from 181e38b

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants