feat: let flow builder operators build AI agents (#11898)

* feat: let flow builder operators build AI agents

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: check linked agents' bound tools and type, validate on the write's transaction

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: bound the linked-agent walk by sorting bound tool ids

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: let builders bind only the agent tool inputs left to the model

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: check every agent tool sharing a bound id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: show builders the agent's own tool inputs read-only and name agents in the setting

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor: show the agent's own tool inputs with the read-only step viewer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style: give the agent-set inputs heading the input label style

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style: label the agent's fixed tool inputs static inputs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: address cubic review on agent builder rights

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: check agent writes against the role's flows capability

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs: name the flows capability as the agent builder right

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: gate agent evals on the role's code capability

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor: move the composed-flow check into windmill-api-auth

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-10-23 15:05:20 +02:00
committed by GitHub
co-authored by Claude Opus 5.5
parent da443d2fb2
commit e5dedd147e
27 changed files with 763 additions and 167 deletions
+37
View File
@@ -499,6 +499,43 @@ async fn update_draft(
}
}
// An agent draft is held to the rules of its deploy, as a flow draft is: a developer who
// tests it in the agent editor runs its tools as themselves.
if caps.flows != AuthorLevel::Full && kind == UserDraftItemKind::Resource {
if let Some(value) = &req.value {
#[derive(Deserialize)]
struct ResourceDraft {
#[serde(default)]
args: serde_json::Value,
resource_type: Option<String>,
}
let draft: ResourceDraft = serde_json::from_str(&strip_json_nul(value.0.get()))
.map_err(|e| Error::BadRequest(format!("Invalid resource draft: {e}")))?;
// Whatever type the draft names, deploying it over a deployed agent keeps the row's type.
let is_agent = draft.resource_type.as_deref() == Some("ai_agent")
|| sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM resource WHERE workspace_id = $1 AND path = $2 AND resource_type = 'ai_agent')",
&w_id,
path,
)
.fetch_one(&db)
.await?
.unwrap_or(false);
if is_agent {
let mut tx = user_db.clone().begin(&authed).await?;
windmill_api_auth::composed::validate_operator_agent(
&draft.args,
&authed,
&db,
&mut tx,
&w_id,
)
.await?;
tx.commit().await?;
}
}
}
// A raw-app draft carries the app value and its policy side by side.
if caps.apps == AuthorLevel::Compose && kind == UserDraftItemKind::RawApp {
if let Some(value) = &req.value {