Skip to content

dispatch-workflow: let the agent specify the target ref/branch per call #49400

Description

@blozano-tt

Motivation

We're using dispatch-workflow in an agentic workflow ("Silencer", in tenstorrent/tt-metal) whose entire job is to scan CI logs for noise, root-cause it, and open a small draft PR fixing the emitter. The natural next step — and the whole reason we reached for dispatch-workflow instead of just documenting "a maintainer should re-run CI" — is to have the same agent turn that opens (or amends) the fix PR also kick off a fresh workflow_dispatch run of the CI workflow it just patched, on its own PR branch, so the fix is validated automatically instead of waiting on a human to notice and manually re-run it.

This is a genuinely common shape for agentic-workflow-authored-fix patterns: agent opens/updates a branch → agent wants to validate that branch by dispatching a workflow_dispatch-triggered CI workflow against it. dispatch-workflow is presumably the intended tool for exactly this, and it's almost there — but there's no way for the agent to say which ref to run against, only which workflow.

The gap

The dispatch_workflow safe-output the agent can emit only accepts workflow_name and inputs (see DispatchWorkflowOutput in schemas/agent-output.json). There is no ref (or branch) field in that schema. The actual ref used is resolved once, server-side, in dispatch_workflow.cjs, from:

  1. target-ref in frontmatter (a single static string, set at compile time), else
  2. GITHUB_HEAD_REF (only populated when the dispatching workflow's own trigger event is a PR), else
  3. GITHUB_REF / context.ref (the dispatching workflow's own triggering ref), else
  4. the target repo's default branch.

None of these give the agent a way to target an arbitrary branch it names at runtime — which is exactly what's needed when the branch (e.g. silencer/unused-var-layernorm) is created by the agent's own create-pull-request / push-to-pull-request-branch safe-output in that same turn, and doesn't correspond to anything in the dispatching workflow's own trigger context.

Concretely, for our scheduled Silencer workflow: it runs on schedule/workflow_dispatch/slash_command, never as a pull_request event, so GITHUB_HEAD_REF is always empty and GITHUB_REF is always refs/heads/main. target-ref can't help either — it's one fixed string in frontmatter, but Silencer's target branch is different every run (it names the branch itself, based on which noise pattern it's fixing that run). There is no configuration of the existing four resolution paths that can ever resolve to "whatever branch this agent turn just created."

We confirmed the rest of the mechanism works correctly: we built a dispatch-workflow config listing 44 target CI workflows (mostly .yaml-suffixed) and confirmed it compiles cleanly on v0.84.1 — this required the findWorkflowFile .yaml fix from #49230, which we validated by reproducing the identical compile failure on v0.84.0. The only remaining blocker is the missing per-call ref.

Proposed fix

Add an optional ref (or branch) field to the dispatch_workflow agent-output schema (DispatchWorkflowOutput in schemas/agent-output.json), and thread it through dispatch_workflow.cjs's ref resolution as the highest-priority source when present — ahead of target-ref, GITHUB_HEAD_REF, and GITHUB_REF:

let ref;
if (output.ref) {
  ref = output.ref.startsWith("refs/") ? output.ref : `refs/heads/${output.ref}`;
} else if (config.target_ref) {
  ref = config.target_ref;
} else if (process.env.GITHUB_HEAD_REF) {
  ref = `refs/heads/${process.env.GITHUB_HEAD_REF}`;
} else if (process.env.GITHUB_REF || context.ref) {
  ref = process.env.GITHUB_REF || context.ref;
} else {
  ref = await getDefaultBranchRef();
}

Whether an agent-supplied ref should be constrained (e.g. to branches created by that same agent turn's own safe-outputs, to avoid an agent dispatching arbitrary refs) is a real design question worth discussing — happy to hear what shape the maintainers would want here. Even a conservative version — e.g. only trusting output.ref when it exactly matches a branch name the same turn's create-pull-request/push-to-pull-request-branch output produced — would unblock our use case while keeping the safety story tight.

Related

How we found this

Investigating this while migrating tenstorrent/tt-metal's agentic workflows to v0.84.1 and trying to wire up exactly the pattern described above for our Silencer workflow. Happy to share the exact frontmatter config we tested, or test any proposed fix against it.

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions