
Description
Build or migrate infra-controller REST API endpoints that call on-site Flow through the generic Flow gRPC proxy. Use when working on REST-to-Flow operations, ExecuteFlowGRPC, grpcproxy, v1.Flow methods, or migrating bespoke TaskRun/Flow workflows to the gRPC proxy.
SKILL.md
REST Flow gRPC Proxy Skill
Use this guidance when building or converting infra-controller REST API
endpoints that need to call on-site Flow through the generic Flow gRPC proxy.
Current Proxy Contract
- Cloud helper:
rest-api/api/pkg/api/handler/util/common/grpcproxy.go,ExecuteFlowGRPC. - Shared contract:
rest-api/common/pkg/grpcproxy/grpcproxy.go, withgrpcproxy.Requestandgrpcproxy.Response. Flow and Core share every layer of the proxy; the backend is named bygrpcproxy.Flow, and each layer keeps a per-backend wrapper only because Temporal dispatches on the registered workflow and activity names. - Site workflow/activity:
InvokeFlowGRPCandInvokeFlowGRPCOnSite. - Site Flow invocation:
FlowGrpcClient.InvokeJSON. - Temporal transport payload: protojson, so non-secret request fields and responses remain readable in Temporal UI.
- Secret transport payload: selected top-level protojson fields are redacted
from
RequestJSONand carried separately inEncryptedSecrets.grpcproxy.RedactSecrets/grpcproxy.MergeSecretsdo the splitting and are shared with the Core proxy. - Final site-to-Flow call: normal binary gRPC. The JSON step is only the generic Temporal payload representation.
Critical Difference From CoreProxy
ExecuteCoreGRPC always generates a fresh workflow ID
(core-grpc-{Method}-{uuid}) and does not set USE_EXISTING.
ExecuteFlowGRPC requires the caller to supply:
workflowID— deterministic for read/list dedup, fresh UUID for creates.conflictPolicy— the two travel together. A deterministic ID takesWORKFLOW_ID_CONFLICT_POLICY_USE_EXISTINGso identical in-flight requests coalesce onto one Flow call; a fresh ID takesWORKFLOW_ID_CONFLICT_POLICY_UNSPECIFIED, matchingExecuteCoreGRPC, whose IDs are always fresh.
Two helpers in common carry this for every migrated handler:
common.FlowWorkflowID applies the transport namespace, and
common.ProxyFlowGRPC dispatches and renders the failure as an Echo response.
Use ExecuteFlowGRPC directly only in helpers that hand the error back to a
caller instead of rendering a response, as resolveTrayIDsBySlot does; return
the *cutil.APIError unwrapped so the status the proxy chose survives.
Kinds of ID derivation that must stay in the handler, using the TaskRun endpoints as the worked example:
- A read whose response shape depends on a query flag must encode that flag, so
GetTaskRunneedsincludeStatsin the ID (flow-grpc-task-run-get-{runID}-{true|false}). Attaching to an execution started with the other value would return stats presence that contradicts the request. - A list must hash its query parameters (
QueryParamHash) into the ID, so two different filters never coalesce. - A create must never coalesce at all, so
CreateTaskRunuses a fresh UUID (flow-grpc-task-run-create-{uuid}) andUSE_EXISTINGcan never match.
Prefix deterministic IDs with the transport (flow-grpc-). A deterministic ID
plus USE_EXISTING attaches to whatever execution already holds that name, so
an ID shared with a different workflow type — a bespoke predecessor still
running during a rollout, for instance — yields a result payload this proxy
cannot decode.
Timeout Ladder
The ladder is bounded from the outside, not chosen from the on-site budget:
server.WriteTimeout is a deadline on the response write, so a handler that
answers later than that cannot deliver its answer. Keep
ActivityStartToCloseTimeout < grpcproxy.WorkflowExecutionTimeout <
cutil.WorkflowContextTimeout < server.WriteTimeout, currently
40s < 45s < 50s < 60s. Test_ProxyTimeoutsFitWriteTimeout in
api/internal/server guards the outer bound. Raising the on-site budget means
raising the server and load-balancer timeouts first.
Because the workflow timeout sits inside the caller's context timeout, a timeout
that Temporal itself reports comes from an execution that has closed. The ladder
does not cover the other way a caller loses its result: wfCtx derives from the
request context, so a client disconnect or a shorter upstream deadline can end
the wait while the execution runs on. wfCtx.Err() is the only evidence that
this happened, which is why a context.DeadlineExceeded in the workflow result
alone does not classify as it — it can come from inside the execution.
Before Coding
Confirm these details before editing:
- REST operation path, method, auth role, org/site scoping, Flow enablement check, request model, response model, and expected status code.
- Target Flow method, usually
/v1.Flow/<Method>, and whether it is unary. The proxy does not support streaming methods. - Typed protobuf request and optional typed protobuf response.
- Workflow ID derivation and conflict policy for this call.
- Secret fields that must not appear in Temporal history (top-level protojson field names).
- Whether the call fits in the proxy's budget. The activity is cut off at
grpcproxy.ActivityStartToCloseTimeout, currently 40s. The bespoke workflows declared 2 minutes, and bring-up and firmware 5 minutes, but every Flow caller bounded itself with the 50scutil.WorkflowContextTimeout, so no declared budget was reachable. Migrating still narrows the window: a Flow call that took 41-49s used to succeed and now fails. - Whether the call tolerates losing an activity retry.
InvokeFlowGRPCruns the activity withMaximumAttempts: 1, so a transient Flow error surfaces to the client instead of being retried. Bespoke workflows that allowed a second attempt lose it on migration, and the retry policy lives in the site workflow, so making it configurable takes another agent release to take effect.
Implementation Workflow
- Keep auth, tenant/org membership, site lookup, Flow enablement, role checks, request validation, and REST semantics in the REST handler.
- Build the typed Flow protobuf request before calling the proxy.
- Call
common.ExecuteFlowGRPC(ctx, siteTemporalClient, fullMethod, reqProto, respProtoOrNil, workflowID, conflictPolicy, siteIDSecretKey, secretFields...). PassingsecretFieldsrequires a non-emptysiteIDSecretKey; the helper rejects the combination rather than send the fields unredacted. - Return
StatusGatewayTimeoutas it comes. Do not callTerminateWorkflowOnTimeOut. When Temporal reports the timeout the execution has already closed, and terminating a closed execution fails and reports a data desync that did not happen. When the caller stopped waiting instead, the execution may still be running, and terminating it still does not help: the activity does not heartbeat, so Temporal cannot deliver cancellation while its Flow RPC is in progress, and terminating only the workflow discards the result without stopping that RPC. For a deterministic ID withUSE_EXISTINGit also frees the ID, so a retried request starts a duplicate mutation instead of attaching to the call already in flight. An abandoned execution stays bounded by the 45s and 40s timeouts. - Return a curated REST response. Do not expose Flow protobufs or secret fields directly unless the API contract already does.
- For a new public REST endpoint, register the route and update OpenAPI. For a migration from a bespoke workflow to the generic proxy, keep the REST contract and the parameter-derivation rules for the workflow ID unchanged, but namespace the resulting ID by transport as described above. The derived ID string itself must change, otherwise the proxy and the workflow it replaces can attach to each other.
Rollout Requirement
A workflow type is known only to the workers that registered it. The cloud API
and each site's agent ship as separate Helm releases (nico-rest and
nico-rest-site-agent) and cannot be upgraded atomically, and Temporal accepts
a submission for a type no worker knows: the execution is created, no worker can
advance it, and the caller sees only a timeout after
grpcproxy.WorkflowExecutionTimeout.
So a handler may not start dispatching through InvokeFlowGRPC in the same
release that first registers it on the site agent. Register the proxy first,
wait for every site to run that agent, and switch handlers in a later release.
Retire the workflow a migration replaces in a third release, once no supported
cloud release still submits it.
When adding a Flow endpoint, reach for the proxy: there is no longer a bespoke Flow workflow to copy, and adding one would reintroduce the per-method registration this replaced.
More skills from the infra-controller repository
View all 2 skillsMore from NVIDIA
View publishernemoclaw-user-guide
retrieve NemoClaw documentation and configuration
NemoClaw
Aug 25DocumentationMCPSearchmcore-build-and-dependency
manage Megatron-LM development environments
Megatron-LM
Jul 27ContainersDeploymentPythonmcore-bump-base-image
update NVIDIA PyTorch base images
Megatron-LM
Jul 14CI/CDDeploymentmcore-cicd
manage CI/CD pipelines for Megatron-LM
Megatron-LM
Aug 28CI/CDDeploymentGitHubmcore-create-issue
investigate CI failures and create issues
Megatron-LM
Jul 14DebuggingGitHubTriagemcore-linting-and-formatting
lint and format Megatron-LM code
Megatron-LM
Jul 14Best PracticesCode Analysis